Net-Base Revistë

30.05.2026

AES-kriptimi në Delphi: një fragment kodi i qëndrueshëm me IV, Salt, Header dhe Streaming

Një fragment kodi burimor praktik Delphi për enkriptim AES me salt dhe IV të rastësishëm, me strukturë të qartë të header-it të skedarit, derivim çelësi me PBKDF2 dhe streaming — përfshirë kurthet tipike në formatet legacy, integritetin dhe çështjet e operimit.

30.05.2026

Nga tema e revistës në praktikën e projektit

Faqe shërbimi dhe teknike të përshtatshme për artikullin

Në praktikë tek AES Verschlüsselung Delphi rrallë dështon për shkak të „AES vetë“, por për shkak të kushteve të kufizuara: të dhënat duhet të përpunohen si stream (skedarë, BLOB-e, kopje rezervë), formatet e vjetra duhet të mbeten të lexueshme, dhe në operim nevojitet mundësi debugimi (header, versionim) dhe default-e të sigurta (Salt/IV rastësor, pa ripërdorim). Ky copë kodi burimor prandaj nuk tregon vetëm „Encrypt/Decrypt“, por një format të vogël dhe të qëndrueshëm me header, version, Salt dhe IV – plus PBKDF2 për nxjerrjen e çelësit dhe një vend ku integriteti mund të shtohet në mënyrë të arsyeshme.

Pse „enkriptimi i një stringu me AES“ pothuajse kurrë nuk mjafton

Në softuerin e personalizuar të ndërmarrjeve, enkriptimi zakonisht shfaqet në tre vende: (1) konfigurim/sekrete (p.sh. kredencialet), (2) skedarë shkëmbimi/eksporti dhe (3) të dhëna në pushim (p.sh. arkiva, kontejnerë dokumentesh). Qasja naive „fjalëkalim → AES-Key → vargu brenda/jashtë“ prishet shpejt:

  • Ripërdorimi i IV: Në modulet si CBC ose GCM një vektor inicializimi (IV) duhet të jetë unik për çdo enkriptim. Një IV konstant është një rrjedhje, edhe nëse fjalëkalimi është i fortë.
  • Çelës nga fjalëkalimi pa KDF: Përdorimi direkt i një fjalëkalimi si çelës (ose hashed një herë) lejon sulme offline. Një KDF (Key Derivation Function) si PBKDF2 ngadalëson sulmuesit në mënyrë të synuar.
  • Mungesa e versionit të formatit: Pa header/version, do të jetë e vështirë të ndryshoni më vonë numrin e iteracioneve, algoritmin ose parametrat pa lënë të dhëna të vjetra të „braktisura”.
  • Mungesa e integritetit: AES-CBC enkripton, por nuk parandalon manipulimin. Pa autentikim (p.sh. HMAC ose AEAD si GCM) përballeni me bitflipping/padding-probleme dhe imazhe gabimesh të vështira për t’u diagnostikuar.

Bërthama e këtij teksti: një format kontejner i vogël që mbështet streaming, mund të versionohet dhe shmang gabimet standarde.

AES Verschlüsselung Delphi me Header, Salt, IV dhe PBKDF2

Ne përcaktojmë një format të thjeshtë kontejneri që mund të përdoret edhe në BLOB-e të bazës së të dhënave ose payload-e mesazhesh:

  • Magic: 4 bytes, p.sh. NBAE (kontroll i shpejtë „A është ky formati ynë?”)
  • Version: 1 byte (lejon migrim)
  • Parametrat KDF: numri i iteracioneve (4 bytes)
  • Salt: 16 bytes (rastësor për çdo skedar)
  • IV: 16 bytes (rastësor për çdo skedar për AES-CBC)
  • Ciphertext: të dhënat e përdorimit të enkriptuara (të përshtatshme për streaming)

E rëndësishme: Salt dhe IV nuk janë sekrete. Ato duhet të jenë thjesht të reja për secilin enkriptim. Fjalëkalimi mbetet sekret; çelësi i nxjerrë prej tij nuk ruhet.

AES Verschlüsselung Delphi në stream: Shkrim/lexim i kontejnerit

Kodi është shkruar me vetëdije si „plan ndërtimi”: funksione të ndara qartë, header-e të verifikueshme, pa variabla globale të fshehura. Për AES dhe PBKDF2 shumë ekipe përdorin një bibliotekë kriptografike të provuar (p.sh. DEC). Copëza tregon formatin dhe modelin e streaming; thirrjet AES-/PBKDF2 janë të kapsuluara në mënyrë që t’i zëvendësoni sipas bibliotekës që përdorni.

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;

// --- Varësitë që duhet të implementoni sipas Crypto-Stack ---

procedure FillRandomBytes(var B: TBytes);
begin
  // Për numra të rastësishëm kriptografikë: përdorni OS-CSPRNG (Windows BCryptGenRandom,
  // Linux getrandom/urandom). Këtu qëllimisht është një vendmbushës.
  raise ENbCryptoError.Create('FillRandomBytes: CSPRNG nuk është i integruar');
end;

function PBKDF2_HMAC_SHA256(const APassword: string; const ASalt: TBytes;
  const AIterations, AKeyLen: Cardinal): TBytes;
begin
  // Implementim, p.sh. me DEC (PBKDF2) ose bibliotekë tjetër.
  // Rezultati: AKeyLen bajtë.
  raise ENbCryptoError.Create('PBKDF2_HMAC_SHA256: nuk është i integruar');
end;

procedure AES256_CBC_EncryptStream(const AKey, AIV: TBytes; const AIn, AOut: TStream);
begin
  // Implementim me bibliotekë:
  // - KeyLen = 32 bajtë
  // - IVLen  = 16 bajtë
  // - PKCS#7 Padding
  // E rëndësishme: përpunoni stream-in, mos i ngarkoni të gjitha në memorje.
  raise ENbCryptoError.Create('AES256_CBC_EncryptStream: nuk është i integruar');
end;

procedure AES256_CBC_DecryptStream(const AKey, AIV: TBytes; const AIn, AOut: TStream);
begin
  raise ENbCryptoError.Create('AES256_CBC_DecryptStream: nuk është i integruar');
end;

// --- Ndihmës ---

procedure WriteHeaderV1(const AOut: TStream; const H: TNbHeaderV1);
begin
  if AOut.Write(H, SizeOf(H)) <> SizeOf(H) then
    raise ENbCryptoError.Create('Header nuk mund të shkruhej');
end;

function ReadHeaderV1(const AIn: TStream): TNbHeaderV1;
var
  H: TNbHeaderV1;
begin
  if AIn.Read(H, SizeOf(H)) <> SizeOf(H) then
    raise ENbCryptoError.Create('Header i paplotë');

  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('Jo një container i vlefshëm (Magic nuk përputhet)');

  if H.Version <> CVersion then
    raise ENbCryptoError.CreateFmt('Version i panjohur i container-it: %d', [H.Version]);

  if (H.Iterations < 10000) or (H.Iterations > 5000000) then
    raise ENbCryptoError.Create('Numri i iteracioneve jashtë kufijve të arsyeshëm');

  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('Fjalëkalimi nuk mund të jetë bosh');

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

  // Mbushni header-in
  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);

  // Derivoni çelësin (32 bajtë për AES-256)
  Key := PBKDF2_HMAC_SHA256(APassword, Salt, AIterations, 32);

  // Shifroni të dhënat e përdorimit (Ciphertext ndjek direkt pas header-it)
  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('Fjalëkalimi nuk mund të jetë bosh');

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

  // Deshifroni nga pozicioni aktual i stream-it (pas header-it)
  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.

Qëllimi: Një kontejner minimal që përshtatet për skedarë dhe BLOB-e, përfshirë versionim dhe parametra KDF. Kushtet paraprake: Duhet të vendosni një lidhje të vërtetë CSPRNG (rastësi kriptografike e sigurt nga sistemi operativ) dhe një implementim të fortë AES/PBKDF2 nën të. Rreziqet: Mos përdorni „çfarëdo“ Random (jo Random()), mos përdorni IV të fiksuar, dhe planifikoni trajtimin e qartë të gabimeve gjatë decrypt (fjalëkalim i gabuar vs. të dhëna të dëmtuara). Varianta: në vend të CBC preferoni AEAD (shih më poshtë), ose zgjerojeni header-in me ID të algoritmit dhe HMAC.

Integriteti: pse AES-CBC vetëm është tepër i rrezikshëm në operim

AES-CBC është ende i pranishëm në shumë kontekste legacy dhe mund të funksionojë, nëse përdorni shtesë një mekanizëm sigurie të integritetit. Pa integritet, një sulmues mund të manipulojë ciphertext-in; edhe pa një sulmues aktiv, gabimet e transmetimit ose shtresat e dëmtuara të magazinimit krijojnë gabime „padding“ që janë të vështira për t’u diagnostikuar.

Opsione pragmatike:

  • Encrypt-then-HMAC: Pas ciphertext-it shkruani një HMAC (p.sh. HMAC-SHA-256) mbi Header+Ciphertext. Kur lexoni, kontrolloni fillimisht HMAC, pastaj dekriptoni. Për këtë qëllim, idealisht nxirrni dy çelësa nga PBKDF2 (p.sh. 64 Bytes: 32 për AES, 32 për HMAC), në vend që të përdorni të njëjtin çelës dy herë.
  • AES-GCM: Modalitet AEAD (Authenticated Encryption with Associated Data). Ofron Ciphertext + Auth-Tag. Kjo shpesh është zgjedhja më e qartë sot, nëse biblioteka juaj Delphi e mbështet GCM stabilisht. Fushat e header-it mund të autentikohen si „AAD“ pa i enkriptuar ato.

Nëse duhet të qëndroni me CBC (p.sh. për shkak të interoperabilitetit), Encrypt-then-HMAC është plotësimi i qëndrueshëm. Për formatet e reja ia vlen GCM, sepse me të merrni autentifikim dhe modelet e gabimeve bëhen më të qarta.

Jashtëzakonisht i rëndësishëm: „Rastësia kriptografike“ dhe pse System.Hash nuk mjafton

Një refleks i zakonshëm legacy në projektet Delphi: „Ne thjesht marrim SHA256 mbi timestamp + diçka dhe kemi Random.“ Kjo nuk është një bazë e besueshme. Për Salt dhe IV ju duhet një CSPRNG (Generator Pseudo-Rastesior Kriptografik i Sigurt) nga sistemi operativ. Nën Windows kjo zakonisht është BCrypt-API (CNG), nën Linux një generator i kernelit si getrandom() ose /dev/urandom. Diferenca është praktike: një CSPRNG është projektuar që nga vëzhgimet e vlerave të mos jetë e mundur të parashikohet vlerat e ardhshme.

Truku arkitekturor: Bëni kapsulim të kësaj në një të vogël „RandomProvider“-Unit, që mund ta mock-oni në testet tuaja. Kështu zgjidhni menjëherë dy raste të kufizuara: teste të riprodhueshme (me seed fikse në mock) dhe siguri reale në prodhim (me OS-CSPRNG). Kështu parandaloni që në një hotfix të futet sërish Random() vetëm për shkak të shpejtësisë.

Debugging dhe Migrim Legacy: Versionimi nuk është luks

Header-i nuk është vetëm për „Krypto-Schönheit“, por për mirëmbajtje:

  • Përshtatja e iteracioneve: Numrat e iteracioneve të PBKDF2 ndryshojnë me vitet. Me një fushë header mund t’i rrisni më vonë pa e bërë të pamundur leximin e të dhënave të vjetra.
  • Ndërrim formati: P.sh., Versioni 2 mund të kalojë në AES-GCM ose të shtojë një HMAC.
  • Diagnozë në terren: Magic/Version lejojnë kontrolle të shpejta në log-e dhe vegla, pa pasur nevojë të dekriptoni të dhënat.

Këshillë praktike: Implementoni një „Inspector“ të vogël që lexon vetëm header-in (Magic/Version/Iterations) dhe e shkruan në një log. Me këtë zgjidhni shumë raste supporti („Cila version është këtu?“) pa menaxhimin e fjalëkalimeve.

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

Nëse po zëvendësoni një format të vjetër (p.sh. IV fiks, pa KDF, Blowfish/3DES, ose XOR i ndërtuar vetë), në projektet Delphi ka funksionuar një model: gjatë leximit duhet të njihni disa formate (Magic/Version ose heuristikë fallback), gjatë shkrimit gjeneroni vetëm formatin e ri. Shtesë, pas deshifrimit të suksesshëm mund të bëni re-encrypt në sfond („lazy migration“) nëse përshtatet me procesin. Kështu reduktoni rrezikun gjatë rollout dhe shmangni nevojën për „të rifreskuar gjithçka njëherësh“ si dritare mirëmbajtjeje.

Threading und Streaming: typische Kanten in Delphi

Kriptimi shpesh ekzekutohet në worker-thread-e (p.sh. gjatë eksportit, gjatë upload-it në një portalin e klientit, gjatë shkruajtjes së arkivave të mëdha). Dy pika që shfaqen rregullisht në projektet Delphi:

  • Stream-Positionen: Para enkriptimit/deshifrimit vendosni kontrakta të qarta: Input-Stream lexon nga pozicioni aktual, Output-Stream shkruan nga pozicioni aktual. Në rast të ripërdorimit të stream-eve, vendosni me vetëdije Position := 0.
  • Memory-Spitzen: Shmangni „të gjitha në TBytes“. Qasja me stream është thelbësore për skedarë të mëdhenj. Nëse biblioteka juaj crypto pranon vetëm byte-array, ia vlen puna shtesë për të kaluar në një implementim që mbështet stream-e ose për të ndërtuar një adaptor të buffer-uar.

Nëse kryeni kriptim brenda services (Windows- ose Linux-Services), kushtoni vëmendje edhe exception-logging të pastër: „fjalëkalim i gabuar“, „header i dëmtuar“, „Tag/HMAC i pavlefshëm“ janë raste operative të ndryshme dhe duhet të dallohen. E rëndësishme: mesazhet e gabimit nuk duhet të jenë tejet të detajuara për konsumatorët e jashtëm (p.sh. jo „Padding i gabuar në bllokun 7“ si gabim API), por brenda log-ut mund të jenë të hollësishme.

Wann sich der Ansatz lohnt – und wo er kippen kann

Ia vlen, nëse ju: (a) ruani në mënyrë të qëndrueshme të dhëna eksport/import të enkriptuara, (b) operoni paralelisht versione të ndryshme të programit, (c) përpunoni të dhëna si stream-e, ose (d) keni nevojë për një ndërfaqe kriptografike të pastër për module të ndryshme (Client/Server/Tooling).

Dështon, nëse përpiqeni të zgjidhni me të „gjithçka“: për Transport përgjegjës është TLS, jo një wrapper AES i ndërtuar vetë. Për Secrets (fjalëkalime, token) shpesh është më e përshtatshme një OS-Secret-Store ose një Vault. Dhe nëse keni nevojë për interoperabilitet me gjuhë të tjera, dokumentoni saktë header-in, endianness dhe encoding-un (ose përdorni një format të konsoliduar).

Fazit: AES in Delphi ist weniger Algorithmus, mehr Engineering

Fitimi i vërtetë i këtij fragmenti nuk është thjesht „AES punon“, por një format i përdorshëm në operacion: Salt dhe IV të rastësishëm, header i versionuar, parametra PBKDF2 në payload dhe përpunim i përshtatshëm për stream-e. Shtoni për formatet e reja integritetin sa më shumë të jetë e mundur (AES-GCM ose Encrypt-then-HMAC). Kështu, nga „ne enkriptojmë diçka“ bëhet një komponent që në zgjidhjet digjitale të ndërmarrjes mbetet i mirëmbajtshëm dhe migrues edhe pas viteve.

Nëse duhet të integroni një container të tillë në një infrastrukturë ekzistuese Delphi-mjedis ose të migroni në mënyrë të pastër nga një format të trashëguar, ia vlen një kontroll i shkurtër arkitekturor (menaxhimi i çelësave, versionet e formatit, operimi/regjistrimi). Detajet i sqarojmë me kënaqësi sipas nevojës në një bisedë:

Në kontekstin profesional, edhe Delphi Aes dhe Pbkdf2 Delphi luajnë një rol të rëndësishëm kur integrimet, rrjedhat e të dhënave dhe zhvillimi i mëtejshëm duhet të bashkëveprojnë në mënyrë të pastër.

Diskutoni projektin ose nismën e modernizimit me Net-Base.

Hapi tjetër

Kur nga një temë bëhet një projekt real, arkitektura, sistemi ekzistues dhe operimi duhet të merren në konsideratë së bashku që në fazat e hershme.

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

  • Gjendja ekzistuese, imazhi i synuar dhe rreziqet teknike vlerësohen së bashku.
  • REST, akses në të dhëna, portalet dhe Rollout nuk shtyhen si pasoja të mëvonshme.
  • Ju e shihni herët se cila rrugë është e qëndrueshme ekonomikisht dhe operativisht.

Ndaje postimin

Shpërndaj këtë postim drejtpërdrejt

LinkedIn, X, XING, Facebook, WhatsApp dhe E‑Mail janë menjëherë të disponueshme. Për Instagram po përgatitim menjëherë lidhjen dhe tekstin e shkurtër.

Postë elektronike

Instagram hapet në një skedë të re. Linku dhe teksti i shkurtër kopjohen më parë në memorjen e kopjimit.