Net-Base Revistă

30.05.2026

Criptare AES în Delphi: un fragment de cod sursă robust cu IV, salt, header și streaming

Un fragment de cod Delphi practic pentru criptarea AES cu salt și IV aleatorii, structură clară a antetului fișierului, derivare a cheii PBKDF2 și streaming – inclusiv capcane tipice în formate legacy, integritate și operare.

30.05.2026

De la tema din revistă la practica în proiecte

Pagini relevante de servicii și pagini tehnice pentru articol

În practică, la Criptare AES Delphi eșecul rar provine din „AES în sine“, ci din condițiile limită: datele trebuie procesate ca flux (fișiere, BLOB-uri, backup-uri), formatele vechi trebuie să rămână lizibile, iar în operare ai nevoie de posibilitate de depanare (header, versionare) și de valori implicite sigure (Salt/IV aleator, fără reutilizare). Acest fragment de cod sursă arată, prin urmare, nu doar „Encrypt/Decrypt“, ci un mic format robust cu header, versiune, Salt și IV – plus PBKDF2 pentru derivarea cheii și un punct în care integritatea poate fi completată în mod rezonabil.

De ce „criptarea unui string cu AES“ este aproape niciodată suficientă

În software-ul individual pentru întreprinderi, criptarea apare de obicei în trei locuri: (1) configurații/secrete (de ex. date de autentificare), (2) fișiere de schimb/export și (3) date în repaus (de ex. arhive, containere de documente). Abordarea naivă „parolă → cheie AES → string in/out“ dă greș rapid:

  • Reutilizarea IV: La moduri precum CBC sau GCM, un vector de inițializare (IV) trebuie să fie unic pentru fiecare criptare. Un IV constant este o scurgere, chiar dacă parola este puternică.
  • Cheie din parolă fără KDF: Folosirea parolei direct ca cheie (sau doar hash-uită o singură dată) invită atacuri offline. O KDF (Key Derivation Function) precum PBKDF2 frânează atacatorii într-un mod țintit.
  • Lipsa versiunii formatului: Fără header/versiune, veți putea modifica dificil numărul de iterații, algoritmul sau parametrii mai târziu, fără ca datele vechi să devină inaccesibile.
  • Fără integritate: AES-CBC criptează, dar nu previne manipularea. Fără autentificare (de ex. HMAC sau AEAD precum GCM) veți obține probleme de tip bitflipping/padding și simptome greu de diagnosticat.

Esența acestui articol: un mic format container care suportă streaming, este versionabil și evită erorile uzuale.

Criptare AES Delphi cu header, Salt, IV și PBKDF2

Definim un format container simplu, care poate fi folosit și în BLOB-uri de baze de date sau în payload-uri de mesaje:

  • Magic: 4 octeți, de ex. NBAE (verificare rapidă „Este acesta formatul nostru?”)
  • Versiune: 1 octet (permite migrarea)
  • Parametri KDF: număr de iterații (4 octeți)
  • Salt: 16 octeți (aleator per fișier)
  • IV: 16 octeți (aleator per fișier pentru AES-CBC)
  • Ciphertext: date utile criptate (compatibil cu streaming)

Important: Salt și IV nu sunt secrete. Ele trebuie doar să fie noi pentru fiecare criptare. Parola rămâne secretă; cheia derivată din ea nu este stocată.

Criptare AES Delphi în flux: scriere/citire a containerului

Codul este scris intenționat ca un „plan de construcție”: funcții clar separate, headere verificabile, niciun global ascuns. Pentru AES și PBKDF2, multe echipe folosesc o bibliotecă crypto consacrată (de ex. DEC). Fragmentul arată formatul și pattern-ul de streaming; apelurile AES/PBKDF2 sunt încapsulate astfel încât să le puteți înlocui în funcție de bibliotecă.

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;

// — Dependențe pe care trebuie să le implementați în funcție de Crypto-Stack —

procedure FillRandomBytes(var B: TBytes);
begin
// Pentru entropie criptografică: utilizați CSPRNG-ul OS-ului (Windows BCryptGenRandom,
// Linux getrandom/urandom). Aici este intenționat un substitut.
raise ENbCryptoError.Create(‚FillRandomBytes: CSPRNG neimplementat‘);
end;

function PBKDF2_HMAC_SHA256(const APassword: string; const ASalt: TBytes;
const AIterations, AKeyLen: Cardinal): TBytes;
begin
// Implementare, de ex. cu DEC (PBKDF2) sau o altă bibliotecă.
// Rezultat: AKeyLen octeți.
raise ENbCryptoError.Create(‚PBKDF2_HMAC_SHA256: neimplementat‘);
end;

procedure AES256_CBC_EncryptStream(const AKey, AIV: TBytes; const AIn, AOut: TStream);
begin
// Implementare prin bibliotecă:
// – KeyLen = 32 octeți
// – IVLen = 16 octeți
// – PKCS#7 Padding
// Important: procesați orientat pe stream, nu totul în memorie.
raise ENbCryptoError.Create(‚AES256_CBC_EncryptStream: neimplementat‘);
end;

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

// — Helper —

procedure WriteHeaderV1(const AOut: TStream; const H: TNbHeaderV1);
begin
if AOut.Write(H, SizeOf(H)) <> SizeOf(H) then
raise ENbCryptoError.Create(‚Antetul nu a putut fi scris‘);
end;

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

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(‚Container nevalid (Magic nu corespunde)‘);

if H.Version <> CVersion then
raise ENbCryptoError.CreateFmt(‚Versiune de container necunoscută: %d‘, [H.Version]);

if (H.Iterations < 10000) or (H.Iterations > 5000000) then
raise ENbCryptoError.Create(‚Număr de iterări în afara limitelor plauzibile‘);

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(‚Parola nu poate fi goală‘);

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

// Completare antet
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);

// Derivare cheie (32 octeți pentru AES-256)
Key := PBKDF2_HMAC_SHA256(APassword, Salt, AIterations, 32);

// Criptare date utile (ciphertext urmează imediat după antet)
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(‚Parola nu poate fi goală‘);

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

// Decriptare de la poziția curentă a stream-ului (după antet)
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.

Scop: Un container minimal potrivit pentru fișiere și BLOB-uri, incluzând versiuni și parametri KDF. Condiții: Trebuie să utilizați o legătură reală către CSPRNG-ul sistemului de operare (aleator criptografic sigur) și să implementați dedesubt un AES/PBKDF2 robust. Capcane: Nu folosiți „orice” generator de aleatoriu (niciun Random()), nu folosiți IV-uri fixe și planificați tratarea erorilor la decriptare pentru a distinge clar între parolă greșită și date corupte. Variante: în loc de CBC preferați AEAD (vezi mai jos), sau extindeți header-ul cu ID-ul algoritmului și HMAC.

Integritate: de ce AES-CBC singur în producție este riscant

AES-CBC este încă prezent în multe contexte legacy și poate funcționa dacă folosiți în plus o verificare a integrității. Fără verificare a integrității, un atacator poate modifica ciphertext-ul; chiar și fără un atac activ, erorile de transmisie sau straturile de stocare defecte generează erori de tip „padding” greu de diagnosticat.

Opțiuni pragmatice:

  • Encrypt-then-HMAC: După ciphertext scrieți un HMAC (de ex. HMAC-SHA-256) peste Header+Ciphertext. La citire verificați întâi HMAC-ul, apoi decriptați. Ideal derivați două chei din PBKDF2 (de ex. 64 octeți: 32 pentru AES, 32 pentru HMAC), în loc să reutilizați aceeași cheie pentru ambele.
  • AES-GCM: Mod AEAD (Authenticated Encryption with Associated Data). Produce ciphertext + tag de autentificare. Astăzi aceasta este adesea alegerea cea mai curată, dacă biblioteca Delphi suportă GCM stabil. Câmpurile din header pot fi autentificate ca „AAD” fără a fi nevoie să le criptați.

Dacă trebuie să rămâneți la CBC (de ex. din motive de interoperabilitate), Encrypt-then-HMAC este completarea robustă. Pentru formate noi merită GCM, deoarece obțineți autentificare „din oficiu” și lucrurile se diagnostichează mai clar.

Neobișnuit de important: „aleator criptografic” și de ce System.Hash nu este suficient

Un reflex frecvent în proiectele Delphi legacy: „facem un SHA256 peste un timestamp + ceva și avem aleatoriu.” Aceasta nu este o bază de încredere. Pentru salt și IV aveți nevoie de un CSPRNG (Cryptographically Secure Pseudo Random Number Generator) al sistemului de operare. Sub Windows aceasta este de regulă API-ul BCrypt (CNG), sub Linux un generator din kernel precum getrandom() sau /dev/urandom. Diferența este practică: un CSPRNG este proiectat astfel încât din valorile observate să nu se poată prezice valori viitoare.

Truc arhitectural: încapsulați asta într-o mică unitate „RandomProvider” pe care o puteți mock-ui în teste. Astfel rezolvați două cazuri: teste reproducerabile (cu seed fix în mock) și securitate reală în producție (cu OS-CSPRNG). Astfel preveniți ca într-un hotfix să „pătrundă” din nou Random() pentru că este mai rapid.

Debugging și migrare legacy: versiunea nu este un lux

Header-ul nu este doar pentru „estetică criptografică”, ci pentru mentenabilitate:

  • Ajustare iteratii: Numărul de iterații PBKDF2 se schimbă in timp. Un câmp în header vă permite să creșteți ulterior fără a face datele vechi inutilizabile.
  • Schimbări de format: Versiunea 2 ar putea trece, de exemplu, la AES-GCM sau să adauge un HMAC.
  • Diagnostice pe teren: Magic/Version permit verificări rapide în loguri și unelte, fără a decripta datele.

Sfat practic: Implementați un mic „inspector” care citește doar header-ul (Magic/Version/Iterations) și îl scrie într-un log. Astfel clarificați multe cazuri de suport („Ce versiune este aici?”) fără a gestiona parole.

Migrați curat: „Read old, write new” în loc de Big Bang

Dacă înlocuiți un format vechi (de ex. IV fix, fără KDF, Blowfish/3DES sau XOR făcut in-house), în proiectele Delphi s-a dovedit util următorul pattern: la citire detectați mai multe formate (Magic/Version sau euristică de fallback), la scriere produceți doar noul format. În plus, după decriptare reușită puteți re-encripta în fundal („migrare leneșă”), dacă se potrivește procesului. Astfel reduceți riscul la rollout și evitați fereastra de mentenanță „rescriem totul odată”.

Threading și streaming: puncte critice tipice în Delphi

Criptarea rulează adesea în worker-threads (de ex. la export, la upload într-un portalul clientului, la scrierea unor arhive mari). Două aspecte care apar frecvent în proiectele Delphi:

  • Pozițiile din stream: Înainte de criptare/decriptare definiți contracte clare: Input-streamul se citește din poziția curentă, Output-streamul se scrie din poziția curentă. Când reutilizați streamuri, setați în mod conștient Position := 0.
  • Vârfuri de memorie: Evitați „totul în TBytes”. Abordarea pe stream este esențială pentru fișiere mari. Dacă biblioteca crypto acceptă doar array-uri de bytes, merită efortul suplimentar de a implementa suport pentru streamuri sau de a construi un adaptor tamponat.

Dacă criptați în servicii (Windows- sau Linux-servicii), acordați atenție și la un logging curat al excepțiilor: „parolă greșită”, „header corupt”, „Tag/HMAC invalid” sunt situații operaționale diferite și ar trebui să fie distincte. Important: mesajele de eroare nu trebuie să fie prea detaliate extern (nu afișați „Padding greșit în blocul 7” ca eroare API), dar în logurile interne pot fi detaliate.

Când merită abordarea — și unde poate eșua

Merită când: (a) stocați durabil date de export/import criptate, (b) rulați versiuni diferite ale programului în paralel, (c) procesați date ca streamuri sau (d) aveți nevoie de o interfață crypto curată pentru mai multe module (Client/Server/Tooling).

Eșuează când încercați să rezolve „totul”: pentru transport răspunde TLS, nu un wrapper AES făcut in-house. Pentru secrete (parole, tokenuri) este adesea mai potrivit un magazin de secrete al sistemului de operare sau un Vault. Și dacă aveți nevoie de interoperabilitate cu alte limbaje, trebuie documentate exact header-ul, endianness și encodingul (sau folosiți un format stabilit).

Concluzie: AES în Delphi este mai puțin algoritm, mai mult inginerie

Beneficiul real al acestui fragment nu este „AES funcționează”, ci un format operațional: salt și IV aleator, header versionat, parametri PBKDF2 în payload și procesare compatibilă cu streamuri. Completați pentru formate noi, pe cât posibil, cu integritate (AES-GCM sau Encrypt-then-HMAC). Astfel, din „criptăm ceva” obțineți un component care, în soluții digitale pentru întreprinderi, rămâne mentenabil și migrabil și după ani.

Dacă trebuie să integrați un astfel de container într-o infrastructură Delphi existentă sau să migrați curat de la un format legacy, merită un scurt control de arhitectură (gestionarea cheilor, versiunile formatului, operare/logare). Detaliile le clarificăm la nevoie cu plăcere într-o discuție:

În contextul funcțional, și Delphi Aes și Pbkdf2 Delphi joacă un rol important, atunci când integrările, fluxurile de date și dezvoltarea ulterioară trebuie să interacționeze coerent.

Discutați un proiect sau o inițiativă de modernizare cu Net-Base.

Următorul pas

Când o temă devine un proiect real, arhitectura, infrastructura existentă și operarea trebuie analizate împreună de la început.

Nu oferim sprijin doar pentru întrebări punctuale, ci și atunci când fragmente de cod sursă, probleme legacy sau idei de portal trebuie transformate într-un proiect robust la nivel de companie.

  • Situația curentă, starea țintă și riscurile tehnice sunt evaluate împreună.
  • REST, accesul la date, portalurile și Rollout nu sunt amânate ca consecințe ulterioare.
  • Veți vedea din timp ce cale este viabilă din punct de vedere economic și operațional.

Partajează postarea

Distribuiți această postare direct

LinkedIn, X, XING, Facebook, WhatsApp și e-mail sunt disponibile imediat. Pentru Instagram pregătim linkul și textul scurt imediat.

E-mail

Instagram se deschide într-o filă nouă. Linkul și textul scurt se copiază în prealabil în clipboard.