Net-Base Magazín

30.05.2026

AES-šifrovanie v Delphi: robustný úryvok zdrojového kódu s IV, Salt, hlavičkou a streamovaním

Prakticky použiteľný Delphi úryvok zdrojového kódu pre AES-šifrovanie s náhodnou soľou a IV, jasnou štruktúrou hlavičky súboru, odvodením kľúča pomocou PBKDF2 a streamovaním – vrátane typických úskalí pri legacy formátoch, integrite a prevádzke.

30.05.2026

Od témy magazínu k projektovej praxi

Súvisiace stránky služieb a technológií k príspevku

Pri AES šifrovaní Delphi v praxi zlyháva problém zriedka v „AES samo o sebe“, skôr v okrajových podmienkach: dáta sa musia spracovávať ako stream (súbory, BLOBy, zálohy), staré formáty musia zostať čitateľné a v prevádzke potrebujete debuggovateľnosť (header, verzovanie) a bezpečné predvolené nastavenia (Salt/IV náhodné, bez opätovného použitia). Tento ukážkový zdrojový kód preto neukazuje len „Encrypt/Decrypt“, ale malé, robustné formátové riešenie s headerom, verziou, Salt a IV – plus PBKDF2 pre odvodenie kľúča a miesto, kde má zmysel doplniť integritu.

Prečo „AES-String zašifrovať“ takmer nikdy nestačí

V individuálnom podnikateľskom softvéri sa šifrovanie zvyčajne objavuje na troch miestach: (1) konfigurácia/secrets (napr. prihlasovacie údaje), (2) výmenné/exportné súbory a (3) neaktívne dáta (napr. archívy, kontajnery dokumentov). Naívny prístup „heslo → AES-kľúč → vložiť/vybrať string“ rýchlo padá:

  • Opätovné použitie IV: Pri režimoch ako CBC alebo GCM musí byť inicializačný vektor (IV) pre každé šifrovanie jedinečný. Konstantné IV je únik, aj keď je heslo silné.
  • Kľúč z hesla bez KDF: Priame použitie hesla ako kľúča (alebo jednorazový hash) umožňuje offline útoky. KDF (Key Derivation Function) ako PBKDF2 útočníkov efektívne spomaľuje.
  • Žiadna verzia formátu: Bez headeru/verzie je neskôr ťažké zmeniť počet iterácií, algoritmus alebo parametre bez toho, aby staré dáta „neostali“ nepoužiteľné.
  • Žiadna integrita: AES-CBC šifruje, ale nebráni manipulácii. Bez autentifikácie (napr. HMAC alebo AEAD ako GCM) dostanete bitflipping-/padding-problémy a ťažko diagnostikovateľné chyby.

Jadro tohto príspevku: malý kontajnerový formát, ktorý podporuje streamovanie, je verzovateľný a vyhnúť sa bežným chybám.

AES šifrovanie Delphi s headerom, Salt, IV a PBKDF2

Definujeme jednoduchý kontajnerový formát, ktorý sa dá využiť aj v databázových BLOBoch alebo v payloadoch správ:

  • Magic: 4 bajty, napr. NBAE (rýchla kontrola „Je to náš formát?“)
  • Version: 1 bajt (umožňuje migráciu)
  • KDF-parameter: počet iterácií (4 bajty)
  • Salt: 16 bajtov (náhodné pre každý súbor)
  • IV: 16 bajtov (náhodné pre každý súbor pre AES-CBC)
  • Ciphertext: zašifrované užitočné dáta (podpora streamovania)

Dôležité: Salt a IV nie sú tajné. Musia byť len pre každé šifrovanie nové. Heslo zostáva tajné; z neho odvodený kľúč sa neukladá.

AES šifrovanie Delphi v streame: zápis/čítanie kontajnera

Kód je zámerne napísaný ako „plán stavby“: jasne oddelené funkcie, overiteľné headery, žiadne skryté globály. Pre AES a PBKDF2 mnohé tímy používajú osvedčenú kryptografickú knižnicu (napr. DEC). Ukážka zobrazuje formát a pattern pre streamovanie; volania AES/PBKDF2 sú zapuzdrené tak, aby ste ich vedeli podľa knižnice vymeniť.

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;

// — Závislosti, ktoré treba implementovať podľa použitého Crypto-Stacku —

procedure FillRandomBytes(var B: TBytes);
begin
// Pre kryptografický náhodný zdroj: použiť OS-CSPRNG (Windows BCryptGenRandom,
// Linux getrandom/urandom). Tu zámerne ako zástupca.
raise ENbCryptoError.Create(‚FillRandomBytes: CSPRNG nie je pripojený‘);
end;

function PBKDF2_HMAC_SHA256(const APassword: string; const ASalt: TBytes;
const AIterations, AKeyLen: Cardinal): TBytes;
begin
// Implementácia napr. s DEC (PBKDF2) alebo inou knižnicou.
// Výsledok: AKeyLen bajtov.
raise ENbCryptoError.Create(‚PBKDF2_HMAC_SHA256: nie je prepojené‘);
end;

procedure AES256_CBC_EncryptStream(const AKey, AIV: TBytes; const AIn, AOut: TStream);
begin
// Implementácia cez knižnicu:
// – KeyLen = 32 Bytes
// – IVLen = 16 Bytes
// – PKCS#7 Padding
// Dôležité: spracovať orientovane do streamu, nie všetko v pamäti.
raise ENbCryptoError.Create(‚AES256_CBC_EncryptStream: nie je prepojené‘);
end;

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

// — Pomocné —

procedure WriteHeaderV1(const AOut: TStream; const H: TNbHeaderV1);
begin
if AOut.Write(H, SizeOf(H)) <> SizeOf(H) then
raise ENbCryptoError.Create(‚Hlavičku sa nepodarilo zapísať‘);
end;

function ReadHeaderV1(const AIn: TStream): TNbHeaderV1;
var
H: TNbHeaderV1;
begin
if AIn.Read(H, SizeOf(H)) <> SizeOf(H) then
raise ENbCryptoError.Create(‚Hlavička je neúplná‘);

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(‚Neplatný kontajner (Magic nesúhlasí)‘);

if H.Version <> CVersion then
raise ENbCryptoError.CreateFmt(‚Neznáma verzia kontajnera: %d‘, [H.Version]);

if (H.Iterations < 10000) or (H.Iterations > 5000000) then
raise ENbCryptoError.Create(‚Počet iterácií mimo rozumných hraníc‘);

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(‚Heslo nesmie byť prázdne‘);

// Salt/IV vygenerovať
SetLength(Salt, CSaltLen);
SetLength(IV, CIvLen);
FillRandomBytes(Salt);
FillRandomBytes(IV);

// Vyplniť hlavičku
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);

// Odvodiť kľúč (32 bajtov pre AES-256)
Key := PBKDF2_HMAC_SHA256(APassword, Salt, AIterations, 32);

// Šifrovať užitočné dáta (ciphertext nasleduje priamo za hlavičkou)
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(‚Heslo nesmie byť prázdne‘);

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

// Dešifrovať od aktuálnej pozície streamu (po hlavičke)
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.

Účel: Minimálny kontajner vhodný pre súbory a BLOBy, vrátane verzovania a parametrov KDF. Podmienky: Musíte pod to nasadiť skutočné pripojenie na CSPRNG (kryptograficky bezpečný náhodný zdroj z operačného systému) a robustnú implementáciu AES/PBKDF2. Nástrahy: Nepoužívajte „nejaký“ Random (nie Random()), žiadne pevné IV a pri dešifrovaní plánujte jednoznačné spracovanie chýb (nesprávne heslo vs. poškodené dáta). Varianty: namiesto CBC radšej AEAD (viď nižšie), alebo rozšíriť header o ID algoritmu a HMAC.

Integrita: prečo je AES-CBC sám o sebe v prevádzke príliš rizikový

AES-CBC je v mnohých legacy kontextoch stále prítomné a môže fungovať, ak použijete dodatočne nejakú integritnú kontrolu. Bez integrity môže útočník manipulovať s ciphertextom; aj bez aktívneho útočníka spôsobia chyby prenosu alebo poškodené storage vrstvy ťažko diagnostikovateľné „padding“ chyby.

Pragmatické možnosti:

  • Encrypt-then-HMAC: Za ciphertext zapíšte HMAC (napr. HMAC-SHA-256) cez header+ciphertext. Pri čítaní najprv overíte HMAC, potom dešifrujete. Na to ideálne odvádzajte dva kľúče z PBKDF2 (napr. 64 B: 32 pre AES, 32 pre HMAC), namiesto použitia toho istého kľúča dvakrát.
  • AES-GCM: AEAD režim (Authenticated Encryption with Associated Data). Dodá ciphertext + auth-tag. Dnes je to často najčistejšia voľba, ak vaša Delphi-knižnica GCM stabilne podporuje. Polia headeru môžete autentifikovať ako „AAD“ bez toho, aby ste ich museli šifrovať.

Ak musíte zostať pri CBC (napr. kvôli interoperabilite), je Encrypt-then-HMAC robustným doplnkom. Pre nové formáty sa oplatí GCM, pretože s ním máte autentifikáciu „zadarmo“ a chybové stavy sú zrozumiteľnejšie.

Neobyčajne dôležité: „Kryptografický náhodný zdroj“ a prečo System.Hash nestačí

Bežný legacy reflex v Delphi projektoch: „Vezmeme SHA256 nad časovým pečiatkom + niečím a máme random.“ To nie je spoľahlivá báza. Pre salt a IV potrebujete CSPRNG (Cryptographically Secure Pseudo Random Number Generator) operačného systému. Pod Windows je to typicky BCrypt-API (CNG), pod Linux kernelový generátor ako getrandom() resp. /dev/urandom. Rozdiel je praktický: CSPRNG je navrhnutý tak, aby z pozorovaných hodnôt nebolo možné predpovedať ďalšie hodnoty.

Architektonický trik: Zapuzdrite to v malej „RandomProvider“-unit, ktorú v testoch dokážete mockovať. Tým vyriešite hneď dva okrajové prípady: reprodukovateľné testy (s fixným seedom v mocku) a skutočnú bezpečnosť v produkcii (s OS-CSPRNG). Tak zabránite, aby sa pri hotfixe „rýchlo“ znovu neobjavil Random(), len preto že je to jednoduché.

Debugging a migrácia legacy: verziovanie nie je luxus

Header nie je len „krypto-krása“, ale otázka udržiavateľnosti:

  • Ladenie parametrov: Iteračné čísla PBKDF2 sa v priebehu rokov menia. S políčkom v headeri ich neskôr môžete zvýšiť bez toho, aby ste staré dáta urobili nečitateľnými.
  • Zmena formátu: Verzia 2 môže napr. prejsť na AES-GCM alebo doplniť HMAC.
  • Diagnostika v teréne: Magic/Version umožňujú rýchle kontroly v logoch a nástrojoch bez nutnosti dešifrovať dáta.

Praktický tip: Implementujte malého „inspektora“, ktorý len prečíta hlavičku (Magic/Version/Iterations) a zapíše ju do logu. Takto vyriešite mnoho podporných prípadov („Ktorá verzia je tu?“) bez spracovania hesiel.

Čistá migrácia: „Read old, write new“ namiesto Big Bang

Keď nahrádzate starý formát (napr. pevné IV, žiadne KDF, Blowfish/3DES alebo vlastnoručne vyrobené XOR), v projektoch Delphi sa osvedčil nasledovný vzor: pri čítaní rozpoznajte viacero formátov (Magic/Version alebo fallback-heuristika), pri zápise generujte už len nový formát. Navyše môžete pri úspešnom dešifrovaní na pozadí prešifrovať („lazy migration“), ak to vyhovuje procesu. Tým znížite riziko rollout-u a vyhnete sa „všetko raz prešifrovať“ ako oknu údržby.

Threading und Streaming: typické problémové miesta v Delphi

Šifrovanie často beží v worker-threadoch (napr. pri exporte, pri nahrávaní do zákazníckeho portálu, pri zápise veľkých archívov). Dve veci, ktoré sa v projektoch Delphi pravidelne objavujú:

  • Pozície streamu: Pred šifrovaním/dešifrovaním majte jasné kontrakty: vstupný stream sa číta od aktuálnej pozície, výstupný stream sa zapisuje od aktuálnej pozície. Pri opätovnom použití streamov vždy vedome nastaviť Position := 0.
  • Pamäťové špičky: Vyhnite sa „všetkému do TBytes“. Prístup so streamami je obzvlášť dôležitý pri veľkých súboroch. Ak vaša kryptobibliotéka akceptuje len byte-arrays, oplatí sa dodatočná práca na prechode na streamovú implementáciu alebo postavení vyrovnávacieho adaptéra.

Ak šifrujete v službách (Windows- alebo Linux-Services), dbajte aj na čisté logovanie výnimiek: „nesprávne heslo“, „poškodená hlavička“, „neplatný Tag/HMAC“ sú rozdielne prevádzkové prípady a mali by byť rozlíšiteľné. Dôležité: chybové hlásenia nesmú smerom von byť príliš detailné (nie „padding chybný v bloku 7“ ako API-chyba), v internom logu však áno.

Kedy má prístup zmysel — a kde môže zlyhať

Má zmysel, ak: (a) trvalo ukladáte zašifrované exportné/importné dáta, (b) prevádzkujete paralelne rôzne verzie programu, (c) spracovávate dáta ako streamy alebo (d) potrebujete čisté krypto-rozhranie pre viac modulov (Client/Server/Tooling).

Zlyhá, ak sa pokúšate riešiť s tým „všetko“: za transport je zodpovedný TLS, nie vlastnoručne postavený AES-wrapper. Pre secrets (heslá, tokeny) je často vhodnejší OS-Secret-Store alebo Vault. A ak potrebujete interoperabilitu s inými jazykmi, musíte hlavičku, endianness a encoding presne zdokumentovať (alebo použiť etablovaný formát).

Záver: AES v Delphi je menej o algoritme, viac o inžinierstve

Skutočný prínos tohto úryvku nie je „AES beží“, ale prevádzkovateľný formát: náhodný salt a IV, verzovaný header, PBKDF2-parametre v payload a streamová spracovateľnosť. Doplňte pre nové formáty integritu (AES-GCM alebo Encrypt-then-HMAC). Tak sa z „nejako niečo zašifrujeme“ stane komponent, ktorý bude v digitálnych podnikových riešeniach aj po rokoch udržiavateľný a migrovateľný.

Ak potrebujete taký kontajner integrovať do existujúceho Delphi prostredia alebo ho spoľahlivo migrovať z legacy formátu, oplatí sa krátka kontrola architektúry (správa kľúčov, verzie formátu, prevádzka/logovanie). Detaily radi upresníme na požiadanie v rozhovore:

V odbornom kontexte zohrávajú tiež Delphi Aes a Pbkdf2 Delphi dôležitú úlohu, keď musia integrácie, dátové toky a ďalší vývoj fungovať bezchybne v súčinnosti.

Prediskutovať projekt alebo zámer modernizácie s Net-Base.

Ďalší krok

Keď sa téma stane reálnym projektom, architektúru, existujúci stav a prevádzku treba včas posudzovať spoločne.

Podporujeme nielen pri jednotlivých otázkach, ale aj vtedy, keď sa z fragmentov zdrojového kódu, tém súvisiacich s legacy systémami alebo nápadov na portál má stať robustný podnikový projekt.

  • Stav, cieľový obraz a technické riziká sa hodnotia spoločne.
  • REST, prístup k dátam, portály a Rollout nebudú odložené na neskôr.
  • Včas zistíte, ktorá cesta je ekonomicky a prevádzkovo životaschopná.

Zdieľať príspevok

Tento príspevok priamo zdieľať

LinkedIn, X, XING, Facebook, WhatsApp a e-mail sú ihneď k dispozícii. Pre Instagram pripravujeme priamo odkaz a krátky text.

E-mail

Instagram sa otvorí v novej karte. Odkaz a krátky text sa predtým skopírujú do schránky.