Net-Base Časopis

30.05.2026

AES-šifriranje u Delphi: robustan isječak izvornog koda s IV-om, saltom, zaglavljem i streamingom

Praktičan Delphi izvorni isječak za AES-šifriranje sa slučajnim saltom i IV-om, jasnom strukturom zaglavlja datoteke, PBKDF2-derivacijom ključa i streamingom — uključujući tipične zamke kod legacy formata, integriteta i operativnog rada.

30.05.2026

Od teme magazina do projektne prakse

Povezane stranice usluga i tehnologije za članak

Kod AES Verschlüsselung Delphi u praksi rijetko zapne „AES sam po sebi“, već u rubnim uvjetima: podaci se moraju obrađivati kao stream (datoteke, BLOB-ovi, backupi), stari formati moraju ostati čitljivi, a u radu treba omogućiti debuggabilnost (header, verzioniranje) i sigurne default postavke (Salt/IV nasumično, bez ponovne uporabe). Ovaj isječak koda zato ne prikazuje samo „Encrypt/Decrypt“, već mali, robustan format s headerom, verzijom, Saltom i IV-om – plus PBKDF2 za izvedbu ključa i mjesto gdje je smisleno dopuniti integritet.

Zašto „AES-String verschlüsseln“ gotovo nikad nije dovoljno

U prilagođenom poslovnom softveru šifriranje se tipično javlja na tri mjesta: (1) konfiguracija/secrets (npr. pristupni podaci), (2) razmjena/eksport datoteka i (3) mirovanje podataka (npr. arhive, dokumentni kontejneri). Naivan pristup „lozinka → AES-ključ → string unutra/van“ brzo zakaže:

  • IV-Wiederverwendung: U režimima poput CBC ili GCM inicijalizacijski vektor (IV) mora biti jedinstven za svaku enkripciju. Stalni IV je curenje, čak i kad je lozinka jaka.
  • Key aus Passwort ohne KDF: Korištenje lozinke direktno kao ključa (ili jednostavno hashiranje jednom) poziva na offline napade. KDF (Key Derivation Function) poput PBKDF2 usporava napadače ciljano.
  • Keine Formatversion: Bez headera/verzije teško ćete kasnije promijeniti broj iteracija, algoritam ili parametre bez da stari podaci ne postanu „siroti“.
  • Keine Integrität: AES-CBC šifrira, ali ne sprječava manipulaciju. Bez autentikacije (npr. HMAC ili AEAD poput GCM) izlažete se bitflipping-/padding-problemima i teško dijagnosticiranim greškama.

Srž ovog teksta: mali kontejnerski format koji podržava streaming, verzioniranje i izbjegava uobičajene pogreške.

AES Verschlüsselung Delphi mit Header, Salt, IV und PBKDF2

Definiramo jednostavan kontejnerski format koji se može koristiti i u podatkovnim BLOB-ovima u bazi ili u message-payloadima:

  • Magic: 4 bajta, npr. NBAE (brza provjera „je li ovo naš format?“)
  • Version: 1 bajt (omogućuje migraciju)
  • KDF-Parameter: broj iteracija (4 bajta)
  • Salt: 16 bajta (nasumično po datoteci)
  • IV: 16 bajta (nasumično po datoteci za AES-CBC)
  • Ciphertext: šifrirani korisni podaci (pogodno za streaming)

Važno: Salt i IV nisu tajni. Moraju biti novi za svaku enkripciju. Lozinka ostaje tajna; iz nje izvedeni ključ se ne pohranjuje.

AES Verschlüsselung Delphi im Stream: Container schreiben/lesen

Kod je namjerno napisan kao „plan izrade“: jasno odvojene funkcije, provjerljivi headeri, bez skrivenih globalnih varijabli. Za AES i PBKDF2 mnogi timovi koriste provjerenu kripto-biblioteku (npr. DEC). Isječak prikazuje format i obrazac za streaming; pozive na AES/PBKDF2 možete enkapsulirati tako da ih po potrebi zamijenite prema korištenoj biblioteci.

unit Nb.AesContainer;

interface

uses
System.SysUtils, System.Classes, System.NetEncoding;

type
ENbCryptoError = class(Exception);

TNbAesContainer = class
public
class procedure EncryptStreamToStream(const AIn: TStream; const AOut: TStream;
const APassword: string; const AIterations: Cardinal = 200000);

class procedure DecryptStreamToStream(const AIn: TStream; const AOut: TStream;
const APassword: string);

class function EncryptBytesToBase64(const APlain: TBytes; const APassword: string): string;
class function DecryptBase64ToBytes(const ACipherB64: string; const APassword: string): TBytes;
end;

implementation

const
CMagic: array[0..3] of AnsiChar = (‚N‘,’B‘,’A‘,’E‘);
CVersion: Byte = 1;
CSaltLen = 16;
CIvLen = 16;

type
TNbHeaderV1 = packed record
Magic: array[0..3] of AnsiChar;
Version: Byte;
Iterations: Cardinal; // little endian
Salt: array[0..CSaltLen-1] of Byte;
IV: array[0..CIvLen-1] of Byte;
end;

// — Ovisnosti koje trebate implementirati prema kripto-stogu —

procedure FillRandomBytes(var B: TBytes);
begin
// Za kriptografske slučajne brojeve: koristiti OS-CSPRNG (Windows BCryptGenRandom,
// Linux getrandom/urandom). Ovdje namjerno kao rezervirano mjesto.
raise ENbCryptoError.Create(‚FillRandomBytes: CSPRNG nije povezan‘);
end;

function PBKDF2_HMAC_SHA256(const APassword: string; const ASalt: TBytes;
const AIterations, AKeyLen: Cardinal): TBytes;
begin
// Implementacija npr. s DEC (PBKDF2) ili drugom bibliotekom.
// Rezultat: AKeyLen bajtova.
raise ENbCryptoError.Create(‚PBKDF2_HMAC_SHA256: nije implementiran‘);
end;

procedure AES256_CBC_EncryptStream(const AKey, AIV: TBytes; const AIn, AOut: TStream);
begin
// Implementacija preko biblioteke:
// – KeyLen = 32 Bytes
// – IVLen = 16 Bytes
// – PKCS#7 Padding
// Važno: obrađivati stream-orijentirano, ne sve u memoriji.
raise ENbCryptoError.Create(‚AES256_CBC_EncryptStream: nije implementiran‘);
end;

procedure AES256_CBC_DecryptStream(const AKey, AIV: TBytes; const AIn, AOut: TStream);
begin
raise ENbCryptoError.Create(‚AES256_CBC_DecryptStream: nije implementiran‘);
end;

// — Pomoćne —

procedure WriteHeaderV1(const AOut: TStream; const H: TNbHeaderV1);
begin
if AOut.Write(H, SizeOf(H)) <> SizeOf(H) then
raise ENbCryptoError.Create(‚Zaglavlje se nije moglo zapisati‘);
end;

function ReadHeaderV1(const AIn: TStream): TNbHeaderV1;
var
H: TNbHeaderV1;
begin
if AIn.Read(H, SizeOf(H)) <> SizeOf(H) then
raise ENbCryptoError.Create(‚Zaglavlje nepotpuno‘);

if (H.Magic[0] <> CMagic[0]) or (H.Magic[1] <> CMagic[1]) or
(H.Magic[2] <> CMagic[2]) or (H.Magic[3] <> CMagic[3]) then
raise ENbCryptoError.Create(‚Nije valjan kontejner (Magic se ne slaže)‘);

if H.Version <> CVersion then
raise ENbCryptoError.CreateFmt(‚Nepoznata verzija kontejnera: %d‘, [H.Version]);

if (H.Iterations < 10000) or (H.Iterations > 5000000) then
raise ENbCryptoError.Create(‚Broj iteracija izvan prihvatljivih granica‘);

Result := H;
end;

class procedure TNbAesContainer.EncryptStreamToStream(const AIn, AOut: TStream;
const APassword: string; const AIterations: Cardinal);
var
H: TNbHeaderV1;
Salt, IV, Key: TBytes;
begin
if APassword = “ then
raise ENbCryptoError.Create(‚Lozinka ne smije biti prazna‘);

// Salt/IV erzeugen
SetLength(Salt, CSaltLen);
SetLength(IV, CIvLen);
FillRandomBytes(Salt);
FillRandomBytes(IV);

// Header befüllen
Move(CMagic[0], H.Magic[0], Length(CMagic));
H.Version := CVersion;
H.Iterations := AIterations;
Move(Salt[0], H.Salt[0], CSaltLen);
Move(IV[0], H.IV[0], CIvLen);

WriteHeaderV1(AOut, H);

// Key ableiten (32 Bytes für AES-256)
Key := PBKDF2_HMAC_SHA256(APassword, Salt, AIterations, 32);

// Nutzdaten verschlüsseln (Ciphertext folgt direkt nach Header)
AES256_CBC_EncryptStream(Key, IV, AIn, AOut);
end;

class procedure TNbAesContainer.DecryptStreamToStream(const AIn, AOut: TStream;
const APassword: string);
var
H: TNbHeaderV1;
Salt, IV, Key: TBytes;
begin
if APassword = “ then
raise ENbCryptoError.Create(‚Lozinka ne smije biti prazna‘);

H := ReadHeaderV1(AIn);

SetLength(Salt, CSaltLen);
SetLength(IV, CIvLen);
Move(H.Salt[0], Salt[0], CSaltLen);
Move(H.IV[0], IV[0], CIvLen);

Key := PBKDF2_HMAC_SHA256(APassword, Salt, H.Iterations, 32);

// Entschlüsseln ab aktueller Stream-Position (nach Header)
AES256_CBC_DecryptStream(Key, IV, AIn, AOut);
end;

class function TNbAesContainer.EncryptBytesToBase64(const APlain: TBytes;
const APassword: string): string;
var
InS, OutS: TBytesStream;
begin
InS := TBytesStream.Create(APlain);
try
OutS := TBytesStream.Create;
try
EncryptStreamToStream(InS, OutS, APassword);
Result := TNetEncoding.Base64.EncodeBytesToString(OutS.Bytes, 0, OutS.Size);
finally
OutS.Free;
end;
finally
InS.Free;
end;
end;

class function TNbAesContainer.DecryptBase64ToBytes(const ACipherB64,
APassword: string): TBytes;
var
Cipher: TBytes;
InS, OutS: TBytesStream;
begin
Cipher := TNetEncoding.Base64.DecodeStringToBytes(ACipherB64);
InS := TBytesStream.Create(Cipher);
try
OutS := TBytesStream.Create;
try
DecryptStreamToStream(InS, OutS, APassword);
Result := OutS.Bytes;
SetLength(Result, OutS.Size);
finally
OutS.Free;
end;
finally
InS.Free;
end;
end;

end.

Svrha: Minimalni spremnik pogodan za datoteke i BLOB-ove, uključujući verzioniranje i KDF-parametre. Uvjeti: Morate ispod toga imati pravu vezu na CSPRNG (kriptografski siguran generator slučajnih brojeva iz operacijskog sustava) i robusnu AES/PBKDF2-implementaciju. Zapreke: Ne koristite „bilo koji“ Random (ne Random()), ne koristite fiksne IV-e, i pri decryptu planirajte jasnu obradu pogrešaka (pogrešna lozinka naspram oštećenih podataka). Varijante: umjesto CBC radije AEAD (vidi dolje), ili proširite Header za Algorithmus-ID i HMAC.

Integritet: zašto je AES-CBC sam po sebi u radu previše rizičan

AES-CBC je u mnogim Legacy-Kontexten i dalje prisutan i može raditi ako koristite dodatno osiguranje integriteta. Bez integriteta napadač može manipulirati Ciphertext-om; čak i bez aktivnog napadača, prijenosne pogreške ili neispravni slojevi pohrane mogu izgenerirati teško dijagnosticirane „Padding“ pogreške.

Pragmatične opcije:

  • Encrypt-then-HMAC: Nakon Ciphertext-a napišite HMAC (npr. HMAC-SHA-256) preko Header+Ciphertext-a. Pri čitanju prvo provjerite HMAC, zatim dešifrirajte. Za to idealno izvedite dva ključa iz PBKDF2 (npr. 64 байta: 32 za AES, 32 za HMAC), umjesto da isti ključ koristite dvaput.
  • AES-GCM: AEAD-modus (Authenticated Encryption with Associated Data). Isporučuje Ciphertext + Auth-Tag. To je danas često najsređeniji izbor, ako vaša Delphi-biblioteka stabilno podržava GCM. Polja Headera mogu se autentificirati kao „AAD“ bez potrebe da ih šifrirate.

Ako morate ostati pri CBC (npr. zbog Interop), Encrypt-then-HMAC je robustno dopunjavanje. Za nove formate GCM se isplati, jer dobivate autentifikaciju „automatski“ i obrasci pogrešaka postaju jasniji.

Neobično važno: „kriptografski slučajni brojevi“ i zašto System.Hash nije dovoljan

Čest Legacy-Reflex u Delphi-projektima: „Uzmemo jednostavno SHA256 preko vremenskog žiga + nešto i imamo Random.“ To nije pouzdana osnova. Za Salt i IV trebate CSPRNG (Cryptographically Secure Pseudo Random Number Generator) operacijskog sustava. Pod Windows to je tipično BCrypt-API (CNG), pod Linux kernel-generator poput getrandom() odnosno /dev/urandom. Razlika u praksi je: CSPRNG je dizajniran tako da iz promatranih vrijednosti nije moguće predvidjeti daljnje vrijednosti.

Arhitektonski trik: Kapsulirajte to u malu „RandomProvider“-unitu koju možete mockati u testovima. Time rješavate dva rubna slučaja odjednom: reproducibilne testove (s fiksnim Seed-om u mocku) i pravu sigurnost u produkciji (s OS-CSPRNG). Tako sprječavate da u nekom Hotfixu „samo tako“ ponovno uđe Random() jer je tako brže.

Debugging und Legacy-Migration: Versionierung ist kein Luxus

Header nije samo za „Krypto-Schönheit“, već za Wartbarkeit:

  • Iteration Tuning: PBKDF2-Iterationszahlen mijenjaju se tijekom godina. S Header-Feldom možete kasnije povećati broj iteracija bez da stare podatke učinite nečitljivima.
  • Formatwechsel: Verzija 2 može, npr., prijeći na AES-GCM ili dodati HMAC.
  • Diagnose im Feld: Magic/Version omogućuju brze provjere u Logs i Tools bez potrebe za dešifriranjem podataka.

Praktičan savjet: Implementirajte mali „Inspector“ koji samo čita zaglavlje (Magic/Version/Iterations) i zapisuje ga u Log. Tako razjasnite mnoge slučajeve podrške („Koja je ovdje verzija?“) bez rukovanja lozinkama.

Sauber migrieren: „Read old, write new“ statt Big Bang

Ako zamjenjujete stari format (npr. fiksni IV, bez KDF, Blowfish/3DES ili vlastiti XOR), u Delphi-projektima se pokazao slijedeći obrazac: pri čitanju prepoznajte više formata (Magic/Version ili fallback-heuristika), pri pisanju generirajte isključivo novi format. Dodatno, pri uspješnom dešifriranju možete u pozadini ponovno šifrirati („lazy migration“), ako to odgovara procesu. Time smanjujete rizik uvođenja i izbjegavate „sve jednom ponovno šifrirati“ kao održavanje.

Threading und Streaming: typische Kanten in Delphi

Šifriranje se često izvodi u worker-threadovima (npr. pri izvozu, pri uploadu u portal za klijente, pri pisanju velikih arhiva). Dva su aspekta koja se u Delphi-projektima redovito uočavaju:

  • Stream-Positionen: Prije šifriranja/dešifriranja definirajte jasne ugovore: ulazni stream se čita od trenutne pozicije, izlazni stream se piše od trenutne pozicije. Ako ponovno koristite streamove, obavezno namjerno postavite Position := 0.
  • Memory-Spitzen: Izbjegavajte „alles in TBytes“. Pristup temeljен na streamovima posebno je važan za velike datoteke. Ako vaša kripto-biblioteka prihvaća samo byte-arrayje, isplati se dodatni rad na prelasku na stream-sposobnu implementaciju ili izradi međuspremnika/adaptora.

Ako šifrirate u servisima (Windows- ili Linux-Services), obratite također pažnju na jasno logiranje iznimki: „pogrešna lozinka“, „zaglavlje oštećeno“, „Tag/HMAC nevaljan“ su različiti operativni slučajevi i trebaju biti razlikovani. Važno: poruke o greškama prema van ne smiju biti pretjerano detaljne (npr. ne „Padding pogrešan u bloku 7“ kao API-greška), ali interno u Logu mogu biti detaljnije.

Kada se pristup isplati – i gdje može zakazati

Isplati se ako: (a) dugoročno pohranjujete šifrirane podatke za izvoz/uvoz, (b) paralelno koristite različite verzije programa, (c) obrađujete podatke kao tokove ili (d) trebate čisto kripto-sučelje za više modula (Client/Server/Tooling).

Ne drži vodu ako pokušavate time riješiti „sve“: za transport je zadužen TLS, ne samorazvijeni AES-wrapper. Za secrete (lozinke, tokeni) često je primjereniji OS-Secret-Store ili Vault. A ako trebate interoperabilnost s drugim jezicima, zaglavlja, endianness i encoding morate točno dokumentirati (ili koristiti etablirani format).

Fazit: AES in Delphi ist weniger Algorithmus, mehr Engineering

Stvarna vrijednost ovog odsječka nije „AES radi“, nego operativno sposoban format: nasumični Salt i IV, verzionirano zaglavlje, PBKDF2-parametri u payloadu i obrada prilagođena streamovima. Za nove formate gdje je moguće dodajte i integritet (AES-GCM ili Encrypt-then-HMAC). Tako od „mi nešto šifriramo“ nastaje građevni blok koji u digitalnim poslovnim rješenjima i nakon godina ostaje održiv i migrabilan.

Ako trebate integrirati takav kontejner u postojeće Delphi okruženje ili ga uredno migrirati iz naslijeđenog formata, isplati se kratka provjera arhitekture (upravljanje ključevima, verzije formata, operacije/logiranje). Detalje ćemo po potrebi rado razjasniti u razgovoru:

U stručnom okruženju Delphi Aes i Pbkdf2 Delphi također igraju važnu ulogu kada integracije, tokovi podataka i daljnji razvoj moraju ispravno surađivati.

Razgovarajte o projektu ili modernizacijskom pothvatu s Net-Base.

Sljedeći korak

Kad se tema pretvori u stvarni projekt, arhitektura, postojeći sustav i operativni rad trebaju se rano sagledati zajedno.

Podržavamo vas ne samo u pojedinačnim pitanjima, već i kada iz isječaka izvornog koda, naslijeđenih sustava ili ideja za portale treba nastati pouzdan poslovni projekt.

  • Postojeće stanje, ciljna slika i tehnički rizici procjenjuju se zajedno.
  • REST, pristup podacima, portali i Rollout neće biti odgođeni kao kasne posljedice.
  • Vidite rano koji je put ekonomski i operativno održiv.

Podijeli objavu

Izravno proslijedite ovu objavu

LinkedIn, X, XING, Facebook, WhatsApp i e-mail su odmah dostupni. Za Instagram pripremamo link i kratak tekst.

E-pošta

Instagram se otvara u novoj kartici. Link i kratki tekst se prethodno kopiraju u međuspremnik.