Net-Base Magasin

30.05.2026

AES-kryptering i Delphi: en robust kodesnutt med IV, salt, header og streaming

Et praktisk anvendelig Delphi-kildeutdrag for AES-kryptering med tilfeldig salt og IV, tydelig filheader-struktur, PBKDF2-nøkkelderivasjon og streaming — inkludert typiske fallgruver ved legacy-formater, integritet og drift.

30.05.2026

Fra magasinetema til prosjektpraksis

Egnede tjeneste- og tekniske sider for innlegget

Ved AES-kryptering Delphi feiler det i praksis sjelden på «AES i seg selv», men på randbetingelsene: data må behandles som stream (filer, BLOBs, backups), gamle formater må fortsatt kunne leses, og i drift trenger man mulighet for debugging (header, versjonering) og sikre standardinnstillinger (salt/IV tilfeldig, ingen gjenbruk). Denne kildekodebiten viser derfor ikke bare „Encrypt/Decrypt“, men et lite, robust format med header, versjon, salt og IV – i tillegg PBKDF2 for nøkkelavledning og et punkt hvor integritet fornuftig kan legges til.

Hvorfor «å kryptere en streng med AES» nesten aldri er nok

I skreddersydd bedriftsprogramvare opptrer kryptering typisk på tre steder: (1) konfigurasjon/secrets (f.eks. påloggingsdata), (2) utvekslings-/eksportfiler og (3) hvilende data (f.eks. arkiver, dokumentcontainere). Den naive tilnærmingen «passord → AES-nøkkel → streng inn/ut» svikter raskt:

  • Gjenbruk av IV: Ved moduser som CBC eller GCM må en initialiseringsvektor (IV) være unik per kryptering. Et konstant IV er en lekkasje, selv om passordet er sterkt.
  • Nøkkel fra passord uten KDF: Å bruke et passord direkte som nøkkel (eller hashe det én gang) åpner for offline-angrep. En KDF (Key Derivation Function) som PBKDF2 begrenser angripere målrettet.
  • Ingen formatversjon: Uten header/versjon kan du senere vanskelig endre iterasjonsantall, algoritme eller parametere uten at eldre data blir utilgjengelige.
  • Ingen integritet: AES-CBC krypterer, men beskytter ikke mot manipulering. Uten autentisering (f.eks. HMAC eller AEAD som GCM) får du bitflipping-/padding-problemer og vanskelig diagnostiserbare feilbilder.

Kjernen i dette innlegget: et lite containerformat som støtter streaming, er versjonerbart og unngår standardfeil.

AES-kryptering Delphi med header, salt, IV og PBKDF2

Vi definerer et enkelt containerformat som også kan brukes i database-BLOBs eller melding-payloads:

  • Magic: 4 Bytes, f.eks. NBAE (rask «er dette vårt format?»-sjekk)
  • Versjon: 1 Byte (muliggjør migrasjon)
  • KDF-parameter: antall iterasjoner (4 Bytes)
  • Salt: 16 Bytes (tilfeldig per fil)
  • IV: 16 Bytes (tilfeldig per fil for AES-CBC)
  • Ciphertext: krypterte nyttedata (streaming-kompatibelt)

Viktig: Salt og IV er ikke hemmelige. De må bare være nye for hver kryptering. Passordet forblir hemmelig; den derav avledede nøkkelen lagres ikke.

AES-kryptering Delphi i stream: skrive/lese container

Koden er bevisst skrevet som en «byggeplan»: klart adskilte funksjoner, kontrollerbare headere, ingen skjulte globale variabler. For AES og PBKDF2 bruker mange team et etablert kryptobibliotek (f.eks. DEC). Utdraget viser formatet og streaming-mønsteret; AES-/PBKDF2-kallene er kapslet slik at du kan bytte dem ut avhengig av 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;

// — Avhengigheter som må implementeres avhengig av crypto-stack —

procedure FillRandomBytes(var B: TBytes);
begin
// For kryptotilfeldighet: bruk OS-CSPRNG (Windows BCryptGenRandom,
// Linux getrandom/urandom). Her bevisst som plassholder.
raise ENbCryptoError.Create(‚FillRandomBytes: CSPRNG ikke tilkoblet‘);
end;

function PBKDF2_HMAC_SHA256(const APassword: string; const ASalt: TBytes;
const AIterations, AKeyLen: Cardinal): TBytes;
begin
// Implementasjon, for eksempel med DEC (PBKDF2) eller annet bibliotek.
// Resultat: AKeyLen bytes.
raise ENbCryptoError.Create(‚PBKDF2_HMAC_SHA256: ikke tilkoblet‘);
end;

procedure AES256_CBC_EncryptStream(const AKey, AIV: TBytes; const AIn, AOut: TStream);
begin
// Implementasjon via bibliotek:
// – KeyLen = 32 Bytes
// – IVLen = 16 Bytes
// – PKCS#7 Padding
// Viktig: behandle strømmen, ikke alt i minnet.
raise ENbCryptoError.Create(‚AES256_CBC_EncryptStream: ikke tilkoblet‘);
end;

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

// — Helper —

procedure WriteHeaderV1(const AOut: TStream; const H: TNbHeaderV1);
begin
if AOut.Write(H, SizeOf(H)) <> SizeOf(H) then
raise ENbCryptoError.Create(‚Header kunne ikke skrives‘);
end;

function ReadHeaderV1(const AIn: TStream): TNbHeaderV1;
var
H: TNbHeaderV1;
begin
if AIn.Read(H, SizeOf(H)) <> SizeOf(H) then
raise ENbCryptoError.Create(‚Header ufullstendig‘);

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 gyldig container (Magic stemmer ikke)‘);

if H.Version <> CVersion then
raise ENbCryptoError.CreateFmt(‚Ukjent containerversjon: %d‘, [H.Version]);

if (H.Iterations < 10000) or (H.Iterations > 5000000) then
raise ENbCryptoError.Create(‚Antall iterasjoner utenfor plausible grenser‘);

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(‚Passord må ikke være tomt‘);

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

// Fyll ut header
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);

// Deriver nøkkel (32 Bytes for AES-256)
Key := PBKDF2_HMAC_SHA256(APassword, Salt, AIterations, 32);

// Krypter nyttedata (ciphertext følger umiddelbart etter 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(‚Passord må ikke være 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);

// Dekrypter fra nåværende posisjon i stream (etter 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.

Formål: En minimal container som egner seg for filer og BLOBs, inkludert versjonering og KDF-parametere. Rammebetingelser: Det må ligge en ekte CSPRNG-tilkobling (kryptografisk sikker tilfeldighet fra operativsystemet) og en robust AES/PBKDF2-implementering under. Fallgruver: Ikke bruk «hvilken som helst» Random (ikke Random()), ingen faste IVer, og planlegg entydig feilhåndtering ved dekryptering (feil passord vs. ødelagte data). Varianter: I stedet for CBC foretrekk AEAD (se nedenfor), eller utvid header med algoritme-ID og HMAC.

Integritet: hvorfor AES-CBC alene er for risikabelt i drift

AES-CBC finnes fortsatt i mange legacy-kontekster og kan fungere hvis du i tillegg bruker integritetssikring. Uten integritet kan en angriper manipulere ciphertext; også uten en aktiv angriper kan overføringsfeil eller defekte lagringslag gi vanskelige å diagnostisere «padding»-feil.

Pragmatiske alternativer:

  • Encrypt-then-HMAC: Etter ciphertext skriver du en HMAC (f.eks. HMAC-SHA-256) over header+ciphertext. Ved lesing verifiser først HMAC, deretter dekrypter. For dette bør du ideelt sett utlede to nøkler fra PBKDF2 (f.eks. 64 bytes: 32 for AES, 32 for HMAC), i stedet for å bruke samme nøkkel to ganger.
  • AES-GCM: AEAD-modus (Authenticated Encryption with Associated Data). Leverer ciphertext + auth-tag. Dette er ofte det reneste valget i dag, hvis din Delphi-bibliotek støtter GCM stabilt. Header-felt kan autentiseres som «AAD» uten at du må kryptere dem.

Hvis du må bli værende ved CBC (f.eks. på grunn av interoperabilitet), er Encrypt-then-HMAC en robust tillegg. For nye formater lønner GCM seg, fordi du da får autentisering «på kjøpet» og feilmønstrene blir tydeligere.

Uvanlig viktig: „kryptografisk tilfeldighet“ og hvorfor System.Hash ikke er nok

En vanlig legacy-refleks i Delphi-prosjekter: «Vi tar bare SHA256 over tidsstempel + noe og så har vi Random.» Det er ikke et pålitelig grunnlag. For salt og IV trenger du en CSPRNG (Cryptographically Secure Pseudo Random Number Generator) fra operativsystemet. Under Windows er dette typisk BCrypt-APIen (CNG), under Linux en kernel-generator som getrandom() eller /dev/urandom. Forskjellen er praktisk: En CSPRNG er designet slik at man ikke kan forutsi fremtidige verdier ut fra observerte verdier.

Arkitekturtriks: Kapsle dette i en liten «RandomProvider»-unit som du kan mocke i tester. Det løser to randtilfeller samtidig: reproducerbare tester (med fast seed i mocken) og reell sikkerhet i produksjon (med OS-CSPRNG). Slik forhindrer du at Random() «sniker» seg inn i et hotfix fordi det går raskere.

Feilsøking og legacy-migrasjon: versjonering er ikke luksus

Headeren er ikke bare for «krypto-penhet», men for vedlikeholdbarhet:

  • Iteration Tuning: PBKDF2-iterasjonstall endres over tid. Med et header-felt kan du senere øke dem uten å gjøre gamle data uleselige.
  • Formatwechsel: Versjon 2 kan for eksempel bytte til AES-GCM eller legge til en HMAC.
  • Diagnose im Feld: Magic/versjon tillater raske sjekker i logger og verktøy uten å måtte dekryptere data.

Praktisk tips: Implementer en liten «inspektør» som bare leser headeren (Magic/Version/Iterations) og skriver den til en logg. Slik avklarer du mange supporttilfeller («Hvilken versjon er dette?») uten passordhåndtering.

Ryddig migrering: «Read old, write new» i stedet for Big Bang

Hvis du skal avløse et gammelt format (f.eks. statiske IV, ingen KDF, Blowfish/3DES, eller egendefinert XOR), har et mønster vist seg nyttig i Delphi-prosjekter: Ved lesing gjenkjenner du flere formater (Magic/Version eller fallback-heuristikk), ved skriving genererer du kun det nye formatet. I tillegg kan du ved vellykket dekryptering gjenskryptere i bakgrunnen («lazy migration») hvis det passer inn i prosessen. Slik reduserer du rollout-risiko og unngår «alt en gang nykryptering» som vedlikeholdsvindu.

Threading og streaming: typiske grenseflater i Delphi

Kryptering kjører ofte i worker-tråder (f.eks. ved eksport, ved opplasting til et kundeportal, ved skriving av store arkiver). To punkter som ofte merkes i Delphi-prosjekter:

  • Strømposisjoner: Før kryptering/dekryptering klare kontrakter: Input-strømmen leses fra gjeldende posisjon, output-strømmen skrives fra gjeldende posisjon. Ved gjenbruk av strømmer må du eksplisitt sette Position := 0.
  • Minne-topper: Unngå «alt i TBytes». Strømtilnærmingen er spesielt viktig for store filer. Hvis ditt kryptobibliotek bare aksepterer byte-arrays, lønner det seg å gjøre den ekstra jobben for å bytte til en strømbasert implementering eller bygge en bufret adapter.

Hvis du krypterer i tjenester (Windows- eller Linux-Services), pass også på ryddig unntakslogging: «feil passord», «header ødelagt», «Tag/HMAC ugyldig» er ulike driftsituasjoner og bør kunne skilles. Viktig: feilmeldinger må ikke være for detaljerte utad (ikke «padding feil i blokk 7» som API-feil), men internt i loggen kan de være det.

Når tilnærmingen lønner seg – og hvor den kan svikte

Lønner seg når du: (a) lagrer krypterte eksport-/importdata varig, (b) kjører forskjellige programversjoner parallelt, (c) behandler data som strømmer eller (d) trenger et ryddig krypto-grensesnitt for flere moduler (Client/Server/Tooling).

Svikter hvis du prøver å løse «alt» med det: For transport er TLS ansvarlig, ikke en egenbygd AES-wrapper. For secrets (passord, tokens) er ofte et OS-secret-store eller en vault mer passende. Og hvis du trenger interoperabilitet med andre språk, må du dokumentere header, byteorden og koding nøyaktig (eller bruke et etablert format).

Konklusjon: AES in Delphi er mindre algoritme, mer ingeniørarbeid

Den egentlige gevinsten med dette utdraget er ikke «AES fungerer», men et driftsdugelig format: tilfeldig salt og IV, versjonert header, PBKDF2-parametre i payload og strømbasert behandling. Legg for nye formater inn integritet der det er mulig (AES-GCM eller Encrypt-then-HMAC). Slik blir «vi krypterer noe» til en byggestein som i digitale bedriftsløsninger fortsatt er vedlikeholdbar og migrerbar etter år.

Hvis dere skal integrere en slik container i et etablert Delphi-landskap eller migrere den fra et legacy-format på en ryddig måte, lønner det seg med en kort arkitektur-sjekk (nøkkelhåndtering, formatversjoner, drift/logging). Detaljer avklarer vi gjerne ved behov i en samtale:

I det faglige miljøet spiller også Delphi Aes og Pbkdf2 Delphi en viktig rolle, når integrasjoner, dataflyter og videreutvikling må fungere godt sammen.

Drøft prosjekt eller moderniseringsprosjekt med Net-Base.

Neste steg

Når et tema blir et reelt prosjekt, bør arkitektur, eksisterende systemer og drift tidlig vurderes samlet.

Vi bistår ikke bare med enkeltspørsmål, men også når kodesnutter, legacy-temaer eller portalideer skal utvikles til et robust virksomhetsprosjekt.

  • Eksisterende tilstand, målbildet og tekniske risikoer vurderes samlet.
  • REST, datatilgang, portaler og utrulling blir ikke utsatt som sene følger.
  • Dere ser tidlig hvilken vei som er økonomisk og driftsmessig levedyktig.

Del innlegg

Del dette innlegget direkte

LinkedIn, X, XING, Facebook, WhatsApp og e‑post er umiddelbart tilgjengelig. For Instagram forbereder vi lenke og kort tekst umiddelbart.

E-post

Instagram åpnes i en ny fane. Lenken og kortteksten kopieres først til utklippstavlen.