Net-Base Tímarit

30.05.2026

AES-dulritun í Delphi: traustur kóðabútur með IV, Salt, Header og streymi

Hagnýt kóðasneið af Delphi fyrir AES-dulritun með handahófskenndu salt og IV, skráarhausuppbyggingu, PBKDF2-lykilafleiðslu og straumvinnslu – þ.m.t. dæmigerðar gildrur í tengslum við eldri snið, heilleika og rekstur.

30.05.2026

Frá tímaritsþema til verkefnaframkvæmdar

Viðeigandi þjónustu- og tæknisíður fyrir greinina

Við AES-dulkóðun Delphi reynist vandamálið sjaldnast vera „AES í sjálfu sér“, heldur jaðarskilmálar: gögn verða að vera unnin sem straumur (skrár, BLOBs, afrit), gömul snið þurfa að vera áfram læsileg, og í rekstri þarfnast kerfið aflæsileika (Header, útgáfustýring) og öruggar sjálfgefna stillingar (salt/IV slembivalin, engin endurnotkun). Þessi kóðasneiður sýnir því ekki aðeins „Encrypt/Decrypt“, heldur lítið, traust skráarsnið með haus, útgáfu, salti og IV – auk PBKDF2 fyrir lykilafleiðslu og stað þar sem heilleiki er hægt að bæta við á skynsamlegan hátt.

Af hverju „að dulkóða AES-streng“ nægir nánast aldrei

Í sérsniðnum fyrirtækjakerfum kemur dulkóðun yfirleitt upp á þremur stöðum: (1) stillingar/leyndarmál (t.d. auðkenni), (2) skiptaskrár-/útflutningsskrár og (3) kyrrstæð gögn (t.d. geymslur, skjalaílát). Einfaldur aðgangur „lykilorð → AES-lykill → strengur inn/út“ bregst fljótt:

  • Endurnotkun IV: Í ham eins og CBC eða GCM þarf upphafsvektor (IV) að vera einstakur fyrir hverja dulkóðun. Fast IV er lekki, jafnvel þótt lykilorðið sé sterkt.
  • Lykill afleiðing úr lykilorði án KDF: Að nota lykilorð beint sem lykil (eða að hasha það einu sinni) býður upp á útrásarárásir. KDF (Key Derivation Function) eins og PBKDF2 hægir á árásaraðila markvisst.
  • Engin sniðútgáfa: Án hauss/útgáfu er nánast ómögulegt að breyta ítrekunarfjölda, reikniritinu eða öðrum þáttum síðar án þess að eldri gögn verði órænanleg.
  • Enginn heilleiki: AES-CBC dulkóðar en tryggir ekki að gögn hafi ekki verið breytt. Án staðfestingar (t.d. HMAC eða AEAD eins og GCM) eru bitaflipping- og padding-vandamál möguleg og leiða til illa greinanlegra bilana.

Kjarni þessa greinar: lítið íláta-snið sem styður straumvinnslu, er útgáfustýranlegt og forðast algengar hönnunarvillur.

AES-dulkóðun Delphi með Header, Salt, IV og PBKDF2

Við skilgreinum einfalt íláta-snið sem hentar einnig í gagnagrunns-BLOB eða skilaboða-payload:

  • Magic: 4 bæti, t.d. NBAE (hraður „Er þetta sniðið okkar?“-próf)
  • Útgáfa: 1 bait (leyfir flutning/útgáfumyndun)
  • KDF-stillingar: fjöldi ítrekana (4 bæti)
  • Salt: 16 bæti (slembivalin fyrir hverja skrá)
  • IV: 16 bæti (slembivalin fyrir hverja skrá fyrir AES-CBC)
  • Ciphertext: dulkóðuð notendagögn (straumvinnanleg)

Mikilvægt: Salt og IV eru ekki leynd. Þau þurfa einungis að vera ný fyrir hverja dulkóðun. Lykilorðið helst leynd; lykillinn sem afleiðist af því er ekki geymdur.

AES-dulkóðun Delphi í straumi: Container schreiben/lesen

Kóðinn er meðvitað skrifaður sem „uppdráttur“: skýrt aðskildar aðgerðir, prófanlegir hausar, engar falnar global-breytur. Fyrir AES og PBKDF2 nota mörg teyma traust kryp­tóbókasafn (t.d. DEC). Sniðið og straummyndunin sem sýnd er hér undir gefa grunninn; AES-/PBKDF2-köllin eru innkapsluð þannig að hægt er að skipta um útfærslu eftir því hvaða bókasafn er notað.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;

// — Háðir þættir sem þarf að útfæra miðað við crypto-stack —

procedure FillRandomBytes(var B: TBytes);
begin
// Fyrir dulkóðunar-tilviljun: nota OS-CSPRNG (Windows BCryptGenRandom,
// Linux getrandom/urandom). Hér er þetta meðvitað staðgengill.
raise ENbCryptoError.Create(‚FillRandomBytes: CSPRNG ekki innleidd‘);
end;

function PBKDF2_HMAC_SHA256(const APassword: string; const ASalt: TBytes;
const AIterations, AKeyLen: Cardinal): TBytes;
begin
// Útfærsla t.d. með DEC (PBKDF2) eða annarri bókasafni.
// Niðurstaða: AKeyLen bæti.
raise ENbCryptoError.Create(‚PBKDF2_HMAC_SHA256: ekki innleidd‘);
end;

procedure AES256_CBC_EncryptStream(const AKey, AIV: TBytes; const AIn, AOut: TStream);
begin
// Útfærsla með bókasafni:
// – KeyLen = 32 bæti
// – IVLen = 16 bæti
// – PKCS#7 fylling
// Mikilvægt: vinna straummiðað, ekki allt í minni.
raise ENbCryptoError.Create(‚AES256_CBC_EncryptStream: ekki innleidd‘);
end;

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

// — Helper —

procedure WriteHeaderV1(const AOut: TStream; const H: TNbHeaderV1);
begin
if AOut.Write(H, SizeOf(H)) <> SizeOf(H) then
raise ENbCryptoError.Create(‚Ekki tókst að skrifa haus‘);
end;

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

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(‚Ekki gildur gámi (Magic passar ekki)‘);

if H.Version <> CVersion then
raise ENbCryptoError.CreateFmt(‚Óþekkt útgáfa gáms: %d‘, [H.Version]);

if (H.Iterations < 10000) or (H.Iterations > 5000000) then
raise ENbCryptoError.Create(‚Fjöldi Iterations utan eðlilegra marka‘);

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(‚Lykilorðið má ekki vera tómt‘);

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

// Header befüllen
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);

// Key ableiten (32 Bytes für AES-256)
Key := PBKDF2_HMAC_SHA256(APassword, Salt, AIterations, 32);

// Nutzdaten verschlüsseln (Ciphertext folgt direkt nach 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(‚Lykilorðið má ekki vera tómt‘);

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

// Entschlüsseln ab aktueller Stream-Position (nach 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.

Tilgangur: Lágmarksí­lát sem hentar fyrir skrár og BLOBs, með útgáfustýringu og KDF-breytum. Skilyrði: Þið verðið að tengja raunverulega CSPRNG-an­bindingu (kryptografískt öruggt slembi úr stýrikerfinu) og leggja undir trausta AES/PBKDF2-innleiðingu. Hættur: Notið ekki „hvern sem er“ Random (ekki Random()), engar fastar IV, og skipuleggið skýra villumeðhöndlun við afkóðun (rangt lykilorð vs. skemmd gögn). Útgáfur: fremur AEAD en CBC (sjá neðar), eða stækkið hausinn með reiknialgoritma-ID og HMAC.

Heilindi: hvers vegna AES-CBC eitt og sér er of áhættusamt í rekstri

AES-CBC er enn til staðar í mörgum legacy-samhengjum og getur virkað ef þið notið aukinnar heilindarvörn. Án heilindarvörnunar getur árásarmaður breytt ciphertext; jafnvel án virks árásarmanns geta flutningsvillur eða biluð geymslulög valdið erfitt greinanlegum „padding“-villum.

Hagnýtar leiðir:

  • Encrypt-then-HMAC: Skrifið HMAC (t.d. HMAC-SHA-256) yfir haus+ciphertext á eftir dulkóðuðum texta. Þegar lesið er, staðfestið HMAC fyrst og afkóðið síðan. Til þess er æskilegt að leiða tvö lykilgildi úr PBKDF2 (t.d. 64 Bytes: 32 fyrir AES, 32 fyrir HMAC) í stað þess að nota sama lykilinn tvisvar.
  • AES-GCM: AEAD-stilling (Authenticated Encryption with Associated Data). Skilar ciphertext + auth-tag. Þetta er oft skýrasta lausnin í dag ef Delphi-bókasafnið ykkar styður GCM áreiðanlega. Hausareiti má vott­festa sem „AAD“ án þess að dulkóða þau.

Ef þið verðið að halda ykkur við CBC (t.d. vegna samvirkni), er Encrypt-then-HMAC traust viðbót. Fyrir ný snið er GCM oft þess virði, því það innbyggir auðkenningu og gerir villumyndir skýrari.

Óvenju mikilvægt: „kryptografískt slembi“ og hvers vegna System.Hash ekki dugar

Algengur legacy-viðbragðsbogi í Delphi-verkefnum: „Við tökum einfaldlega SHA256 yfir tímatákna + eitthvað og höfum Random.“ Þetta er ekki traustur grunnur. Fyrir salt og IV þarfnast þið CSPRNG (Cryptographically Secure Pseudo Random Number Generator) stýrikerfisins. Undir Windows er þetta yfirleitt BCrypt-APIið (CNG), undir Linux er það kjarnagjafi eins og getrandom() eða /dev/urandom. Munurinn er praktískur: CSPRNG er hannað þannig að út frá skoðuðum gögnum sé ekki hægt að spá fyrir um áframhaldandi gildi.

Arkitektúrráð: Innkapslið þetta í litla „RandomProvider“-einingu sem hægt er að mocka í prófum. Þannig leysið þið tvo tilfelli í einu: endurframkvæmanleg próf (með föstum seed í mock) og raunverulegt öryggi í framleiðslu (með OS-CSPRNG). Þannig komið þið í veg fyrir að í hotfixi detti aftur inn Random() „bara svo það gangi hraðar“.

Villuleitun og legacy-flutningur: Útgáfustjórnun er ekki lúxus

Hausinn er ekki aðeins fyrir „krypto-fágun“, heldur fyrir viðhald:

  • Ítrunastilling: Fjöldi PBKDF2-ítrana breytist með árunum. Með hausreit er hægt að hækka þessa tölu síðar án þess að gera eldri gögn ólesanleg.
  • Breyting á sniði: Útgáfa 2 gæti t.d. skipt yfir í AES-GCM eða bæta við HMAC.
  • Greining á vettvangi: Magic/Version leyfa hraðar athuganir í loggum og tólum án þess að afkóða gögn.

Hagnýt ráð: Innleiðið lítinn „Inspector“ sem les aðeins hausinn (Magic/Version/Iterations) og skráir í logg. Með því getið þið leyst mörg stuðningsmál („Hvaða útgáfa er þetta?“) án lykilorðshandfærslu.

Hreinn flutningur: „Read old, write new“ statt Big Bang

Ef þið eruð að afnema gamalt snið (t.d. fast IV, engin KDF, Blowfish/3DES eða sérsmíðað XOR) hefur í Delphi-verkefnum einn vinnubrögð reynst traust: Við lestri greinið þið mörg snið (Magic/Version eða fallback-heuristik), við ritun framleiðið þið eingöngu nýja sniðið. Enn fremur er hægt, við velheppnaða afkóðun, að endurkryptera í bakgrunni („lazy migration“) ef það fellur að vinnuferlinu. Þannig minnkið þið áhættu við dreifingu og forðið ykkur frá því að þurfa „að endurkryptera allt í einu“ sem þjónustuslot.

Þráða- og straumvinnsla: algengar þröskulda í Delphi

Dulkóðun keyrir oft í worker-þráðum (t.d. við útflutning, upphleðslu í viðskiptavinaportál, við skrif stórra skjalasafna). Tvö atriði sem koma reglulega upp í Delphi-verkefnum:

  • Straumstaða: Fyrir dulkóðun/afkóðun þurfa skýr samningsskilyrði að gilda: Inntaksstraumurinn er lesinn frá núverandi stöðu, úttaksstraumurinn skrifaður frá núverandi stöðu. Þegar straumar eru endurnýttir þarf að stilla Position := 0 af ásettu ráði.
  • Minni-toppur: Forðist „allt í TBytes“. Straumaaðferðin er lykilatriði fyrir stórar skrár. Ef dulkóðunarbókasafnið ykkar tekur aðeins við byte-fylkjum er vinnan við að færa í straumhæfa innleiðingu eða smíða púfferaðan aðlaga oft þess virði.

Ef þið dulkóðið innan þjónusta ( Windows- eða Linux-þjónustur ) skuluð þið jafnframt gæta hreinnar undantekningarskráningar: „rangt lykilorð“, „haus skemmdur“, „Tag/HMAC ógilt“ eru mismunandi rekstrartilvik og eiga að vera aðgreinanleg. Mikilvægt er að villuskilaboð út á við séu ekki of nákvæm (ekki „Padding rangt í blokk 7“ sem API-villu), en í innanhúss logginu má vera ítarlegra.

Hvenær aðferðin borgar sig – og hvar hún getur brugðist

Á við, ef þið: (a) geymið dulkóðuð út-/inntaksgögn til lengri tíma, (b) rekið mismunandi forritsútgáfur hlið við hlið, (c) vinnið gögn sem strauma eða (d) þurfið hreint dulkóðunarviðmót fyrir mörg módule (Client/Server/Tooling).

Hentar ekki, þegar þið reynið að leysa „allt“ með þessu: Fyrir flutning ber TLS ábyrgð, ekki sjálfsmótaður AES-wrapper. Fyrir secrets (lykilorð, tokens) er oft betra að nota OS-Secret-Store eða Vault. Og ef þið þurfið samvirkni við önnur forritunarmál þarf að skjalfesta header, endianness og encoding nákvæmlega (eða nota staðlað snið).

Niðurstaða: AES in Delphi ist weniger Algorithmus, mehr Engineering

Raunverulegi ávinningurinn við þetta brot er ekki „AES keyrir“, heldur rekstrarfært snið: handahófskennt salt og IV, útgáfumerktur haus, PBKDF2-parameter í payload og straumhæf vinnsla. Bætið við heilleika (til dæmis AES-GCM eða Encrypt-then-HMAC) fyrir ný snið. Þannig verður úr „við dulkóðum eitthvað“ byggingareining sem í stafrænum fyrirtækjalausnum er viðhaldshæf og hægt að flytja yfir jafnvel eftir mörg ár.

Ef þið þurfið að samþætta slíkan Container inn í vaxandi Delphi-umhverfi eða hreint flytja frá eldri snið (Legacy-Format), þá borgar sig stuttur arkitektúrsskoðun (lykilstjórnun, sniðútgáfur, rekstur/loggun). Nánari atriði ræðum við gjarnan eftir þörfum í samtali:

Í fagsamhengi gegna einnig Delphi Aes og Pbkdf2 Delphi mikilvægu hlutverki þegar samþættingar, gagnaflæði og áframhaldandi þróun þurfa að samræmast nákvæmlega.

Ræða verkefni eða nútímavæðingarverkefni með Net-Base.

Næsta skref

Þegar úr málinu verður raunverulegt verkefni ber að skoða arkitektúr, núverandi kerfi og rekstur snemma saman.

Við styðjum ekki aðeins við einstakar spurningar, heldur einnig þegar úr kóðabútum, eldri kerfum eða gáttahugmyndum þarf að verða traust fyrirtækjaverkefni.

  • Núverandi staða, markmynd og tæknileg áhætta eru metin saman.
  • REST, gagnaaðgangur, gáttir og innleiðing eru ekki skildir eftir til síðar.
  • Það sést snemma hvaða leið er fjárhagslega og rekstrarlega sjálfbær.

Deila færslu

Deila þessari færslu beint

LinkedIn, X, XING, Facebook, WhatsApp og tölvupóstur eru strax tiltækir. Fyrir Instagram undirbúum við hlekk og stuttan texta beint.

Tölvupóstur

Instagram opnast í nýjum flipa. Tengill og stuttur texti eru afritaðir í klippiborðið á undan.