Net-Base Magazin

30.05.2026

AES-titkosítás a Delphi-ben: egy robusztus kódrészlet IV-vel, Salt-tal, Headerrel és streameléssel

Gyakorlatias Delphi forráskódrészlet AES-titkosításhoz véletlenszerű salttal és IV-vel, egyértelmű fájlfejléc-struktúrával, PBKDF2 kulcs-deriválással és streaminggel — beleértve a tipikus buktatókat öröklött formátumoknál, az integritás kezelésénél és az üzemeltetés során.

30.05.2026

A magazintémától a projektgyakorlatig

A bejegyzéshez tartozó szolgáltatási és technikai oldalak

Az AES titkosításra Delphi vonatkozó gyakorlati problémák ritkán magából az „AES”-ből adódnak, sokkal inkább a környezeti feltételekből: az adatoknak folyamként kell feldolgozhatónak lenniük (fájlok, BLOB-ok, backupok), régi formátumoknak továbbra is olvashatónak kell maradniuk, és az üzemeltetés során szükség van hibakereshetőségre (header, verziózás) és biztonságos alapbeállításokra (salt/IV véletlenszerű, ne legyen újrafelhasználás). Ez a forrássnippet ezért nem csak az „Encrypt/Decrypt”-et mutatja, hanem egy kis, terhelhető formátumot headerrel, verzióval, salttal és IV-vel – plusz PBKDF2 a kulcskinyeréshez és egy hely, ahol érdemes integritásellenőrzést hozzáadni.

Miért nem elég szinte soha a „String AES-szel való titkosítása”

Egyedi vállalati szoftverekben a titkosítás tipikusan három helyen jelenik meg: (1) konfiguráció/secretek (pl. hozzáférési adatok), (2) cseréhez/exporthoz használt fájlok és (3) nyugvó adatok (pl. archívumok, dokumentumkonténerek). Az egyszerű megközelítés – „jelszó → AES-kulcs → string be/ki” – gyorsan megbicsaklik:

  • IV ismételt használata: CBC vagy GCM módoknál az inicializációs vektor (IV) minden titkosításnál egyedi kell legyen. Egy konstans IV adatvesztéshez vezet, még ha maga a jelszó erős is.
  • Kulcs jelszóból KDF nélkül: A jelszó közvetlen kulcsként való használata (vagy egyszeri hash-elése) offline támadásoknak teszi ki az adatot. Egy KDF (Key Derivation Function), például PBKDF2, célzottan lassítja a támadót.
  • Nincs formátumverzió: Header/verzió nélkül később alig tudja az ember megváltoztatni az iterációszámot, algoritmust vagy paramétereket anélkül, hogy a régi adatok „elhagyatva” maradnának.
  • Nincs integritásvédelem: AES-CBC titkosít, de nem akadályozza meg a manipulációt. Hitelesítés (pl. HMAC vagy AEAD, mint a GCM) hiányában bitflipping-/padding-problémák és nehezen diagnosztizálható hibaképek jelentkeznek.

A bejegyzés lényege: egy kis konténerformátum, amely támogatja a streamelést, verzionálható és elkerüli a tipikus hibákat.

AES titkosítás Delphi headerrel, salttal, IV-vel és PBKDF2-vel

Definiálunk egy egyszerű konténerformátumot, amely adatbázis-BLOB-okban vagy üzenet-payloadokban is használható:

  • Magic: 4 bájt, pl. NBAE (gyors „Ez a mi formátumunk?“ ellenőrzés)
  • Version: 1 bájt (lehetővé teszi a migrációt)
  • KDF-Paraméterek: iterációszám (4 bájt)
  • Salt: 16 bájt (véletlenszerű, fájlonként)
  • IV: 16 bájt (véletlenszerű, fájlonként AES-CBC esetén)
  • Ciphertext: titkosított felhasználói adatok (folyamként feldolgozható)

Fontos: a Salt és az IV nem titkosak. Csak minden egyes titkosításnál kell újnak lenniük. A jelszó marad titok; a belőle származtatott kulcsot nem tároljuk.

AES titkosítás Delphi folyamként: konténer írás/olvasás

A kód szándékosan „tervrajzként” van megírva: jól elkülönített funkciók, ellenőrizhető header, nincsenek rejtett globális változók. AES-hez és PBKDF2-höz sok csapat bevált kriptókönyvtárat használ (pl. DEC). A példa megmutatja a formátumot és a streamelési mintát; az AES-/PBKDF2-hívások úgy kapszulázhatók, hogy tetszőleges könyvtárral kicserélhetőek legyenek.

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;

// — Implementálandó függőségek a használt kripto-stack függvényében —

procedure FillRandomBytes(var B: TBytes);
begin
// Kriptográfiai véletlenszám-generáláshoz használjon OS CSPRNG-t (Windows BCryptGenRandom,
// Linux getrandom/urandom). Itt szándékosan helykitöltőként hagyva.
raise ENbCryptoError.Create(‚FillRandomBytes: CSPRNG nem csatolva‘);
end;

function PBKDF2_HMAC_SHA256(const APassword: string; const ASalt: TBytes;
const AIterations, AKeyLen: Cardinal): TBytes;
begin
// Megvalósítás pl. DEC (PBKDF2) vagy más könyvtár segítségével.
// Eredmény: AKeyLen bájt.
raise ENbCryptoError.Create(‚PBKDF2_HMAC_SHA256: nincs csatolva‘);
end;

procedure AES256_CBC_EncryptStream(const AKey, AIV: TBytes; const AIn, AOut: TStream);
begin
// Megvalósítás könyvtár segítségével:
// – KeyLen = 32 Bytes
// – IVLen = 16 Bytes
// – PKCS#7 Padding
// Fontos: stream-orientált feldolgozás, ne töltsön mindent memóriába.
raise ENbCryptoError.Create(‚AES256_CBC_EncryptStream: nincs csatolva‘);
end;

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

// — Segédfüggvények —

procedure WriteHeaderV1(const AOut: TStream; const H: TNbHeaderV1);
begin
if AOut.Write(H, SizeOf(H)) <> SizeOf(H) then
raise ENbCryptoError.Create(‚Fejléc nem írható‘);
end;

function ReadHeaderV1(const AIn: TStream): TNbHeaderV1;
var
H: TNbHeaderV1;
begin
if AIn.Read(H, SizeOf(H)) <> SizeOf(H) then
raise ENbCryptoError.Create(‚A fejléc hiányos‘);

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(‚Nem érvényes konténer (Magic nem egyezik)‘);

if H.Version <> CVersion then
raise ENbCryptoError.CreateFmt(‚Ismeretlen konténerverzió: %d‘, [H.Version]);

if (H.Iterations < 10000) or (H.Iterations > 5000000) then
raise ENbCryptoError.Create(‚Az iterációk száma kívül esik az ésszerű tartományon‘);

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(‚A jelszó nem lehet üres‘);

// Salt/IV létrehozása
SetLength(Salt, CSaltLen);
SetLength(IV, CIvLen);
FillRandomBytes(Salt);
FillRandomBytes(IV);

// Fejléc kitöltése
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);

// Kulcs leszármaztatása (32 bájt az AES-256 számára)
Key := PBKDF2_HMAC_SHA256(APassword, Salt, AIterations, 32);

// Nyers adatok titkosítása (a Ciphertext közvetlenül a fejléc után következik)
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(‚A jelszó nem lehet üres‘);

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

// Visszafejtés a jelenlegi stream pozíciótól (a fejléc után)
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.

Cél: Egy minimális tároló, amely fájlokhoz és BLOB-okhoz alkalmas, beleértve a verziókezelést és a KDF-paramétereket. Keretfeltételek: Valódi CSPRNG-kapcsolatot kell használni (operációs rendszerből származó kriptográfiailag biztonságos véletlen) és alá egy robusztus AES/PBKDF2-implementációt kell helyezni. Buktatók: Ne használjon „irgendeinen” Random-t (ne Random()), ne alkalmazzon fix IV-ket, és a dekódolásnál tervezzen egyértelmű hibakezelést (rossz jelszó vs. sérült adatok). Változatok: CBC helyett inkább AEAD (lásd lent), vagy bővítse a fejlécet algoritmus-azonosítóval és HMAC-kal.

Integritás: miért kockázatos az AES-CBC önmagában éles környezetben

Az AES-CBC sok örökölt kontextusban még jelen van, és működhet, ha Ön emellett integritásvédelmet használ. Integritás nélkül egy támadó manipulálhatja a ciphertextet; még aktív támadó hiányában is az átvitelhibák vagy hibás tárolórétegek nehezen diagnosztizálható „padding”-hibákat okozhatnak.

Pragmatikus lehetőségek:

  • Encrypt-then-HMAC: A ciphertext után írjon egy HMAC-ot (pl. HMAC-SHA-256) a header+ciphertext fölé. Olvasáskor először a HMAC-ot ellenőrizze, majd dekódolja az adatot. Erre ideálisan két kulcsot nyerjen PBKDF2-ből (pl. 64 bájt: 32 az AES-hez, 32 a HMAC-hoz), ahelyett, hogy ugyanazt a kulcsot kétszer használná.
  • AES-GCM: AEAD-mód (Authenticated Encryption with Associated Data). Ciphertextet és Auth-Tag-et ad. Ma ez gyakran a legtisztább választás, ha az Ön Delphi-könyvtára stabilan támogatja a GCM-et. A fejlécmezők ‚AAD‘-ként hitelesíthetők anélkül, hogy titkosítani kellene őket.

Ha CBC-nél kell maradnia (pl. interoperabilitási okokból), az Encrypt-then-HMAC robusztus kiegészítés. Új formátumoknál megéri a GCM, mert ezzel az autentikáció „jár” és a hibajelenségek egyértelműbbek lesznek.

Rendkívül fontos: „kriptográfiai véletlen” és miért nem elég a System.Hash

Egy gyakori öröklött reflex Delphi-projektekben: „Egyszerűen veszünk egy SHA256-ot időbélyeg + valami más fölött és kész a Random.” Ez nem megbízható alap. Salt és IV esetén az operációs rendszer CSPRNG-jére van szükség (Cryptographically Secure Pseudo Random Number Generator). Windows alatt ez tipikusan a BCrypt-API (CNG), Linux alatt pedig egy kernelgenerátor, mint a getrandom() vagy a /dev/urandom. A különbség gyakorlatilag az, hogy a CSPRNG-t úgy tervezték, hogy megfigyelt értékekből ne legyen lehetséges további értékek előrejelzése.

Architekturtrükk: Kapszulálja ezt egy kis „RandomProvider”-egységbe, amelyet tesztekben mockolni tud. Így két peremesetet old meg egyszerre: reprodukálható tesztek (fix seed a mockban) és valódi biztonság éles környezetben (az OS-CSPRNG-vel). Így megakadályozza, hogy egy hotfix során „mal eben” ismét beüljön a Random(), mert az éppen gyorsabb.

Hibakeresés és örökölt migráció: A verziózás nem luxus

A fejléc nem csak a „kriptográfiai szépség” miatt fontos, hanem a karbantarthatóság érdekében:

  • Iteration Tuning: A PBKDF2-iterációs számok az évek során változnak. Fejlécmezővel később feltekerheti az értéket anélkül, hogy a régi adatok olvashatatlanná válnának.
  • Formatwechsel: A 2-es verzió például áttérhet AES-GCM-re vagy kiegészítheti egy HMAC-kal.
  • Diagnose im Feld: Magic/Version lehetővé teszi gyors ellenőrzéseket naplókban és eszközökben anélkül, hogy az adatokat dekódolni kellene.

Gyakorlati tipp: Implementáljon egy kis „Inspector”-t, amely csak a fejlécet olvassa ki (Magic/Version/Iterations) és naplózza. Így sok támogatási eset tisztázható („Melyik verzió van itt?”) jelszókezelés nélkül.

Tiszta migráció: „Read old, write new“ a Big Bang helyett

Ha egy régi formátumot vált ki (pl. fix IV, nincs KDF, Blowfish/3DES vagy házi XOR), a Delphi-projektekben bevált egy mintázat: olvasáskor több formátumot is felismer (Magic/Version vagy visszaesési heuristika), íráskor pedig már csak az új formátumot hozza létre. Ezen felül a sikeres visszafejtéskor háttérben újra-titkosíthat („lazy migration”), ha ez illeszkedik a folyamathoz. Így csökkenti a roll-out kockázatát és elkerüli, hogy „egyszer mindent újra kell titkosítani” karbantartási ablakként jelenjen meg.

Threading und Streaming: tipikus határhelyzetek a Delphi-ben

A titkosítás gyakran worker-threadekben fut (pl. exportnál, feltöltéskor egy ügyfélportál-ba, nagy archívumok írásakor). Két pont, amelyek a Delphi-projektekben rendszeresen felmerülnek:

  • Stream-Positionen: Titkosítás/decodálás előtt legyenek egyértelmű kontraktusok: a bemeneti streamet az aktuális pozíciótól olvassuk, a kimeneti streamet az aktuális pozíciótól írjuk. Streamek újrafelhasználásakor feltétlenül állítsa tudatosan a Position := 0-t.
  • Memória-csúcsok: Kerülje a „mindent TBytes-ban” megközelítést. A stream-alapú feldolgozás különösen fontos nagy fájloknál. Ha a kripto-könyvtára csak byte-tömböket fogad el, megéri a plusz munka: áttérni egy stream-kompatibilis implementációra vagy építeni egy pufferelt adaptert.

Ha szolgáltatásokban (Windows– vagy Linux-Services) titkosít, ügyeljen továbbá a tiszta kivétel-naplózásra: „hibás jelszó”, „fejléc sérült”, „Tag/HMAC érvénytelen” különböző üzemviteli esetek és meg kell tudni őket különböztetni. Fontos: a hibaüzenetek kifelé ne legyenek túl részletesek (ne legyen API-hibaként például „Padding hibás a 7. blokkban”), de a belső naplóban részletesebb információ megengedett.

Mikor érdemes ez a megközelítés – és hol csúszhat el

Megéri, ha Ön: (a) titkosított export-/importadatokat hosszú távon tárol, (b) különböző programverziókat párhuzamosan üzemeltet, (c) adatokat streamekként dolgoz fel vagy (d) tiszta kripto-API-ra van szüksége több modulhoz (Client/Server/Tooling).

Nem alkalmas, ha mindent ezzel próbál megoldani: az átvitelért a TLS a felelős, nem egy házilag összerakott AES-wrapper. A titkok (jelszavak, tokenek) kezelésére gyakran jobb egy operációs rendszer titoktárolója vagy egy Vault. És ha interoperabilitásra van szükség más nyelvekkel, akkor a fejlécet, az endianness-t és a kódolást pontosan dokumentálni kell (vagy használjon egy bevett formátumot).

Összefoglalás: AES a Delphi-ben kevesebb algoritmus, több mérnöki munka

Az igazi nyereség nem az, hogy „az AES működik”, hanem egy üzemkész formátum: véletlenszerű salt és IV, verzionált fejléc, PBKDF2-paraméterek a payloadban és stream-kompatibilis feldolgozás. Új formátumoknál lehetőleg egészítse ki integritással (AES-GCM vagy Encrypt-then-HMAC). Így a „valamit titkosítunk” helyett olyan építőelemmé válik, amely digitális vállalati megoldásokban évek múlva is karbantartható és migrálható.

Ha egy ilyen konténert egy meglévő Delphi-környezetbe kell integrálnia, vagy egy örökölt formátumból tisztán kell migrálnia, érdemes egy rövid architektúra-ellenőrzést végezni (kulcskezelés, formátumverziók, üzemeltetés/logolás). Részleteket szükség szerint szívesen tisztázunk egy egyeztetés során:

A szakmai környezetben a Delphi Aes és Pbkdf2 Delphi is fontos szerepet játszanak, ha az integrációknak, adatáramlásoknak és a továbbfejlesztésnek zökkenőmentesen kell együttműködniük.

Projekt vagy modernizációs feladat megbeszélése Net-Base-vel.

Következő lépés

Ha egy témából valós projekt lesz, az architektúrát, a meglévő rendszert és az üzemeltetést korai fázisban együtt kell vizsgálni.

Nemcsak egyedi kérdésekben támogatunk, hanem akkor is, amikor forráskódrészletekből, örökölt rendszerekkel kapcsolatos témákból vagy portálötletekből robusztus vállalati projektet kell kialakítani.

  • A jelenlegi állapotot, a célállapotot és a műszaki kockázatokat együttesen értékeljük.
  • REST, az adathozzáférést, a portálokat és a bevezetést nem halasztjuk későbbi fázisokra.
  • Ön korán látja, melyik út gazdaságilag és üzemeltetési szempontból tartható.

Bejegyzés megosztása

Ezt a bejegyzést közvetlenül megosztani

LinkedIn, X, XING, Facebook, WhatsApp és E-Mail azonnal elérhetők. Instagramra a linket és a rövid szöveget közvetlenül előkészítjük.

E-mail

Az Instagram egy új lapon nyílik meg. A link és a rövid szöveg előzetesen a vágólapra másolódik.