Net-Base Магазин

30.05.2026

AES шифровање у Delphi: робустан фрагмент изворног кода са IV, Salt, заглављем и стримингом

Практичан Delphi изворни исечак кода за AES-шифровање са насумичним salt-ом и IV-јем, јасном структуром заглавља датотеке, PBKDF2-деривацијом кључа и стримовањем — укључујући типичне замке у наследним форматима, аспекте интегритета и оперативног рада.

30.05.2026

Од теме часописа до пројектне праксе

Одговарајуће странице услуга и техничке странице за чланак

При AES шифровању Delphi у пракси се ретко ради о „самом AES-у“, већ о рубним условима: подаци морају да се обрађују као stream (фајлови, BLOB-ови, резервне копије), стари формати морају остати читљиви, а у раду требају дебагабилност (заглавље, верзионисање) и сигурни подразумевани избори (Salt/IV насумични, без поновне употребе). Овај исечак из кода зато не показује само „Encrypt/Decrypt“, већ мали, робустан формат са заглављем, верзијом, Salt-ом и IV-ом – плус PBKDF2 за извођење кључа и место где је смислено допунити интегритет.

Зашто „шифровање AES-стринга“ готово никад није довољно

У индивидуалном корпоративном софтверу шифровање се типично јавља на три места: (1) конфигурација/секрети (нпр. подаци за приступ), (2) размена/извозни фајлови и (3) подаци у миру (нпр. архиве, контејнери докумената). Наиван приступ „лозинка → AES-ключ → стринг у/из“ брзо пукне:

  • Поновна употреба IV-а: У режимима као што су CBC или GCM иницијализациони вектор (IV) мора бити јединствен за сваку енкрипцију. Константан IV је цури, чак и ако је лозинка јака.
  • Кључ из лозинке без KDF: Коришћење лозинке директно као кључ (или једнократно хеширање) омогућава офлајн нападе. KDF (Key Derivation Function) као PBKDF2 намењено успорава нападаче.
  • Нема верзије формата: Без заглавља/верзије тешко касније мењате број итерација, алгоритам или параметре без да старе податке оставите „сирочадима”.
  • Нема интегритета: AES-CBC шифрује, али не спречава манипулацију. Без аутентификације (нпр. HMAC или AEAD као GCM) добијате битфлип/паддинг проблеме и тешко дијагностиковате грешке.

Керн овог чланка: мали контејнер-формат који подржава стримовање, верзионисање и избегава уобичајене грешке.

AES шифровање Delphi са заглављем, Salt-ом, IV-ом и PBKDF2

Дефинишемо прост контејнер-формат који се може користити и у BLOB-овима базе података или као payload у порукама:

  • Magic: 4 bajta, нпр. NBAE (брза провера „да ли је ово наш формат?”)
  • Верзија: 1 bajt (омогућава миграцију)
  • KDF-параметри: број итерација (4 bajta)
  • Salt: 16 bajta (насумично по фајлу)
  • IV: 16 bajta (насумично по фајлу за AES-CBC)
  • Ciphertext: шифровани корисни подаци (погодни за стримовање)

Важно: Salt и IV нису тајни. Морају бити само нови за сваку енкрипцију. Лозинка остаје тајна; из ње изведени кључ се не чува.

AES шифровање Delphi у стриму: писање/читање контејнера

Код је намерно написан као „Bauplan“: јасно раздвојене функције, проверљива заглавља, без скривених глобалних променљивих. За AES и PBKDF2 многи тимови користе проверену крипто-библиотеку (нпр. DEC). Исечак показује формат и шаблон стримовања; AES-/PBKDF2-позиви су тако капсулирани да их можете заменити у зависности од библиотеке.

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;

// — Zavisnosti koje treba implementirati zavisno od Crypto-Stack-a —

procedure FillRandomBytes(var B: TBytes);
begin
// Za kripto-slučajnost: koristiti OS-CSPRNG (Windows BCryptGenRandom,
// Linux getrandom/urandom). Ovde namerno kao rezervna oznaka.
raise ENbCryptoError.Create(‚FillRandomBytes: CSPRNG nije povezan‘);
end;

function PBKDF2_HMAC_SHA256(const APassword: string; const ASalt: TBytes;
const AIterations, AKeyLen: Cardinal): TBytes;
begin
// Implementacija npr. pomoću DEC (PBKDF2) ili druge biblioteke.
// Rezultat: AKeyLen bajtova.
raise ENbCryptoError.Create(‚PBKDF2_HMAC_SHA256: nije povezan‘);
end;

procedure AES256_CBC_EncryptStream(const AKey, AIV: TBytes; const AIn, AOut: TStream);
begin
// Implementacija putem biblioteke:
// – KeyLen = 32 Bytes
// – IVLen = 16 Bytes
// – PKCS#7 Padding
// Važno: obrađivati stream-orijentisano, ne sve u memoriji.
raise ENbCryptoError.Create(‚AES256_CBC_EncryptStream: nije povezan‘);
end;

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

// — Pomoćne rutine —

procedure WriteHeaderV1(const AOut: TStream; const H: TNbHeaderV1);
begin
if AOut.Write(H, SizeOf(H)) <> SizeOf(H) then
raise ENbCryptoError.Create(‚Zaglavlje nije moglo biti zapisano‘);
end;

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

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(‚Nije važeći kontejner (Magic se ne poklapa)‘);

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

if (H.Iterations < 10000) or (H.Iterations > 5000000) then
raise ENbCryptoError.Create(‚Broj iteracija van razumnog opsega‘);

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(‚Lozinka ne sme biti prazna‘);

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

// Popunjavanje zaglavlja
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);

// Izvod ključa (32 bajta za AES-256)
Key := PBKDF2_HMAC_SHA256(APassword, Salt, AIterations, 32);

// Šifrovanje korisnih podataka (Ciphertext sledi odmah nakon zaglavlja)
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(‚Lozinka ne sme biti prazna‘);

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

// Dešifrovanje počevši od trenutne pozicije streama (posle zaglavlja)
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.

Svrha: Minimalni kontejner pogodan za fajlove i BLOB-ove, uključujući verzionisanje i parametre KDF. Ograničenja: Morate imati pravu CSPRNG-vezu (kriptografski siguran izvor slučajnih brojeva iz operativnog sistema) i robusnu implementaciju AES/PBKDF2 ispod. Zamke: Ne koristite „bilo koji“ Random (ne Random()), izbegavajte fiksne IV-e i pri dešifrovanju planirajte jasno rukovanje greškama (pogrešna lozinka naspram oštećenih podataka). Varijante: umesto CBC radije AEAD (vidi dole), ili proširite header za ID algoritma i HMAC.

Integritet: zašto je AES-CBC sam po sebi rizičan u produkciji

AES-CBC je i dalje prisutan u mnogim legacy kontekstima i može funkcionisati ako dodatno uvedete proveru integriteta. Bez integriteta napadač može manipulisati šifratom; čak i bez aktivnog napada prenosi greške ili oštećeni slojevi skladištenja mogu izazvati teško dijagnostikovane „padding“ greške.

Pragmatične opcije:

  • Encrypt-then-HMAC: Nakon šifrata upišite HMAC (npr. HMAC-SHA-256) preko hedera+šifrata. Pri čitanju prvo proverite HMAC, pa onda dešifrujte. Za to je idealno izvesti dva ključa iz PBKDF2 (npr. 64 bajta: 32 za AES, 32 za HMAC), umesto da isti ključ koristite dvaput.
  • AES-GCM: AEAD režim (Authenticated Encryption with Associated Data). Isporučuje šifrat + auth-tag. To je danas često najsimpatičniji izbor, ako vaša Delphi-biblioteka stabilno podržava GCM. Polja hedera mogu se autentifikovati kao „AAD“ bez potrebe da ih šifrujete.

Ako morate ostati na CBC (npr. zbog interoperabilnosti), Encrypt-then-HMAC je robustno dopunsko rešenje. Za nove formate isplati se GCM, jer time dobijate autentifikaciju i slike grešaka postaju jasnije.

Neobično važno: „Kryptografischer Zufall“ und warum System.Hash nicht reicht

Čest legacy refleks u Delphi-projektima: „Samo uradimo SHA256 preko vremenskog žiga + nečega i imamo Random.“ To nije pouzdana osnova. Za salt i IV trebate CSPRNG (Cryptographically Secure Pseudo Random Number Generator) operativnog sistema. Na Windows je to tipično BCrypt-API (CNG), na Linux generator u kernelu kao što su getrandom() ili /dev/urandom. Praktična razlika je sledeća: CSPRNG je dizajniran tako da iz posmatranih vrednosti nije moguće predvideti naredne vrednosti.

Arhitektonski trik: Kapsulirajte to u malu „RandomProvider“-unit koju možete mock-ovati u testovima. Time rešavate dva slučaja: reproduktivne testove (sa fiksnim seed-om u mocku) i stvarnu bezbednost u produkciji (sa OS-CSPRNG). Tako sprečavate da u hitnom popravku „samo tako“ ponovo uđe Random() zato što je brzo.

Debugging und Legacy-Migration: Versionierung ist kein Luxus

Header nije samo zbog „kriptografske lepote“, već zbog održivosti:

  • Iteration Tuning: Broj PBKDF2 iteracija menja se tokom godina. Sa poljem u headeru možete kasnije povećati vrednosti, bez da učinite stare podatke nečitljivim.
  • Formatwechsel: Verzija 2 može, na primer, preći na AES-GCM ili dodati HMAC.
  • Diagnose im Feld: Magic/Version omogućavaju brze provere u logovima i alatima, bez potrebe za dešifrovanjem podataka.

Практичан савет: Имплементирајте мали „инспектор“ који чита само хедер (Magic/Version/Iterations) и уписује га у лог. То решава многе случајеве подршке („Која верзија је овде?“) без руковања лозинкама.

Чиста миграција: „Read old, write new“ уместо Big Bang

Ако уклањате стари формат (нпр. фиксни IV, без KDF-а, Blowfish/3DES или самоградњени XOR), у Delphi-пројектима се показао следећи образац: при читању препознајете више формата (Magic/Version или fallback-хеуристика), при писању генеришете само нови формат. Додатно, при успешној декрипцији можете у позадини поново шифровати („lazy migration“), ако то одговара процесу. Тако смањујете ризик при rollout-у и избегавате „поново све шифровати“ као прозор одржавања.

Нитовање и стриминг: типичне граничне тачке у Delphi

Шифровање често ради у радним нитима (нпр. при експорту, при отпремању у клијентски портал, при писању великих архива). Два аспекта која се у Delphi-пројектима редовно уочавају:

  • Позиције стрима: Пре шифровања/дешифровања јасни уговори: улазни стрим се чита од тренутне позиције, излазни стрим се пише од тренутне позиције. При поновној употреби стримова обавезно намерно подесите Position := 0.
  • Пикови меморије: Избегавајте „све у TBytes“. Приступ заснован на стримовима је пресудан за велике датотеке. Ако ваша крипто-библиотека прихвата само бајт-низове, вреди уложити додатни рад да пређете на стрим-способну имплементацију или да изградите буферирани адаптер.

Ако шифрујете у сервисима (Windows- или Linux-Services), обратите пажњу и на прецизно логовање изузетака: „неисправна лозинка“, „хедер оштећен“, „Tag/HMAC неважећи“ су различити оперативни случајеви и треба да буду разликовани. Важно: поруке о грешкама споља не смеју бити прекомерно детаљне (није прикладно јављати „Padding falsch in Block 7“ као API-гршку), али унутрашњи лог може садржати потребне детаље.

Када се приступ исплати — и где може да закаже

Исплати се, ако ви: (a) дугорочно складиштите шифроване податке за извоз/увоз, (b) покрећете паралелно различите верзије програма, (c) обрађујете податке као стримове или (d) требате чисту крипто-интерфејс за више модула (Client/Server/Tooling).

Неће успети, ако покушавате тиме да решите „све“: за транспорт је надлежан TLS, не самоградњени AES-Wrapper. За секрете (лозинке, токени) често је прикладнији OS-Secret-Store или Vault. И ако вам је потребна интероперабилност са другим језицима, морате тачно документовати хедер, Endianness и кодирање (или користити утврђени формат).

Закључак: AES у Delphi је мање алгоритам, више инжењеринг

Прави добитак овог исечка није „AES ради“, већ оперативни формат: случајни salt и IV, верзионисани хедер, PBKDF2-параметри у payload-у и обрада прилагођена стримовима. За нове формате, где год је могуће, допуните интегритет (AES-GCM или Encrypt-then-HMAC). Тако „ми нешто шифрујемо“ постаје грађевни блок који у дигиталним корпоративним решењима и после година остаје одржив и могућ за миграцију.

Ако треба да интегришете такав контејнер у постојећу Delphi-ландшафт или га чисто мигрирате са наслеђеног формата, исплати се кратка провера архитектуре (управљање кључевима, верзије формата, рад/логовање). Детаље радо разјашњавамо у разговору по потреби:

У стручном контексту, Delphi Aes и Pbkdf2 Delphi такође имају важну улогу када интеграције, токови података и даљи развој треба да беспрекорно функционишу.

Разговарајте о пројекту или плану модернизације са Net-Base.

Следећи корак

Када тема прерасте у реалан пројекат, архитектуру, постојеће системе и операције треба рано разматрати заједно.

Подржавамо не само у појединачним питањима, већ и када из исечака изворног кода, застарелих тема или идеја за портале треба да настане поуздан корпоративни пројекат.

  • Постојеће стање, циљано стање и технички ризици оцењују се заједно.
  • REST, приступ подацима, портали и роллаут се неће одлагати као накнадне последице.
  • Ви рано видите који пут је економски и оперативно одржив.

Подели објаву

Поделите ову објаву директно

LinkedIn, X, XING, Facebook, WhatsApp и е-пошта су одмах доступни. За Instagram припремамо линк и кратак текст.

Е-пошта

Инстаграм се отвара у новој картици. Линк и кратак текст се претходно копирају у међуспремник.