Net-Base Magazín

30.05.2026

AES šifrování v Delphi: robustní ukázka zdrojového kódu s IV, solí, hlavičkou a streamováním

Prakticky použitelný Delphi zdrojový útržek pro AES šifrování s náhodným saltem a IV, s jasnou strukturou hlavičky souboru, odvozením klíče PBKDF2 a streamováním – včetně typických úskalí u legacy formátů, integrity a provozu.

30.05.2026

Od tématu magazínu k projektové praxi

Vhodné stránky služeb a technické stránky k příspěvku

Při AES Verschlüsselung Delphi v praxi málokdy jde o „AES samo o sobě“, spíše o okrajové podmínky: data musí být zpracovávána jako stream (soubory, BLOBy, zálohy), staré formáty musí zůstat čitelné a v provozu je potřeba debugovatelnost (Header, verzování) a bezpečné výchozí hodnoty (Salt/IV náhodné, bez opakovaného použití). Tento úryvek zdrojového kódu proto neukazuje jen „Encrypt/Decrypt“, ale malé, odolné formátování s Headerem, verzí, Saltem a IV – plus PBKDF2 pro odvozování klíče a místo, kde lze smysluplně doplnit integritu.

Proč „AES-String verschlüsseln“ téměř nikdy stačí

V individuálním podnikové softwaru se šifrování typicky objevuje na třech místech: (1) konfigurace/sekrety (např. přístupové údaje), (2) výměnné/exportní soubory a (3) data v klidu (např. archivy, kontejner dokumentů). Naivní přístup „heslo → AES-Key → string dovnitř/ven“ rychle narazí:

  • Opakované použití IV: V režimech jako CBC nebo GCM musí být inicializační vektor (IV) pro každé šifrování jedinečný. Konstantní IV představuje bezpečnostní slabinu, i když je heslo silné.
  • Klíč z hesla bez KDF: Použití hesla přímo jako klíče (nebo jednorázové zahashování) umožňuje offline útoky. KDF (Key Derivation Function) jako PBKDF2 útočníky cíleně zpomalí.
  • Žádná verze formátu: Bez Headeru/verze nelze později měnit počet iterací, algoritmus nebo parametry, aniž by stará data nezůstala nepoužitelná.
  • Žádná integrita: AES-CBC šifruje, ale nezabrání manipulaci. Bez autentizace (např. HMAC nebo AEAD jako GCM) dostanete problémy s bitflippingem/paddingem a těžko diagnostikovatelné chybové stavy.

Jádro tohoto příspěvku: malé kontejnerové formát, který podporuje streaming, umožňuje verzování a vyhýbá se běžným chybám.

AES Verschlüsselung Delphi s Header, Salt, IV a PBKDF2

Definujeme jednoduchý kontejnerový formát, který se dá použít i v databázových BLOBech nebo v payloadu zpráv:

  • Magic: 4 bajty, např. NBAE (rychlá kontrola „Je to náš formát?“)
  • Verze: 1 bajt (umožňuje migraci)
  • Parametry KDF: počet iterací (4 bajty)
  • Salt: 16 bajtů (náhodné pro každý soubor)
  • IV: 16 bajtů (náhodné pro každý soubor pro AES-CBC)
  • Ciphertext: zašifrovaná užitečná data (podporuje streaming)

Důležité: Salt a IV nejsou tajné. Musí být pouze nové pro každé šifrování. Heslo zůstává tajné; z něj odvozený klíč se neukládá.

AES Verschlüsselung Delphi ve streamu: zápis/čtení kontejneru

Kód je úmyslně napsán jako „Bauplan“: jasně oddělené funkce, ověřitelné Headery, žádné skryté globály. Pro AES a PBKDF2 mnoho týmů používá osvědčenou kryptografickou knihovnu (např. DEC). Tento úryvek ukazuje formát a vzor pro streaming; volání AES/PBKDF2 jsou tak zapouzdřena, že je můžete podle použité knihovny vyměnit.

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, které je třeba implementovat podle zvoleného kryptostacku —

procedure FillRandomBytes(var B: TBytes);
begin
// Pro kryptografický náhodný výstup použijte OS‑CSPRNG (Windows BCryptGenRandom,
// Linux getrandom/urandom). Zde úmyslně jako zástupce.
raise ENbCryptoError.Create(‚FillRandomBytes: CSPRNG není napojen‘);
end;

function PBKDF2_HMAC_SHA256(const APassword: string; const ASalt: TBytes;
const AIterations, AKeyLen: Cardinal): TBytes;
begin
// Implementace např. pomocí DEC (PBKDF2) nebo jiné knihovny.
// Výsledek: AKeyLen bajtů.
raise ENbCryptoError.Create(‚PBKDF2_HMAC_SHA256: není napojeno‘);
end;

procedure AES256_CBC_EncryptStream(const AKey, AIV: TBytes; const AIn, AOut: TStream);
begin
// Implementace pomocí knihovny:
// – KeyLen = 32 bajtů
// – IVLen = 16 bajtů
// – PKCS#7 padding
// Důležité: zpracovávat orientovaně ke streamu, nikoli celý obsah v paměti.
raise ENbCryptoError.Create(‚AES256_CBC_EncryptStream: není napojeno‘);
end;

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

// — Pomocné rutiny —

procedure WriteHeaderV1(const AOut: TStream; const H: TNbHeaderV1);
begin
if AOut.Write(H, SizeOf(H)) <> SizeOf(H) then
raise ENbCryptoError.Create(‚Hlavičku se nepodařilo zapsat‘);
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 nekompletní‘);

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ý kontejner (Magic nesouhlasí)‘);

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

if (H.Iterations < 10000) or (H.Iterations > 5000000) then
raise ENbCryptoError.Create(‚Počet iterací mimo rozumné mezí‘);

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 nesmí být prázdné‘);

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

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

// Odvození klíče (32 bajtů pro AES-256)
Key := PBKDF2_HMAC_SHA256(APassword, Salt, AIterations, 32);

// Šifrování uživatelských dat (šifrový text následuje přímo 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 nesmí být prázdné‘);

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šifrovat od aktuální pozice streamu (po hlavičce)
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ální kontejner vhodný pro soubory a BLOBy, včetně verzování a parametrů KDF. Okolnosti: Musíte pod to nasadit skutečné napojení na CSPRNG (kryptograficky bezpečný zdroj náhodnosti operačního systému) a robustní implementaci AES/PBKDF2. Úskalí: Nepoužívejte „nějaký“ Random (ne Random()), žádné pevné IV, a při dešifrování počítejte s jednoznačným zacházením s chybami (špatné heslo vs. poškozená data). Varianty: místo CBC raději AEAD (viz níže), nebo rozšířit hlavičku o ID algoritmu a HMAC.

Integrita: proč je AES-CBC samostatně v provozu příliš rizikové

AES-CBC je v mnoha legacy kontextech stále přítomné a může fungovat, pokud navíc použijete zajištění integrity. Bez integrity může útočník manipulovat s ciphertextem; i bez aktivního útočníka mohou přenosové chyby nebo poškozené vrstvy úložiště vyvolat těžko diagnostikovatelné chyby „Padding“.

Pragmatické možnosti:

  • Encrypt-then-HMAC: Po ciphertextu zapsat HMAC (např. HMAC-SHA-256) přes Header+Ciphertext. Při čtení nejprve ověřit HMAC, pak dešifrovat. K tomu ideálně odvodíte dva klíče z PBKDF2 (např. 64 bytů: 32 pro AES, 32 pro HMAC), místo aby byl tentýž klíč použit dvakrát.
  • AES-GCM: AEAD mód (Authenticated Encryption with Associated Data). Vytváří Ciphertext + Auth-Tag. Dnes je to často nejčistší volba, pokud vaše Delphi-knihovna GCM stabilně podporuje. Pole hlavičky lze autentizovat jako „AAD“, aniž byste je museli šifrovat.

Pokud musíte zůstat u CBC (např. kvůli interoperabilitě), je Encrypt-then-HMAC robustním doplněním. Pro nová formáty se vyplatí GCM, protože získáte autentizaci „v ceně“ a chybové stavy jsou přehlednější.

Neobvykle důležité: „kryptografická náhodnost“ a proč System.Hash nestačí

Častý legacy reflex v projektech Delphi: „Prostě vezmeme SHA256 přes timestamp + něco a máme Random.“ To není spolehlivý základ. Pro salt a IV potřebujete systémový CSPRNG (Cryptographically Secure Pseudo Random Number Generator). Pod Windows je to obvykle BCrypt-API (CNG), pod Linux generátor jádra jako getrandom() nebo /dev/urandom. Praktický rozdíl je v tom, že CSPRNG je navržen tak, aby z pozorovaných hodnot nebylo možné předpovědět další hodnoty.

Architektonický fígl: Zabalte to do malé „RandomProvider“-unit, kterou lze v testech mockovat. Tím vyřešíte dva okrajové případy najednou: reprodukovatelné testy (s pevně nastaveným seedem v mocku) a skutečné zabezpečení v produkci (se systémovým CSPRNG). Tím zabráníte tomu, aby se při hotfixu „prostě“ znovu objevil Random(), protože je to rychle.

Debugování a migrace z legacy: verzování není luxus

Hlavička není jen pro „krypto-estetiku“, ale pro udržovatelnost:

  • Iteration Tuning: Počty iterací PBKDF2 se v průběhu let mění. S políčkem v hlavičce můžete později zvýšit počet, aniž byste stará data učinili nečitelná.
  • Změna formátu: Verze 2 by se např. mohla přesunout na AES-GCM nebo doplnit HMAC.
  • Diagnostika v poli: Magic/Version umožní rychlé kontroly v logech a nástrojích bez nutnosti dešifrovat data.

Praxe – tip: Implementujte malý „inspektor“, který pouze přečte hlavičku (Magic/Version/Iterations) a zapíše ji do logu. Tím vyřešíte mnoho support případů („Která verze je zde?“) bez manipulace s hesly.

Čistá migrace: „Read old, write new“ místo Big Bang

Pokud nahrazujete starý formát (např. pevné IV, žádná KDF, Blowfish/3DES nebo vlastní XOR), osvědčil se v projektech Delphi následující vzor: Při čtení rozpoznáte více formátů (Magic/Version nebo fallback heuristika), při zápisu už vytváříte pouze nový formát. Navíc můžete po úspěšném dešifrování na pozadí provést re-encrypt („lazy migration“), pokud to zapadá do procesu. Tím snížíte riziko rolloutů a vyhnete se „jednou všechno znovu zašifrovat“ jako údržbovému oknu.

Threading und Streaming: typické okrajové případy v Delphi

Šifrování často běží ve worker vláknech (např. při exportu, při uploadu do zákaznického portálu, při zápisu velkých archivů). Dva body, které se v projektech Delphi pravidelně objevují:

  • Stream-pozice: Před šifrováním/dešifrováním jasné kontrakty: vstupní stream se čte od aktuální pozice, výstupní stream se zapisuje od aktuální pozice. Při opětovném použití streamů vždy vědomě nastavte Position := 0.
  • Paměťové špičky: Vyhněte se „všemu v TBytech“. Přístup přes streamy je klíčový u velkých souborů. Pokud vaše crypto-knihovna akceptuje pouze pole bajtů, vyplatí se dodatečná práce: přejít na streamově schopnou implementaci nebo postavit bufferovaný adaptér.

Pokud šifrujete ve službách (Windows- nebo Linux-služby), dbejte také na čisté logování výjimek: „špatné heslo“, „hlavička poškozená“, „Tag/HMAC neplatný“ jsou různé provozní případy a měly by být rozlišitelné. Důležité: chybová hlášení směrem ven nesmí být příliš podrobná (žádné „padding špatný v bloku 7“ jako API-chyba), v interním logu však ano.

Kdy se přístup vyplatí – a kde může selhat

Vyplatí se, pokud: (a) šifrovaná export-/importní data uchováváte dlouhodobě, (b) provozujete paralelně různé verze programu, (c) zpracováváte data jako streamy nebo (d) potřebujete čisté krypto-rozhraní pro více modulů ( klient/ server/ tooling ).

Nehodí se, pokud se snažíte tím vyřešit „vše“: Za transport zodpovídá TLS, ne vlastní AES-wrapper. Pro secrets (hesla, tokeny) je často vhodnější OS-Secret-Store nebo Vault. A pokud potřebujete interoperabilitu s jinými jazyky, musíte přesně zdokumentovat header, endianness a encoding (nebo použít etablovaný formát).

Závěr: AES v Delphi je méně algoritmus, více engineering

Skutečný přínos tohoto úseku není „AES běží“, ale provozuschopný formát: náhodný salt a IV, verzovaný header, PBKDF2 parametry v payloadu a streamově orientované zpracování. U nových formátů doplňte pokud možno integritu (AES-GCM nebo Encrypt-then-HMAC). Tím se z „něco zašifrujeme“ stane komponenta, která v digitálních podnikových řešeních zůstane i po letech udržitelná a migrovatelná.

Pokud musíte takový kontejner integrovat do existujícího Delphi-prostředí nebo jej čistě migrovat z legacy formátu, vyplatí se krátká architektonická kontrola (správa klíčů, verze formátu, provoz/logování). Detaily rádi vyjasníme dle potřeby v osobním rozhovoru:

V odborném prostředí hrají také Delphi Aes a Pbkdf2 Delphi důležitou roli, pokud integrace, datové toky a další vývoj musí hladce spolupracovat.

Projednat projekt nebo modernizační záměr s Net-Base.

Další krok

Když se z tématu stane reálný projekt, měly by být architektura, stávající systém a provoz včas posuzovány společně.

Podporujeme nejen při jednotlivých otázkách, ale i v případě, že se z útržků zdrojového kódu, legacy témat nebo nápadů na portál má vyvinout robustní podnikový projekt.

  • Současný stav, cílový stav a technická rizika jsou hodnoceny společně.
  • REST, přístup k datům, portály a nasazení nebudou odkládány na později.
  • Vidíte včas, která cesta je ekonomicky i provozně životaschopná.

Sdílet příspěvek

Sdílet tento příspěvek přímo

LinkedIn, X, XING, Facebook, WhatsApp a e-mail jsou ihned k dispozici. Pro Instagram připravíme odkaz a stručný text.

E-mail

Instagram se otevře v nové záložce. Odkaz a krátký text budou předtím zkopírovány do schránky.