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.
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á.