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

Ef efnið verður að raunverulegu verkefni, ætti snemma að skoða kerfisarkitektúr, núverandi kerfi og rekstur í sameiningu.

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, aðgangur að gögnum, gáttir og innleiðing verða ekki flutt til síðari tíma sem afleiðingar.
  • Þú sérð snemma hvaða leið er efnahagslega og rekstrarlega framkvæmanleg.

Deila færslu

Deila þessari færslu beint

LinkedIn, X, XING, Facebook, WhatsApp og tölvupóstur eru strax í boði. Fyrir Instagram undirbúum við tengil og stuttan texta strax.

Tölvupóstur

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