Net-Base Časopis

30.05.2026

AES-šifrovanje u Delphi: robustan isječak izvornog koda sa IV, Salt, Header i streamingom

Praktičan Delphi izvorni isječak za AES-šifrovanje sa nasumičnim saltom i IV-om, jasnom strukturom zaglavlja datoteke, PBKDF2-derivacijom ključa i streamingom — uključujući tipične zamke kod naslijeđenih formata, pitanja integriteta i aspekte rada u produkciji.

30.05.2026

Od teme magazina do projektne prakse

Povezane stranice usluga i tehnologije za članak

U praksi kod AES šifrovanja Delphi rijetko je problem „sam AES“, već rubni uvjeti: podaci moraju biti obrađeni kao stream (datoteke, BLOB-ovi, backupi), stari formati moraju ostati čitljivi, a u radu su potrebni mogućnosti debugovanja (zaglavlje, verzionisanje) i sigurni podrazumevani izbori (Salt/IV nasumično, bez ponovne upotrebe). Ovaj isječak izvornog koda zato ne prikazuje samo „Encrypt/Decrypt“, već mali, robusni format sa zaglavljem, verzijom, Salt i IV – plus PBKDF2 za izvodjenje ključa i mjesto na kojem je smisleno dopuniti integritet.

Zašto „šifriranje AES-stringa“ gotovo nikad nije dovoljno

U prilagođenom poslovnom softveru enkripcija se tipično pojavljuje na tri mjesta: (1) konfiguracija/Secrets (npr. pristupni podaci), (2) razmjena/eksport fajlova i (3) podaci u mirovanju (npr. arhive, kontejneri dokumenata). Naivni pristup „lozinka → AES-Key → String unutra/van“ brzo puca:

  • Ponovna upotreba IV: Kod režima poput CBC ili GCM inicijalizacioni vektor (IV) mora biti jedinstven za svaku enkripciju. Konstantan IV predstavlja curenje, čak i ako je lozinka jaka.
  • Ključ iz lozinke bez KDF: Korištenje lozinke direktno kao ključa (ili jednokratno hashiranje) omogućava offline napade. KDF (Key Derivation Function) poput PBKDF2 ciljano usporava napadača.
  • Nema verzije formata: Bez zaglavlja/verzije teško je kasnije promijeniti broj iteracija, algoritam ili parametre bez da stari podaci postanu neupotrebljivi.
  • Nedostatak integriteta: AES-CBC šifrira, ali ne spriječava manipulaciju. Bez autentifikacije (npr. HMAC ili AEAD kao GCM) dobijate bitflipping-/padding-probleme i teško dijagnostikovane greške.

Suština ovog doprinosa: mali kontejnerski format koji podržava streaming, može se verzionisati i izbjegava uobičajene greške.

AES šifrovanje Delphi sa zaglavljem, Salt, IV i PBKDF2

Definišemo jednostavan kontejnerski format koji se može koristiti i u BLOB-ovima baze podataka ili u message-payloadima:

  • Magic: 4 Bytes, npr. NBAE (brza provjera „da li je ovo naš format?“)
  • Version: 1 Byte (omogućava migraciju)
  • KDF-Parameter: broj iteracija (4 Bytes)
  • Salt: 16 Bytes (nasumično po datoteci)
  • IV: 16 Bytes (nasumično po datoteci za AES-CBC)
  • Ciphertext: šifrirani korisni podaci (podržava 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 šifrovanje Delphi u streamu: pisanje/čitanje kontejnera

Kod je namjerno pisan kao „bauplan“: jasno odvojene funkcije, provjerljiva zaglavlja, nema skrivenih globalnih varijabli. Za AES i PBKDF2 mnogi timovi koriste provjerenu kripto-biblioteku (npr. DEC). Isječak prikazuje format i streaming-patern; pozivi AES/PBKDF2 su tako enkapsulirani da ih možete zamijeniti prema izabranoj biblioteci.

Delphi
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;

// --- Zavisnosti koje trebate implementirati u zavisnosti od Crypto-stacka ---

procedure FillRandomBytes(var B: TBytes);
begin
  // Za kriptografski slučajni broj: koristiti OS-CSPRNG (Windows BCryptGenRandom,
  // Linux getrandom/urandom). Ovdje namjerno ostavljeno kao zamjena.
  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 povezan');
end;

procedure AES256_CBC_EncryptStream(const AKey, AIV: TBytes; const AIn, AOut: TStream);
begin
  // Implementacija putem biblioteke:
  // - KeyLen = 32 Bytes
  // - IVLen  = 16 Bytes
  // - PKCS#7 Padding
  // Važno: obrada treba biti orijentisana na tok (stream), ne sve u memoriji.
  raise ENbCryptoError.Create('AES256_CBC_EncryptStream: nije povezan');
end;

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

// --- Pomoćne rutine ---

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 važeći kontejner (Magic se ne poklapa)');

  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 generisati
  SetLength(Salt, CSaltLen);
  SetLength(IV, CIvLen);
  FillRandomBytes(Salt);
  FillRandomBytes(IV);

  // Popuniti zaglavlje
  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);

  // Izvesti ključ (32 bajta za AES-256)
  Key := PBKDF2_HMAC_SHA256(APassword, Salt, AIterations, 32);

  // Šifrirati korisne podatke (ciphertext slijedi odmah nakon zaglavlja)
  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);

  // Dekriptovati od trenutne pozicije u streamu (nakon zaglavlja)
  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 kontejner pogodan za fajlove i BLOB-ove, uključujući verzionisanje i KDF-parametre. Ograničenja: Morate koristiti pravu CSPRNG-vezu (kriptografski siguran izvor slučajnosti iz operativnog sistema) i robusnu AES/PBKDF2-implementaciju ispod. Zamke: Nemojte koristiti „bilo koji“ Random (ne Random()), bez fiksnih IV-ova, i pri dekripciji predvidite jasno rukovanje greškama (pogrešna lozinka naspram oštećenih podataka). Varijante: umjesto CBC radije AEAD (vidi dolje), ili proširiti header za ID algoritma i HMAC.

Integritet: warum AES-CBC allein im Betrieb zu riskant ist

AES-CBC je i dalje prisutan u mnogim legacy kontekstima i može funkcionisati ako dodatno koristite provjeru integriteta. Bez integriteta napadač može manipulisati cipherteksom; čak i bez aktivnog napada, greške u prijenosu ili oštećeni slojevi skladištenja stvaraju teško dijagnosticirajuće „padding“-greške.

Pragmatske opcije:

  • Encrypt-then-HMAC: Nakon ciphertexta zapišite HMAC (npr. HMAC-SHA-256) preko Header+Ciphertext. Pri čitanju prvo provjerite HMAC, zatim dešifrirajte. Za to izvedite idealno dva ključa iz PBKDF2 (npr. 64 bajta: 32 za AES, 32 za HMAC), umjesto korištenja istog ključa dvaput.
  • AES-GCM: AEAD-mod (Authenticated Encryption with Associated Data). Daje Ciphertext + Auth-Tag. To je danas često najčistiji izbor, ako vaša Delphi-biblioteka stabilno podržava GCM. Polja headera se mogu autentificirati kao „AAD“ bez da ih šifrirate.

Ako morate ostati pri CBC (npr. zbog interoperabilnosti), Encrypt-then-HMAC je robusno dopunjavanje. Za nove formate isplati se GCM, jer dobijate autentifikaciju „uz put“ i jasnije obrasce grešaka.

Ungewöhnlich wichtig: „Kryptografischer Zufall“ und warum System.Hash nicht reicht

Čest legacy-refleks u Delphi-projektima: „Uzmemo jednostavno SHA256 nad timestampom + nečim i imamo Random.“ To nije pouzdana osnova. Za salt i IV trebate CSPRNG (Cryptographically Secure Pseudo Random Number Generator) operativnog sistema. Pod Windows to je tipično BCrypt-API (CNG), pod Linux generator u kernelu poput getrandom() odnosno /dev/urandom. Razlika je praktična: CSPRNG je dizajniran tako da se iz promatranih vrijednosti ne mogu predvidjeti naredne vrijednosti.

Arhitektonski trik: Kapsulirajte to u malu „RandomProvider“-unitu koju možete mockati u testovima. Time rješavate odmah dva slučaja: reproduktivne testove (sa fiksnim seedom u mocku) i pravu sigurnost u produkciji (sa OS-CSPRNG). Tako spriječite da u hotfixu „samo tako“ opet završi Random(), jer je brzo i jednostavno.

Debugging und Legacy-Migration: Versionierung ist kein Luxus

Header nije samo za „krypto-finu“ strukturu, već i za održavanje:

  • Iteration Tuning: PBKDF2-broj iteracija se mijenja tokom godina. Sa poljem u headeru kasnije možete povećati broj iteracija bez da stare podatke učinite nečitljivim.
  • Formatwechsel: Verzija 2 bi, npr., mogla preći na AES-GCM ili dodati HMAC.
  • Diagnose im Feld: Magic/Version omogućavaju brze provjere u logovima i alatima bez dešifriranja podataka.

Praktičan savjet: Implementirajte mali „Inspector“, koji čita samo header (Magic/Version/Iterations) i zapisuje 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 želite zamijeniti stari format (npr. fiksni IV, bez KDF, Blowfish/3DES ili vlastito XOR rješenje), u Delphi-projektima se pokazao ovaj obrazac: pri čitanju prepoznajte više formata (Magic/Version ili fallback heuristika), pri pisanju generirajte samo novi format. Dodatno, pri uspješnom dešifriranju možete u pozadini re-encrypten („lazy migration“), ako to odgovara procesu. Time smanjujete rizik pri rollout-u i izbjegavate „sve jednom ponovno enkriptovati“ kao prozor za održavanje.

Threading und Streaming: typische Kanten in Delphi

Enkripcija često radi u worker-threadovima (npr. pri exportu, pri uploadu u portal za klijente, pri pisanju velikih arhiva). Dva aspekta koja se u Delphi-projektima redovno pojavljuju:

  • Pozicije streama: Prije enkriptovanja/dekriptovanja definirajte jasne ugovore: input-stream se čita od tekuće pozicije, output-stream se zapisuje od tekuće pozicije. Ako ponovo koristite streamove, obavezno svjesno postavite Position := 0.
  • Pikovi memorije: Izbjegavajte „sve u TBytes“. Pristup zasnovan na streamovima posebno je važan za velike fajlove. Ako vaša crypto-biblioteka prihvata samo nizove bajtova, isplati se dodatni napor da se prijeđete na stream-kompatibilnu implementaciju ili izgradite puferski adapter.

Ako enkriptujete unutar servisa (Windows- ili Linux-Services), obratite dodatno pažnju na uredno logovanje izuzetaka: „pogrešna lozinka“, „header oštećen“, „Tag/HMAC nevažeći“ su različiti operativni slučajevi i trebaju biti razdvojivi. Važno: poruke o greškama prema vani ne smiju biti previše detaljne (nema „Padding pogrešan u bloku 7“ kao API-greška), ali interno u logu takve detalje evidentirajte.

Kada se pristup isplati – i gdje može zakazati

Isplati se, ako: (a) trajno pohranjujete enkriptovane export/import podatke, (b) upravljate paralelnim verzijama programa, (c) obrađujete podatke kao streamove ili (d) trebate čist kripto-API za više modula (Client/Server/Tooling).

Može zakazati, ako pokušavate s tim riješiti „sve“: za transport odgovoran je TLS, a ne vlastiti AES-wrapper. Za secrets (lozinke, tokeni) često je prikladniji OS-Secret-Store ili Vault. I ako trebate interoperabilnost s drugim jezicima, morate header, endianness i encoding precizno dokumentirati (ili koristiti etablirani format).

Fazit: AES in Delphi ist weniger Algorithmus, mehr Engineering

Prava vrijednost ovog fragmenta nije „AES radi“, nego operativno upotrebljiv format: nasumični salt i IV, verzionirani header, PBKDF2-parametri u payloadu i stream-kompatibilna obrada. Za nove formate dodajte integritet kad god je moguće (AES-GCM ili Encrypt-then-HMAC). Tako od „mi nešto enkriptujemo“ nastaje komponenta koja u digitalnim poslovnim rješenjima ostaje održiva i migrabilna i nakon godina.

Ako trebate integrisati takav kontejner u postojeću Delphi-okruženje ili ga uredno migrirati iz naslijeđenog formata, isplati se kratak pregled arhitekture (upravljanje ključevima, verzije formata, operativni rad/logiranje). Detalje po potrebi rado razjasnimo u razgovoru:

U stručnom okruženju također Delphi Aes i Pbkdf2 Delphi imaju važnu ulogu, kada integracije, tokovi podataka i dalji razvoj moraju besprijekorno funkcionisati zajedno.

Razgovarajte o projektu ili modernizacijskom poduhvatu sa Net-Base.

Sljedeći korak

Ako se tema pretvori u stvarni projekat, arhitekturu, postojeći sistem i operacije trebalo bi rano zajednički razmotriti.

Wir unterstuetzen nicht nur bei Einzelfragen, sondern auch dann, wenn aus Source-Schnipseln, Legacy-Themen oder Portalideen ein belastbares Unternehmensprojekt werden soll.

  • Postojeće stanje, ciljno stanje i tehnički rizici procjenjuju se zajedno.
  • REST, pristup podacima, portali i Rollout neće se odgađati za kasnije faze.
  • Pravovremeno prepoznajete koji pristup je ekonomski i operativno održiv.

Podijeli objavu

Ovu objavu direktno proslijediti

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

E-pošta

Instagram se otvara u novom tabu. Link i kratak tekst se prethodno kopiraju u međuspremnik.