Net-Base Žurnāls

30.05.2026

AES-šifrēšana Delphi: robusts avota koda fragments ar IV, Salt, Header un Streaming

Praktiski izmantojams Delphi avota koda fragments AES šifrēšanai ar nejaušu saltu un IV, skaidru faila galvenes struktūru, PBKDF2 atslēgas atvasināšanu un straumēšanu – ieskaitot tipiskās paklupšanas vietas saistībā ar novecojušiem formātiem, integritāti un ekspluatāciju.

30.05.2026

No žurnāla tēmas līdz projektu praksei

Atbilstošas pakalpojumu un tehniskās lapas rakstam

Praksē ar AES šifrēšanu Delphi reti vienīgi rodas problēmas ar „AES kā tādu“; parasti problēmas ir saistītas ar malas nosacījumiem: dati jāapstrādā kā plūsma (faili, BLOBi, dublējumi), vecajiem formātiem jāpaliek lasāmiem, un ekspluatācijā nepieciešama debug iespēja (galvene, versiju vadība) un droši noklusējumi (Salt/IV nejauši, bez atkārtotas izmantošanas). Šis avota koda fragments rāda ne tikai „Encrypt/Decrypt“, bet nelielu, robustu formātu ar galveni, versiju, Salt un IV — plus PBKDF2 atslēgas atvasināšanai un vietu, kur saprātīgi papildināt integritātes pārbaudi.

Kāpēc „AES virknes šifrēšana“ gandrīz nekad nepietiek

Individuālajā uzņēmuma programmatūrā šifrēšana parasti parādās trīs vietās: (1) konfigurācija/sekreti (piem., piekļuves dati), (2) apmaiņas/eksporta faili un (3) stāvošie dati (piem., arhīvi, dokumentu konteineri). Naivs piegājiens „parole → AES atslēga → virkne iekšā/ārā“ ātri sabrūk:

  • IV atkārtota izmantošana: režīmos kā CBC vai GCM inicializācijas vektoram (IV) jābūt unikālam katrai šifrēšanas operācijai. Fiksēts IV ir informācijas noplūde, pat ja parole ir spēcīga.
  • Atslēga no paroles bez KDF: paroles tieša izmantošana kā atslēga (vai tikai vienreizēja hash) atstāj iespēju offline uzbrukumiem. KDF (Key Derivation Function), piemēram PBKDF2, mērķtiecīgi palēnina uzbrucējus.
  • Nav formāta versijas: bez galvenes/versijas vēlāk būs grūti mainīt iterāciju skaitu, algoritmu vai parametrus, nezaudējot piekļuvi veciem datiem.
  • Nav integritātes: AES-CBC šifrē datus, bet nepasargā pret manipulācijām. Bez autentifikācijas (piem., HMAC vai AEAD kā GCM) rodas bitu apgriešanas/padding problēmas un grūti diagnosticējami kļūdu stāvokļi.

Šī ieraksta kodols: neliels konteinerformāts, kas atbalsta straumēšanu, ir versionējams un izvairās no tipiskajām kļūdām.

AES šifrēšana Delphi ar galveni, Salt, IV un PBKDF2

Definējam vienkāršu konteinerformātu, ko var izmantot arī datubāzes BLOBos vai ziņojumu payloados:

  • Magic: 4 baiti, piemēram NBAE (ātra pārbaude “vai tas ir mūsu formāts?”)
  • Versija: 1 baits (atvieglo migrācijas)
  • KDF parametri: iterāciju skaits (4 baiti)
  • Salt: 16 baiti (nejaušs katrai datnei)
  • IV: 16 baiti (nejaušs katrai datnei AES-CBC gadījumā)
  • Ciphertext: šifrēti lietotāja dati (piemēroti straumēšanai)

Svarīgi: Salt un IV nav noslēpums. Tie vienkārši jāģenerē jaunāki katrai šifrēšanai. Parole paliek slepena; no tās izrietošā atslēga netiek saglabāta.

AES šifrēšana Delphi plūsmā: konteineru rakstīšana/lasīšana

Kods ir apzināti rakstīts kā „būvplāns“: skaidri nodalītas funkcijas, pārbaudāmas galvenes, nav slēptu globālo mainīgo. AES un PBKDF2 daudzas komandas īsteno ar pārbaudītu kriptogrāfijas bibliotēku (piem., DEC). Fragments demonstrē formātu un straumēšanas modeli; AES/PBKDF2 izsaukumus var nokapsulēt tā, lai tos varētu aizstāt atkarībā no izmantotās bibliotēkas.

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;

// --- Abhängigkeiten, die Sie je nach Crypto-Stack implementieren ---

procedure FillRandomBytes(var B: TBytes);
begin
  // Kriptogrāfiskai nejaušībai: izmantot OS-CSPRNG (Windows BCryptGenRandom,
  // Linux getrandom/urandom). Šeit apzināti kā vietturis.
  raise ENbCryptoError.Create('FillRandomBytes: CSPRNG nav piesaistīts');
end;

function PBKDF2_HMAC_SHA256(const APassword: string; const ASalt: TBytes;
  const AIterations, AKeyLen: Cardinal): TBytes;
begin
  // Implementācija, piem., ar DEC (PBKDF2) vai citu bibliotēku.
  // Rezultāts: AKeyLen baiti.
  raise ENbCryptoError.Create('PBKDF2_HMAC_SHA256: nav piesaistīts');
end;

procedure AES256_CBC_EncryptStream(const AKey, AIV: TBytes; const AIn, AOut: TStream);
begin
  // Implementācija ar bibliotēku:
  // - KeyLen = 32 Bytes
  // - IVLen  = 16 Bytes
  // - PKCS#7 Padding
  // Svarīgi: apstrādāt straumi orientēti, nevis visu ielādēt atmiņā.
  raise ENbCryptoError.Create('AES256_CBC_EncryptStream: nav piesaistīts');
end;

procedure AES256_CBC_DecryptStream(const AKey, AIV: TBytes; const AIn, AOut: TStream);
begin
  raise ENbCryptoError.Create('AES256_CBC_DecryptStream: nav piesaistīts');
end;

// --- Helper ---

procedure WriteHeaderV1(const AOut: TStream; const H: TNbHeaderV1);
begin
  if AOut.Write(H, SizeOf(H)) <> SizeOf(H) then
    raise ENbCryptoError.Create('Neizdevās ierakstīt Headeru');
end;

function ReadHeaderV1(const AIn: TStream): TNbHeaderV1;
var
  H: TNbHeaderV1;
begin
  if AIn.Read(H, SizeOf(H)) <> SizeOf(H) then
    raise ENbCryptoError.Create('Header nav pilnīgs');

  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('Nav derīgs konteiners (Magic nesakrīt)');

  if H.Version <> CVersion then
    raise ENbCryptoError.CreateFmt('Nezināma konteineru versija: %d', [H.Version]);

  if (H.Iterations < 10000) or (H.Iterations > 5000000) then
    raise ENbCryptoError.Create('Iterāciju skaits ārpus pieņemamām robežā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('Parole nedrīkst būt tukša');

  // 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('Parole nedrīkst būt tukša');

  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.

Mērķis: Minimāls konteiners, piemērots failiem un BLOBs, ieskaitot versiju vadību un KDF parametrus. Nosacījumi: Jānodrošina reāla CSPRNG pieslēgums (kriptogrāfiski drošs nejaušais skaitlis no operētājsistēmas) un robusta AES/PBKDF2 implementācija zem tā. Klupšanas akmeņi: Neizmantojiet „jebkādu“ Random (ne Random()), nedrīkst būt fiksēti IV, un dekriptēšanas laikā paredziet skaidru kļūdu apstrādi (nepareiza parole pret bojātiem datiem). Varianti: CBC vietā labāk AEAD (skat. zemāk), vai paplašināt headeru ar algoritma ID un HMAC.

Integritāte: kāpēc AES-CBC vien pats darbībā ir pārāk riskants

AES-CBC joprojām sastopams daudzos mantojuma kontekstos un var darboties, ja jūs papildus izmantojat integritātes nodrošinājumu. Bez integritātes uzbrucējs var manipulēt ar šifrtekstu; pat bez aktīva uzbrucēja pārsūtīšanas kļūdas vai bojātas krātuves slāņi var radīt grūti diagnosticējamas „Padding” kļūdas.

Pragmatiski risinājumi:

  • Encrypt-then-HMAC: pēc šifrteksta ierakstīt HMAC (piem., HMAC-SHA-256) pāri Header+Ciphertext. Lasīšanas laikā vispirms pārbaudīt HMAC, tikai pēc tam atšifrēt. Tam ideāli divas atslēgas atvasināt no PBKDF2 (piem., 64 baiti: 32 priekš AES, 32 priekš HMAC), nevis atkārtoti izmantot vienu un to pašu atslēgu.
  • AES-GCM: AEAD režīms (Authenticated Encryption with Associated Data). Sniedz šifrtekstu + Auth-Tag. Tā mūsdienās bieži ir tīrākā izvēle, ja jūsu Delphi-bibliotēka stabilā veidā atbalsta GCM. Header lauki var tikt autentificēti kā „AAD” bez to šifrēšanas.

Ja ir jāpaliek pie CBC (piem., interoperabilitātes dēļ), Encrypt-then-HMAC ir robusts papildinājums. Jauniem formātiem GCM atmaksājas, jo tas nodrošina autentifikāciju un kļūdu diagnostika kļūst skaidrāka.

Pārsteidzoši svarīgi: „Kryptografischer Zufall“ un kāpēc System.Hash nav pietiekami

Bieži sastopams mantojuma reflekss Delphi projektos: „Mēs vienkārši ņemam SHA256 pār laika zīmogu + kaut ko un mums ir Random.“ Tas nav uzticams pamats. Salt un IV vajag operētājsistēmas CSPRNG (Cryptographically Secure Pseudo Random Number Generator). Zem Windows tas parasti ir BCrypt-API (CNG), zem Linux — kernela ģenerators kā getrandom() vai /dev/urandom. Praktiskais atšķirības punkts: CSPRNG ir projektēts tā, lai no novērotām vērtībām nebūtu iespējams paredzēt nākamās vērtības.

Arhitektūras triks: inkapsulējiet to nelielā „RandomProvider“-vienībā, kuru testos varat mockot. Tas risina divus gadījumus uzreiz: reproducējami testi (ar fiksētu sēklu mockā) un reāla drošība ražošanā (ar OS-CSPRNG). Tā jūs novēršat, ka kādā hotfixā atkal „ienāk“ Random(), jo tas šķiet ātri izdarāms.

Debugēšana un mantojuma migrācija: versiju vadība nav luksuss

Galvene nav tikai „krypto-skaistumam“, bet svarīga uzturēšanas ērtībai:

  • Iteration Tuning: PBKDF2 iterāciju skaits laika gaitā mainās. Ar galvenes lauku vēlāk varat palielināt iterāciju daudzumu, nepadarot vecos datus nelasāmus.
  • Formāta maiņa: Piemēram, versija 2 varētu pāriet uz AES-GCM vai papildināt ar HMAC.
  • Diagnostika laukā: Magic/Version ļauj ātri veikt pārbaudes žurnālos un rīkos, bez datu atšifrēšanas.

Praktisks padoms: ieviesiet nelielu „Inspector“, kas tikai nolasa galveni (Magic/Version/Iterations) un ieraksta to žurnālā. Tādā veidā atrisināsiet daudzus atbalsta gadījumus („Kura versija šeit ir?“) bez paroles apstrādes.

Tīra migrācija: „Read old, write new“ nevis Big Bang

Ja aizvietojat vecu formātu (piem., fiksēts IV, bez KDF, Blowfish/3DES vai pašu izstrādāts XOR), Delphi projektos ir pierādījies šāds paraugs: Beim Lesen atpazīstat vairākus formātus (Magic/Version vai fallback-heuristika), beim Schreiben ģenerējat tikai jauno formātu. Papildus, sekmīgas atšifrēšanas gadījumā fonā var veikt atkārtotu šifrēšanu („lazy migration“), ja tas atbilst procesam. Tādējādi samazināsiet izvēršanās risku un izvairīsieties no „reiz visa pārsifrēšana“ kā uzturēšanas loga.

Threading un Streaming: tipiskas klupšanas vietas Delphi

Šifrēšana bieži notiek worker-threados (piem., eksportā, augšupielādē uz klientu portālu, rakstot lielus arhīvus). Divi punkti, kas Delphi projektos regulāri izceļas:

  • Stream-pozīcijas: pirms šifrēšanas/atšifrēšanas skaidri definējiet noteikumus: Input-Stream tiek lasīts no pašreizējās pozīcijas, Output-Stream tiek rakstīts no pašreizējās pozīcijas. Ja plūsmas tiek izmantotas atkārtoti, obligāti apzināti iestatiet Position := 0.
  • Atmiņas pīķi: izvairieties no „viss TB apjomos“. Stream-pieeja ir īpaši svarīga lieliem failiem. Ja jūsu kripto bibliotēka pieņem tikai byte-array, ir vērts ieguldīt papildus darbu, pāriet uz stream-spējīgu implementāciju vai uzbūvēt buferizētu adapteri.

Ja šifrējat servisos (Windows- vai Linux-pakalpojumi), pievērsiet uzmanību arī kārtīgai izņēmumu žurnēšanai: „nepareiza parole“, „galvene bojāta“, „Tag/HMAC nederīgs“ ir atšķirīgi darbības gadījumi un tiem jābūt atšķirāmiem. Svarīgi: ārēji kļūdu ziņojumi nedrīkst būt pārāk detalizēti (nedodiet „Padding nepareizs blokā 7“ kā API kļūdu), bet iekšēji žurnālā tas drīkst parādīties.

Kad pieeja atmaksājas – un kur tā var izgāzties

Ir lietderīgi, ja Jūs: (a) ilgtermiņā glabājat šifrētus eksporta/importa datus, (b) vienlaicīgi darbināt vairākas programmas versijas, (c) apstrādājat datus kā streamus vai (d) vajadzīga tīra kripto-saskarne vairākiem moduļiem (Client/Server/Tooling).

Var izgāzties, ja mēģināt ar to atrisināt „visu“: par transportu atbild TLS, nevis paštaisīts AES-Wrapper. Par secrets (paroles, tokeni) biežāk piemērotāks ir OS-Secret-Store vai Vault. Un ja nepieciešama starpvalodu savietojamība, jādokumentē galvene, Endianness un Encoding precīzi (vai jāizmanto etablēts formāts).

Secinājums: AES in Delphi ir mazāk algoritms, vairāk inženierija

Īstais ieguvums no šī piemēra nav „AES darbojas“, bet gan darbotspējīgs formāts: nejaušs salt un IV, versionēta galvene, PBKDF2 parametri payloadā un stream-spējīga apstrāde. Papildiniet jauniem formātiem iespējami ar integritāti (AES-GCM vai Encrypt-then-HMAC). Tādējādi no „mēs kaut ko šifrējam“ rodas komponents, kas digitālajos uzņēmuma risinājumos arī pēc gadiem paliek uzturams un migrējams.

Ja jums jāintegrē šāds konteiners esošā Delphi-ainavā vai jāveic tīra migrācija no mantojuma formāta, ir vērts veikt īsu arhitektūras pārbaudi (atslēgu pārvaldība, formātu versijas, darbība/žurnēšana). Detalizācijas pēc vajadzības labprāt pārrunāsim sarunā:

Tehniskajā kontekstā arī Delphi Aes un Pbkdf2 Delphi spēlē nozīmīgu lomu, kad integrācijām, datu plūsmām un turpmākajai attīstībai jāstrādā kopā precīzi.

Apspriest projektu vai modernizācijas iniciatīvu ar Net-Base.

Nākamais solis

Wenn aus dem Thema ein reales Projekt wird, sollten Architektur, Bestand und Betrieb frueh zusammen betrachtet werden.

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

  • Esošais stāvoklis, mērķa stāvoklis un tehniskie riski tiek kopīgi vērtēti.
  • REST, datu piekļuve, portāli un izvēršana netiek atlikti kā vēlākas sekas.
  • Jūs savlaicīgi redzat, kurš ceļš ir ekonomiski un darbības ziņā dzīvotspējīgs.

Kopīgot ierakstu

Kopīgot šo ierakstu tieši

LinkedIn, X, XING, Facebook, WhatsApp un e-pasts ir uzreiz pieejami. Instagramam saiti un īsu tekstu sagatavosim nekavējoties.

E-pasts

Instagram atveras jaunā cilnē. Saite un īss teksts tiek iepriekš nokopēti starpliktuvē.