Från magasinets tema till projektpraxis
Passande tjänste- och tekniksidor för inlägget
I praktiken beror det sällan på AES Verschlüsselung Delphi i sig, utan på randvillkoren: data måste behandlas som en ström (filer, BLOBs, backuper), gamla format måste förbli läsbara, och i drift krävs debugbarhet (header, versionering) och säkra standardinställningar (salt/IV slumpmässigt, ingen återanvändning). Denna kodsnutt visar därför inte bara „Encrypt/Decrypt“, utan ett litet, robust format med header, version, salt och IV – plus PBKDF2 för nyckelavledning och en plats där integritet med fördel kan kompletteras.
Varför „AES-String verschlüsseln“ nästan aldrig räcker
I specialanpassad företagsprogramvara uppträder kryptering typiskt på tre ställen: (1) konfiguration/hemligheter (t.ex. åtkomstuppgifter), (2) utbytes-/exportfiler och (3) vilande data (t.ex. arkiv, dokumentcontainrar). Det naiva tillvägagångssättet „lösenord → AES-nyckel → sträng in/ut“ fallerar snabbt:
- IV-Wiederverwendung: I lägen som CBC eller GCM måste en initialiseringsvektor (IV) vara unik för varje kryptering. Ett konstant IV är en läcka, även om lösenordet är starkt.
- Key aus Passwort ohne KDF: Att använda ett lösenord direkt som nyckel (eller hasha det en gång) utsätter för offline-attacker. En KDF (Key Derivation Function) som PBKDF2 bromsar angripare målmedvetet.
- Keine Formatversion: Utan header/version kan du senare knappast ändra iterationsantal, algoritm eller parametrar utan att äldre data blir oanvändbara.
- Keine Integrität: AES-CBC krypterar men förhindrar inte manipulation. Utan autentisering (t.ex. HMAC eller AEAD som GCM) får du bitflipping-/padding-problem och svårt diagnostiserade felbilder.
Kärnan i detta inlägg: ett litet containerformat som stödjer streaming, är versionerbart och undviker standardfelen.
AES Verschlüsselung Delphi mit Header, Salt, IV und PBKDF2
Vi definierar ett enkelt containerformat som även lämpar sig för användning i databas-BLOBs eller meddelandepayloads:
- Magic: 4 byte, t.ex.
NBAE(snabbt „är det vårt format?“-kontroll) - Version: 1 byte (möjliggör migration)
- KDF-Parameter: iterationsantal (4 byte)
- Salt: 16 byte (slumpmässigt per fil)
- IV: 16 byte (slumpmässigt per fil för AES-CBC)
- Ciphertext: krypterade nyttodata (strömvänligt)
Viktigt: Salt och IV är inte hemliga. De behöver bara vara nya för varje kryptering. Lösenordet förblir hemligt; den därav härledda nyckeln sparas inte.
AES Verschlüsselung Delphi im Stream: Container schreiben/lesen
Koden är medvetet skriven som en „ritning“: tydligt separerade funktioner, kontrollerbara headers, inga dolda globala variabler. För AES och PBKDF2 använder många team ett beprövat kryptobibliotek (t.ex. DEC). Kodsnutten visar formatet och streamingmönstret; AES-/PBKDF2-anropen är kapslade så att du kan byta dem beroende på bibliotek.
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;
// — Beroenden som du måste implementera beroende på Crypto-Stack —
procedure FillRandomBytes(var B: TBytes);
begin
// För kryptografisk slump: använd OS-CSPRNG (Windows BCryptGenRandom,
// Linux getrandom/urandom). Här avsiktligt som platshållare.
raise ENbCryptoError.Create(‚FillRandomBytes: CSPRNG inte ansluten‘);
end;
function PBKDF2_HMAC_SHA256(const APassword: string; const ASalt: TBytes;
const AIterations, AKeyLen: Cardinal): TBytes;
begin
// Implementering t.ex. med DEC (PBKDF2) eller annat bibliotek.
// Resultat: AKeyLen bytes.
raise ENbCryptoError.Create(‚PBKDF2_HMAC_SHA256: inte implementerad‘);
end;
procedure AES256_CBC_EncryptStream(const AKey, AIV: TBytes; const AIn, AOut: TStream);
begin
// Implementering via bibliotek:
// – KeyLen = 32 bytes
// – IVLen = 16 bytes
// – PKCS#7-padding
// Viktigt: behandla strömmar, inte allt i minnet.
raise ENbCryptoError.Create(‚AES256_CBC_EncryptStream: inte implementerad‘);
end;
procedure AES256_CBC_DecryptStream(const AKey, AIV: TBytes; const AIn, AOut: TStream);
begin
raise ENbCryptoError.Create(‚AES256_CBC_DecryptStream: inte implementerad‘);
end;
// — Helper —
procedure WriteHeaderV1(const AOut: TStream; const H: TNbHeaderV1);
begin
if AOut.Write(H, SizeOf(H)) <> SizeOf(H) then
raise ENbCryptoError.Create(‚Header kunde inte skrivas‘);
end;
function ReadHeaderV1(const AIn: TStream): TNbHeaderV1;
var
H: TNbHeaderV1;
begin
if AIn.Read(H, SizeOf(H)) <> SizeOf(H) then
raise ENbCryptoError.Create(‚Header ofullständig‘);
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(‚Ingen giltig container (Magic stämmer inte)‘);
if H.Version <> CVersion then
raise ENbCryptoError.CreateFmt(‚Okänd container-version: %d‘, [H.Version]);
if (H.Iterations < 10000) or (H.Iterations > 5000000) then
raise ENbCryptoError.Create(‚Antal iterationer utanför rimliga gränser‘);
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(‚Lösenord får inte vara tomt‘);
// Generera salt/IV
SetLength(Salt, CSaltLen);
SetLength(IV, CIvLen);
FillRandomBytes(Salt);
FillRandomBytes(IV);
// Fyll i headern
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);
// Derivera nyckel (32 bytes för AES-256)
Key := PBKDF2_HMAC_SHA256(APassword, Salt, AIterations, 32);
// Kryptera nyttodata (ciphertext följer direkt efter 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(‚Lösenord får inte vara tomt‘);
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);
// Dekryptera från aktuell strömposition (efter 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.
Syfte: En minimal behållare som lämpar sig för filer och BLOBs, inklusive versionering och KDF‑parametrar. Förutsättningar: En riktig CSPRNG‑anslutning (kryptografiskt säker slump från operativsystemet) och en robust AES/PBKDF2‑implementation ska användas under. Fallgropar: Ta inte vilken slumpgenerator som helst (ingen Random()), inga fasta IV, och planera för tydlig felhantering vid dekryptering (fel lösenord kontra korrupta data). Varianter: istället för CBC föredra AEAD (se nedan), eller utöka headern med algoritm‑ID och HMAC.
Integritet: varför AES-CBC ensam i drift är för riskabelt
AES‑CBC förekommer fortfarande i många legacy‑sammanhang och kan fungera om du använder en ytterligare integritetsskyddsmekanism. Utan integritet kan en angripare manipulera ciphertext; även utan aktiv angripare kan överföringsfel eller defekta lagringslager orsaka svårdiagnostiserade padding‑fel.
Pragmatiska alternativ:
- Encrypt‑then‑HMAC: Skriv en HMAC (t.ex. HMAC‑SHA‑256) över header+ciphertext efter ciphertexten. Vid läsning verifieras först HMAC och därefter dekrypteras. Här bör ni helst härleda två nycklar ur PBKDF2 (t.ex. 64 byte: 32 för AES, 32 för HMAC) istället för att återanvända samma nyckel dubbelt.
- AES‑GCM: AEAD‑läge (Authenticated Encryption with Associated Data). Levererar ciphertext + auth‑tag. Det är ofta det renaste valet idag, om din Delphi-bibliotek stabilt stöder GCM. Headerfält kan autentiseras som „AAD“ utan att krypteras.
Om ni måste hålla er till CBC (t.ex. av interoperabilitetsskäl) är Encrypt‑then‑HMAC en robust komplettering. För nya format är GCM ofta att föredra, eftersom det ger inbyggd autentisering och tydligare felbilder.
Ovanligt viktigt: „Kryptografisk slump“ och varför System.Hash inte räcker
Ett vanligt legacy‑reflex i Delphi‑projekt är: „Vi tar helt enkelt SHA256 över tidsstämpel + nånting och har slump.“ Det är inte en pålitlig grund. För salt och IV behöver ni en CSPRNG (Cryptographically Secure Pseudo Random Number Generator) från operativsystemet. Under Windows är det typiskt BCrypt‑API:n (CNG), under Linux en kernel‑generator som getrandom() eller /dev/urandom. Skillnaden är praktisk: en CSPRNG är konstruerad så att observerade värden inte möjliggör förutsägelse av framtida värden.
Arkitektur‑knep: Kapsla detta i en liten „RandomProvider“‑unit som ni kan mocka i tester. Då löser ni två saker samtidigt: reproducerbara tester (med fast seed i mocken) och riktig säkerhet i produktionsdrift (med OS‑CSPRNG). Så förhindrar ni att ett snabbfix återinför Random() bara för att det går fort.
Debugging och legacy‑migrering: versionering är ingen lyx
Headern är inte bara för „krypto‑snygghet“, utan för underhållbarhet:
- Justering av iterationsantal: PBKDF2‑iterationsantal förändras över åren. Med ett headerfält kan ni höja iterationsantalet senare utan att göra gamla data oläsbara.
- Formatbyte: Version 2 kan till exempel byta till AES‑GCM eller lägga till en HMAC.
- Felsökning i fältet: Magic/Version möjliggör snabba kontroller i loggar och verktyg utan att behöva dekryptera data.
Praktiskt tips: Implementera en liten „Inspector“ som endast läser av headern (Magic/Version/Iterations) och skriver till en logg. På så sätt kan ni hantera många supportfall („Vilken version är det här?“) utan lösenordshantering.
Migrera rent: „Read old, write new“ istället för Big Bang
Om ni byter ut ett gammalt format (t.ex. fast IV, ingen KDF, Blowfish/3DES eller egenbyggd XOR) har ett mönster visat sig fungera väl i Delphi-projekt: vid läsning känner ni igen flera format (Magic/Version eller fallback-heuristik), vid skrivning skapar ni endast det nya formatet. Dessutom kan ni vid framgångsrik dekryptering i bakgrunden re-encrypta („lazy migration“) om det passar processen. Så minskar ni rollout-risk och undviker att behöva „kryptera om allt på en gång“ som ett underhållsfönster.
Threading und Streaming: typische Kanten in Delphi
Kryptering körs ofta i worker-trådar (t.ex. vid export, vid uppladdning till ett kundportal, vid skrivning av stora arkiv). Två punkter som regelbundet dyker upp i Delphi-projekt:
- Stream-Positionen: Innan kryptering/dekryptering tydliga kontrakt: ingångsströmmen läses från aktuell position, utgångsströmmen skrivs från aktuell position. Vid återanvändning av strömmar måste
Position := 0sättas medvetet. - Memory-Spitzen: Undvik „allt i TBytes“. Strömansatsen är särskilt viktig för stora filer. Om ert kryptobibliotek endast accepterar byte-arrayer kan det vara värt det extra arbetet att byta till en strömkompatibel implementation eller bygga en buffrad adapter.
Om ni krypterar i tjänster (Windows- eller Linux-tjänster), tänk också på ordentlig exception-logging: „fel lösenord“, „header korrupt“, „Tag/HMAC ogiltigt“ är olika driftfall och bör gå att särskilja. Viktigt här: felmeddelanden får inte vara för detaljerade externt (ingen „Padding fel i block 7“ som API-fel), men internt i loggen kan de vara det.
När tillvägagångssättet lönar sig – och var det kan bli olämpligt
Lönar sig, om ni: (a) lagrar krypterade export-/importdata långsiktigt, (b) kör olika programversioner parallellt, (c) bearbetar data som strömmar eller (d) behöver ett renodlat kryptogränssnitt för flera moduler (Client/Server/Tooling).
Blir olämpligt, om ni försöker lösa „allt“ med detta: För transport ansvarar TLS, inte en egenbyggd AES-wrapper. För secrets (lösenord, tokens) är ofta ett OS-secret-store eller ett Vault mer lämpligt. Och om ni behöver interop med andra språk måste ni dokumentera header, endianness och encoding exakt (eller använda ett etablerat format).
Slutsats: AES i Delphi är mindre algoritm, mer ingenjörsarbete
Den egentliga vinsten med detta utdrag är inte „AES fungerar“, utan ett driftsdugligt format: slumpmässig salt och IV, versionerad header, PBKDF2-parametrar i payload och strömkompatibel behandling. Lägg till integritet för nya format där det är möjligt (AES-GCM eller Encrypt-then-HMAC). På så sätt blir „vi krypterar någonting“ en byggsten som i digitala företagslösningar även efter år är underhållsbar och migrerbar.
Om ni ska integrera en sådan container i ett etablerat Delphi-landskap eller måste migrera den från ett legacy-format på ett ordnat sätt, är en kort arkitekturgranskning (nyckelhantering, formatversioner, drift/loggning) väl motiverad. Detaljer klargör vi vid behov gärna i ett samtal:
I det tekniska sammanhanget spelar också Delphi Aes och Pbkdf2 Delphi en viktig roll när integrationer, dataflöden och vidareutveckling måste samspela på ett ordnat sätt.
Nästa steg
När ett ämne blir ett verkligt projekt bör arkitektur, befintliga system och drift behandlas gemensamt redan i ett tidigt skede.
Vi stöder inte bara vid enstaka frågor, utan även när kodsfragment, legacy-frågor eller portalidéer ska utvecklas till ett robust företagsprojekt.
- Nuläge, målbild och tekniska risker bedöms tillsammans.
- REST, dataåtkomst, portaler och utrullning skjuts inte upp som sena följder.
- Ni ser tidigt vilken väg som är ekonomiskt och driftsmässigt bärkraftig.