Net-Base Magasin

30.05.2026

AES-kryptering i Delphi: eit robust kjeldekodeutdrag med IV, salt, header og streaming

Eit praksisnært Delphi source-kodeutdrag for AES-kryptering med tilfeldig salt og IV, klar fil‑header‑struktur, PBKDF2‑nøkkelderivasjon og streaming – inkludert typiske fallgruver ved legacy‑format, integritet og drift.

30.05.2026

Frå magasinetema til prosjektpraksis

Passande teneste- og tekniske sider til innlegget

I praksis feiler det sjeldan på «AES i seg sjølv» ved AES-kryptering Delphi, men heller på randvilkåra: data må handsamast som stream (filer, BLOBs, sikkerheitskopiar), gamle format må framleis kunne lesast, og i drift treng ein feilsøkingsmoglegheit (header, versjonering) og trygge standardar (Salt/IV tilfeldig, ikkje gjenbruk). Denne kodesnutten viser difor ikkje berre «krypter/dekrypter», men eit lite, robust format med header, versjon, salt og IV – pluss PBKDF2 for nøkkelderivering og eit punkt der integritet kan leggast til på ein fornuftig måte.

Kvifor «AES-strengkryptering» nesten aldri held

I skreddarsydd bedriftsprogramvare dukkar kryptering typisk opp på tre område: (1) konfigurasjon/secrets (t.d. påloggingsdata), (2) utvekslings-/eksportfiler og (3) kvilerande data (t.d. arkiv, dokumentcontainer). Den naive tilnærminga «passord → AES-nøkkel → streng inn/ut» bryt raskt saman:

  • IV-gjenbruk: Ved modusar som CBC eller GCM må ein initialiseringsvektor (IV) vere unik for kvar kryptering. Eit konstant IV er eit lekkasje, sjølv om passordet er sterkt.
  • Nøkkel frå passord utan KDF: Å bruke eit passord direkte som nøkkel (eller å hashe det ein gong) inviterer til offline-angrep. Ein KDF (Key Derivation Function) som PBKDF2 bremsar angriparar målretta.
  • Ingen formatversjon: Utan header/versjon er det vanskeleg å endre iterasjonstal, algoritme eller parameter seinare utan at gamle data blir «forlatt».
  • Ingen integritet: AES-CBC krypterer, men hindrar ikkje manipulering. Utan autentisering (t.d. HMAC eller AEAD som GCM) får du bitflipping-/padding-problem og vanskeleg diagnostiserte feilsituasjonar.

Kjernen i dette innlegget er eit lite containerformat som støttar streaming, er versjonerbart og unngår dei vanlegaste feila.

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

Vi definerer eit enkelt containerformat som òg kan nyttast i database-BLOBs eller melding-payloads:

  • Magic: 4 byte, f.eks. NBAE (rask «er dette vårt format?»-sjekk)
  • Version: 1 byte (gir moglegheit for migrasjon)
  • KDF-Parameter: iterasjonstal (4 byte)
  • Salt: 16 byte (tilfeldig per fil)
  • IV: 16 byte (tilfeldig per fil for AES-CBC)
  • Ciphertext: krypterte nyttelast (støttar streaming)

Viktig: Salt og IV er ikkje hemmelege. Dei må berre vere nye for kvar kryptering. Passordet held seg hemmeleg; nøkkelen avleia frå det vert ikkje lagra.

AES-kryptering Delphi i stream: skrive/lese container

Koden er medvite skriven som ein «byggeplan»: klart avgrensa funksjonar, kontrollerbare header, inga skjulte globale variablar. For AES og PBKDF2 nyttar mange team ei velprøvd kryptobibliotek (t.d. DEC). Kodesnutten syner formatet og streaming-mønsteret; AES-/PBKDF2-kalla er innkapsla slik at du kan byte dei ut etter kva bibliotek de brukar.

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;

// — Avhengigheiter som må implementerast avhengig av Crypto-Stack —

procedure FillRandomBytes(var B: TBytes);
begin
// For kryptotilfeldighet: bruk OS-CSPRNG (Windows BCryptGenRandom,
// Linux getrandom/urandom). Her med vilje som plasshaldar.
raise ENbCryptoError.Create(‚FillRandomBytes: CSPRNG ikkje tilkopla‘);
end;

function PBKDF2_HMAC_SHA256(const APassword: string; const ASalt: TBytes;
const AIterations, AKeyLen: Cardinal): TBytes;
begin
// Implementering t.d. med DEC (PBKDF2) eller anna bibliotek.
// Resultat: AKeyLen byte.
raise ENbCryptoError.Create(‚PBKDF2_HMAC_SHA256: ikkje tilkopla‘);
end;

procedure AES256_CBC_EncryptStream(const AKey, AIV: TBytes; const AIn, AOut: TStream);
begin
// Implementering via bibliotek:
// – KeyLen = 32 byte
// – IVLen = 16 byte
// – PKCS#7 Padding
// Viktig: handsam straumen, ikkje alt i minnet.
raise ENbCryptoError.Create(‚AES256_CBC_EncryptStream: ikkje tilkopla‘);
end;

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

// — Helper —

procedure WriteHeaderV1(const AOut: TStream; const H: TNbHeaderV1);
begin
if AOut.Write(H, SizeOf(H)) <> SizeOf(H) then
raise ENbCryptoError.Create(‚Headeren kunne ikkje skrivast‘);
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(‚Ikkje ein gyldig container (Magic stemmer ikkje)‘);

if H.Version <> CVersion then
raise ENbCryptoError.CreateFmt(‚Ukjend container-versjon: %d‘, [H.Version]);

if (H.Iterations < 10000) or (H.Iterations > 5000000) then
raise ENbCryptoError.Create(‚Iterasjonstal utanfor 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(‚Passordet kan ikkje vere tomt‘);

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

// Fyll ut headeren
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 byte for AES-256)
Key := PBKDF2_HMAC_SHA256(APassword, Salt, AIterations, 32);

// Krypter nyttedata (ciphertekst følgjer direkte etter headeren)
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(‚Passordet kan ikkje vere 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 frå gjeldande strømposisjon (etter headeren)
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: Ein minimal behaldar som eignar seg for filer og BLOBs, inklusive versjonering og KDF-parametrar. Rammevilkår: De må legge under ein ekte CSPRNG-tilkopling (kryptografisk sikker tilfeldigheit frå operativsystemet) og ein robust AES/PBKDF2-implementasjon. Fallgruver: Ikkje bruk «ein eller annan» Random (ikkje Random()), inga faste IV, og planlegg eintydig feilhandtering ved dekryptering (feil passord vs. skadde data). Variantar: i staden for CBC heller AEAD (sjå nedanfor), eller utvid header med algoritme-ID og HMAC.

Integritet: kvifor AES-CBC åleine er for risikabelt i drift

AES-CBC finst framleis i mange legacy-samanhengar og kan fungere så lenge du i tillegg brukar ei integritetssikring. Utan integritet kan ein angripar manipulere ciphertext; sjølv utan aktiv angrip kan overføringsfeil eller feil i lagringslag gi vanskelege å diagnostisere «padding»-feil.

Pragmatiske alternativ:

  • Encrypt-then-HMAC: Skriv ein HMAC (t.d. HMAC-SHA-256) over header+ciphertext etter ciphertext. Ved lesing sjekkar du først HMAC, og deretter dekrypterer. For dette bør du ideelt sett utleie to nøklar frå PBKDF2 (t.d. 64 Bytes: 32 for AES, 32 for HMAC), i staden for å bruke same nøkkelen to gongar.
  • AES-GCM: AEAD-modus (Authenticated Encryption with Associated Data). Leverer ciphertext + auth-tag. Dette er ofte det reinaste valet i dag, dersom din Delphi-bibliotek støttar GCM stabilt. Headerfelt kan autentiserast som «AAD» utan at du treng å kryptere dei.

Om du må halde fast ved CBC (t.d. av interoperabilitetsgrunnar), er Encrypt-then-HMAC den robuste tilleggsregelen. For nye format er GCM ofte å føretrekkje, fordi du då får autentisering som ein del av formatet og feilsituasjonar blir tydare.

Særleg viktig: „kryptografisk tilfeldighet“ og kvifor System.Hash ikkje rekk

Eit vanleg legacy-refleks i Delphi-prosjekt: «Vi tek berre SHA256 over tidsstempel + noko og har Random.» Det er ikkje ein påliteleg basis. For salt og IV trengst ein CSPRNG (Cryptographically Secure Pseudo Random Number Generator) frå operativsystemet. Under Windows er det typisk BCrypt-APIen (CNG), under Linux ein kernel-generator som getrandom() eller /dev/urandom. Forskjellen er praktisk: ein CSPRNG er designa slik at ein ikkje frå observerte verdiar kan føreseie påfølgjande verdiar.

Arkitektur-tipset: Kapsle dette i ei lita «RandomProvider»-Unit som du kan mocke i testar. Då løyser du to problem samtidig: reproducerbare testar (med fast seed i mocken) og ekte sikkerheit i produksjon (med OS-CSPRNG). Det hindrar at ein i eit hastetiltak «sjå, det er kjapt» igjen klistrar inn Random().

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

Headeren er ikkje berre for «krypto-skjønnheit», men for vedlikehald:

  • Justering av iterasjonar: PBKDF2-iterasjonstal endrar seg over år. Med eit headerfelt kan du seinare auke talet utan å gjere gamle data uleselege.
  • Formatendring: Versjon 2 kan til dømes byte til AES-GCM eller leggje til ein HMAC.
  • Diagnose i felt: Magic/Version tillet raske sjekkar i loggar og verktøy utan å dekryptere data.

Praktisk tips: Implementer ein liten „Inspector“, som berre les headeren (Magic/Version/Iterations) og skriv til ein logg. Slik avklarar du mange supporttilfelle («Kva versjon ligg her?») utan passordhandtering.

Migrer ryddig: „Read old, write new“ i staden for Big Bang

Når du skal erstatte eit gamalt format (t.d. fast IV, ingen KDF, Blowfish/3DES, eller eigenbygd XOR), har eit mønster vist seg å fungere i Delphi-prosjekt: Ved lesing gjenkjenner du fleire format (Magic/Version eller fallback-heuristikk), ved skriving produserer du berre det nye formatet. I tillegg kan du ved vellykka dekryptering i bakgrunnen gjenkryptere («lazy migration») dersom det passar i prosessen. Slik reduserer du rollout-risiko og unngår å måtte «kryptere alt på nytt på ein gong» som eit vedlikehaldsvindu.

Threading og Streaming: typiske kantar i Delphi

Kryptering køyrer ofte i Worker-Threads (t.d. ved eksport, ved opplasting til eit kundeportal, ved skriving av store arkiv). To punkt som jamleg dukkar opp i Delphi-prosjekt:

  • Stream-posisjonar: Før kryptering/dekryptering må det vere tydelege kontraktar: Input-streamen blir lesen frå gjeldande posisjon, output-streamen blir skrive frå gjeldande posisjon. Ved gjenbruk av streamar må du medvite setje Position := 0.
  • Minne-toppar: Unngå «alt i TBytes». Stream-tilnærminga er særleg viktig for store filer. Dersom kryptobiblioteket ditt berre godtar byte-arrays, kan det løne seg å gjere det ekstra arbeidet med å bytte til ei stream-venleg implementering eller å byggje ein bufra adapter.

Når du krypterer i tenester (Windows- eller Linux-tenester), ver òg merksam på ryddig unntakslogging: «feil passord», «header øydelagt», «Tag/HMAC ugyldig» er ulike driftstilfelle og bør vere skilne. Viktig her: Feilmeldingar skal ikkje vere for detaljerte utad (ikkje «Padding feil i blokk 7» som ein API-feil), men internt i loggen kan dei vere det.

Når lønar tilnærminga seg – og kvar han kan svikte

Lønar seg, når du: (a) lagrar krypterte eksport-/importdata over tid, (b) driv ulike programversjonar parallelt, (c) handsamar data som streamar, eller (d) treng eit ryddig kryptogrensesnitt for fleire modul (Client/Server/Tooling).

Kjem til kort, når du prøver å løyse «alt» med dette: For transport er TLS ansvarleg, ikkje ein eigenbygd AES-wrapper. For secrets (passord, tokens) er ofte eit OS-Secret-Store eller ein Vault meir fornuftig. Og dersom du treng interoperabilitet med andre språk, må du dokumentere header, endianness og encoding nøyaktig (eller nytte eit etablert format).

Konklusjon: AES in Delphi er mindre algoritme, meir engineering

Den eigentlege gevinsten med dette utdraget er ikkje «AES fungerer», men eit driftsføre format: tilfeldig salt og IV, versjonert header, PBKDF2-parameter i payload og stream-venleg handsaming. Legg til integritetsbeskyttelse for nye format der det let seg gjere (AES-GCM eller Encrypt-then-HMAC). Slik går ein frå «vi krypterar noko» til eit byggjelement som i digitale verksemdsløysingar framleis er vedlikehaldsemd og kan migrerast også etter fleire år.

Dersom ein skal integrere ein slik container i eit etablert Delphi-landskap eller migrere frå eit legacy-format på ein ryddig måte, er ein kort arkitektur-sjekk på sin plass (nøkkelhandsaming, formatversjonar, drift/logging). Detaljar avklarar vi ved behov gjerne i samtale:

I det faglege miljøet spelar også Delphi Aes og Pbkdf2 Delphi ei viktig rolle når integrasjonar, dataflyt og vidareutvikling må samspela på ein ryddig måte.

Drøfte prosjekt eller moderniseringsprosjekt med Net-Base.

Neste steg

Når temaet blir eit reelt prosjekt, bør arkitektur, eksisterande system og drift vurderast tidleg saman.

Vi støttar ikkje berre ved enkeltspørsmål, men òg når korte kildekodesnuttar, legacy-tema eller portalidéar skal utviklast til eit robust bedriftsprosjekt.

  • Eksisterande tilstand, målbiletet og tekniske risikoar blir vurderast samla.
  • REST, datatilgang, portalar og utrulling blir ikkje utsette til seinare som etterverknader.
  • De ser tidleg kva veg som er økonomisk og driftsmessig berekraftig.

Del innlegg

Del dette innlegget direkte

LinkedIn, X, XING, Facebook, WhatsApp og e-post er tilgjengelege med ein gong. For Instagram klargjer vi lenke og kort tekst med det same.

E-post

Instagram opnar i ein ny fane. Lenkje og kort tekst blir kopiert til utklippstavla på førehand.