Net-Base Revista

30.05.2026

Xifrat AES a Delphi: un fragment de codi robust amb IV, sal, capçalera i streaming

Un fragment de codi font pràctic Delphi per al xifrat AES amb salt i IV aleatoris, estructura clara de capçalera de fitxer, derivació de clau PBKDF2 i xifrat en streaming – incloent les trampes típiques en formats heretats, integritat i operació.

30.05.2026

Del tema de la revista a la pràctica del projecte

Pàgines de serveis i tècniques pertinents per a l'article

En AES Verschlüsselung Delphi a la pràctica rarament falla «AES en si», sinó a causa de condicions marginals: les dades cal processar-les com a stream (fitxers, BLOBs, backups), els formats antics han de continuar llegibles, i en producció es necessita capacitat de depuració (capçaleres, versionament) i valors per defecte segurs (Salt/IV aleatoris, res d’una reutilització). Aquest fragment de codi no mostra només «Encrypt/Decrypt», sinó un petit format resistent amb capçalera, versió, Salt i IV — més PBKDF2 per derivar la clau i un punt on cal afegir integritat de manera raonable.

Per què «xifrar una cadena amb AES» gairebé mai no n’és suficient

En el programari empresarial a mida la xifració apareix típicament en tres llocs: (1) configuració/secrets (p. ex. credencials d’accés), (2) fitxers d’intercanvi/exportació i (3) dades en repòs (p. ex. arxius, contenidors de documents). L’enfocament naïf «contrasenya → clau AES → cadena dins/fora» s’enfonsa ràpidament:

  • Reutilització d’IV: En modes com CBC o GCM cal que un vector d’inicialització (IV) sigui únic per a cada xifrat. Un IV constant és una fuga, encara que la contrasenya sigui robusta.
  • Clau a partir de la contrasenya sense KDF: Utilitzar una contrasenya directament com a clau (o fer-ne un hash únic) convida a atacs offline. Una KDF (Key Derivation Function) com PBKDF2 frena els atacants de manera intencionada.
  • Absència de versió de format: Sense capçalera/versió serà gairebé impossible canviar més endavant el nombre d’iteracions, l’algorisme o altres paràmetres sense que les dades antigues restin obsoletes.
  • Cap integritat: AES-CBC xifra però no evita la manipulació. Sense autenticació (p. ex. HMAC o AEAD com GCM) s’exposen a bitflipping/padding i a imatges d’error difícils de diagnosticar.

El nucli d’aquest article: un petit format contenidor que suporta streaming, és versionable i evita els errors habituals.

AES Verschlüsselung Delphi amb capçalera, Salt, IV i PBKDF2

Definim un format contenidor senzill que també es pot utilitzar en BLOBs de base de dades o en payloads de missatges:

  • Magic: 4 bytes, p. ex. NBAE (comprovació ràpida «¿és el nostre format?»)
  • Version: 1 byte (permet migracions)
  • KDF-Parameter: nombre d’iteracions (4 bytes)
  • Salt: 16 bytes (aleatori per fitxer)
  • IV: 16 bytes (aleatori per fitxer per AES-CBC)
  • Ciphertext: dades d’usuari xifrades (compatible amb streaming)

Important: Salt i IV no són secrets. Només han de ser nous per a cada xifrat. La contrasenya roman secreta; la clau derivada d’aquesta no s’emmagatzema.

AES Verschlüsselung Delphi en stream: escriure/llegir el contenidor

El codi està deliberadament escrit com un «pla»: funcions clarament separades, capçaleres verificables, sense variables globals ocultes. Per a AES i PBKDF2 molts equips fan servir una biblioteca criptogràfica consolidada (p. ex. DEC). El fragment mostra el format i el patró de streaming; les crides AES/PBKDF2 estan tan encapsulades que les podeu substituir segons la biblioteca que utilitzeu.

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;

// — Dependències que cal implementar segons la pila criptogràfica —

procedure FillRandomBytes(var B: TBytes);
begin
// Per a la generació aleatòria per criptografia: utilitzeu el CSPRNG del SO
// (Windows BCryptGenRandom,
// Linux getrandom/urandom).
// Aquí s’ha deixat intencionadament com a marcador.
raise ENbCryptoError.Create(‚FillRandomBytes: CSPRNG no implementat‘);
end;

function PBKDF2_HMAC_SHA256(const APassword: string; const ASalt: TBytes;
const AIterations, AKeyLen: Cardinal): TBytes;
begin
// Implementació, p. ex., amb DEC (PBKDF2) o una altra biblioteca.
// Resultat: AKeyLen bytes.
raise ENbCryptoError.Create(‚PBKDF2_HMAC_SHA256: no implementat‘);
end;

procedure AES256_CBC_EncryptStream(const AKey, AIV: TBytes; const AIn, AOut: TStream);
begin
// Implementació amb biblioteca:
// – KeyLen = 32 Bytes
// – IVLen = 16 Bytes
// – PKCS#7 Padding
// Important: processar orientat a flux; no carregar-ho tot a memòria.
raise ENbCryptoError.Create(‚AES256_CBC_EncryptStream: no implementat‘);
end;

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

// — Helper —

procedure WriteHeaderV1(const AOut: TStream; const H: TNbHeaderV1);
begin
if AOut.Write(H, SizeOf(H)) <> SizeOf(H) then
raise ENbCryptoError.Create(‚No s\’ha pogut escriure l\’encapçalament‘);
end;

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

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(‚Contenidor no vàlid (Magic no coincideix)‘);

if H.Version <> CVersion then
raise ENbCryptoError.CreateFmt(‚Versió del contenidor desconeguda: %d‘, [H.Version]);

if (H.Iterations < 10000) or (H.Iterations > 5000000) then
raise ENbCryptoError.Create(‚Nombre d\’iteracions fora dels límits plausibles‘);

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(‚La contrasenya no pot estar buida‘);

// Generar Salt i IV
SetLength(Salt, CSaltLen);
SetLength(IV, CIvLen);
FillRandomBytes(Salt);
FillRandomBytes(IV);

// Omplir l\’encapçalament
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);

// Derivar la clau (32 bytes per AES-256)
Key := PBKDF2_HMAC_SHA256(APassword, Salt, AIterations, 32);

// Xifrar les dades útils (el criptotext segueix directament després de l\’encapçalament)
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(‚La contrasenya no pot estar buida‘);

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

// Desxifrar a partir de la posició actual del flux (després de l\’encapçalament)
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.

Objectiu: Un contenidor mínim que serveix per a fitxers i BLOBs, incloent versionat i paràmetres KDF. Condicions: Heu d’integrar una connexió CSPRNG real (aleatorietat criptogràfica segura des del sistema operatiu) i una implementació robusta d’AES/PBKDF2 a la base. Riscos: No utilitzeu un random qualsevol (no Random()), no emprar IVs fixes, i planifiqueu un maneig d’errors clar en el desxifrat (contrasenya errònia vs. dades corrompudes). Variants: en lloc de CBC, preferible AEAD (vegeu més avall), o ampliar l’header amb una ID d’algorisme i HMAC.

Integritat: per què AES-CBC sol no és segur en entorns d’explotació

AES-CBC encara està present en molts contextos legacy i pot funcionar si utilitzeu una protecció d’integritat addicional. Sense integritat, un atacant pot manipular el ciphertext; fins i tot sense atacant actiu, errors de transmissió o capes d’emmagatzematge defectuoses poden generar errors de «padding» difícils de diagnosticar.

Opcions pragmàtiques:

  • Encrypt-then-HMAC: Escriure després del ciphertext un HMAC (p. ex. HMAC-SHA-256) sobre Header+Ciphertext. En la lectura, comprovar primer l’HMAC i després desxifrar. Per això, idealment deriveu dos claus amb PBKDF2 (p. ex. 64 bytes: 32 per AES, 32 per HMAC), en comptes d’usar la mateixa clau doblement.
  • AES-GCM: Mode AEAD (Authenticated Encryption with Associated Data). Proporciona ciphertext + auth-tag. Avui sovint és l’opció més neta, si la vostra biblioteca Delphi suporta GCM de manera estable. Els camps d’header es poden autenticar com a «AAD» sense necessitat d’encriptar-los.

Si heu de mantenir CBC (p. ex. per interoperabilitat), Encrypt-then-HMAC és la complementació robusta. Per a nous formats val la pena GCM, perquè aporta autenticació integrada i esquemes d’error més clars.

Inusualment important: «aleatorietat criptogràfica» i per què System.Hash no és suficient

Un reflex legacy comú en projectes Delphi: «Fem un SHA256 sobre timestamp + alguna cosa i ja tenim Random.» Això no és una base fiable. Per a salt i IV necessiteu un CSPRNG (Cryptographically Secure Pseudo Random Number Generator) del sistema operatiu. Sota Windows això sol ser l’API BCrypt (CNG), i sota Linux un generador del kernel com getrandom() o /dev/urandom. La diferència és pràctica: un CSPRNG està dissenyat perquè, a partir de valors observats, no es pugui predir valors posteriors.

Truc d’arquitectura: encapsuleu-ho en una petita «RandomProvider»-Unit que pugueu mockar en tests. Així resolreu dos casos límit: proves reproduïbles (amb un Seed fix en el mock) i seguretat real en l’entorn de producció (amb OS-CSPRNG). D’aquesta manera eviteu que en un hotfix s’introdueixi de nou Random() perquè és més ràpid.

Depuració i migració legacy: el versionat no és un luxe

L’header no serveix només per a la «bellesa criptogràfica», sinó per a la mantenibilitat:

  • Ajust d’iteracions: Les iteracions de PBKDF2 canvien amb els anys. Amb un camp d’header podeu augmentar-les més endavant sense fer les dades antigues inaccessibles.
  • Canvi de format: La versió 2 podria, per exemple, passar a AES-GCM o afegir un HMAC.
  • Diagnòstic en camp: Magic/Version permeten comprovacions ràpides en logs i eines sense desxifrar les dades.

Consell pràctic: Implementi un petit «Inspector» que només llegeixi la capçalera (Magic/Version/Iterations) i la registri en un log. Això aclareix molts casos de suport («Quina versió hi ha aquí?») sense gestionar contrasenyes.

Migrar netament: „Read old, write new“ en comptes de Big Bang

Si substitueix un format antic (p. ex. IV fix, sense KDF, Blowfish/3DES o XOR fet a mida), en projectes Delphi ha funcionat un patró: en el moment de llegir reconeixeu diversos formats (Magic/Version o una heurística de fallback), i en el moment d‘escriure genereu només el nou format. Addicionalment, en desencriptar amb èxit podeu re-encryptar en segon pla («lazy migration») si encaixa amb el procés. Així reduïu el risc del rollout i eviteu «xifrar-ho tot de nou d’una sola vegada» com a finestra de manteniment.

Threading i streaming: punts crítics en Delphi

El xifrat sovint s’executa en worker-threads (p. ex. durant l’export, l’upload a un Kundenportal, o en escriure arxius grans). Dos punts que apareixen regularment en projectes Delphi:

  • Posicions del stream: Abans de xifrar/desxifrar, estableixi contracts clars: l’Input-Stream es llegeix a partir de la posició actual i l’Output-Stream s’escriu a partir de la posició actual. Si reutilitza streams, fixeu conscientment Position := 0.
  • Pics de memòria: Eviteu «tot en TBytes». L’enfocament amb streams és especialment important per a fitxers grans. Si la seva biblioteca de criptografia només accepta byte-arrays, val la pena l’esforç addicional de canviar a una implementació amb suport de streams o construir un adaptador amb búfer.

Si xifra dins de serveis (Windows- o Linux-Services), atenció també al registre net d’excepcions: «contrasenya incorrecta», «capçalera danyada», «Tag/HMAC invàlid» són casos operatius diferents i han de ser distinguibles. Important: els missatges d’error cap a l’exterior no han de ser massa detallats (cap «Padding incorrecte al bloc 7» com a error d’API), però internament al log sí que ho poden ser.

Quan val la pena l’enfocament — i on pot fallar

Val la pena si vostè: (a) emmagatzema dades d’exportació/importació xifrades de manera durable, (b) opera versions de programa diferents en paral·lel, (c) processa dades com a streams, o (d) necessita una interfície de criptografia neta per a diversos mòduls (Client/Server/Tooling).

Falla si intenta resoldre amb això «tot»: per al transport hi ha TLS, no un wrapper AES fet a mida. Per als secrets (contrasenyes, tokens) sovint és més adequat un OS-Secret-Store o un Vault. I si necessita interoperabilitat amb altres llenguatges, cal documentar amb precisió Header, Endianness i Encoding (o utilitzar un format establert).

Conclusió: AES en Delphi és menys algoritme i més engineering

El veritable benefici d’aquest fragment no és «AES funciona», sinó un format operatiu: salt i IV aleatoris, capçalera versionada, paràmetres PBKDF2 al payload i processament amb suport de streams. Per a formats nous, afegeixi preferiblement integritat (AES-GCM o Encrypt-then-HMAC). Així, de «xifrem alguna cosa» en resulta un component que en solucions digitals d’empresa continua sent mantenible i migrable passats els anys.

Si cal integrar un contenidor així en un entorn Delphi consolidat o migrar-lo de forma neta des d’un format heretat, val la pena fer una breu revisió d’arquitectura (gestió de claus, versions de format, explotació/registre). Els detalls els aclarirem, si cal, en una conversa:

En l’àmbit funcional també tenen un paper important Delphi Aes i Pbkdf2 Delphi quan les integracions, els fluxos de dades i el desenvolupament continu han de funcionar de manera coherent.

Parlar d’un projecte o d’una iniciativa de modernització amb Net-Base.

Pas següent

Quan un tema esdevé un projecte real, l'arquitectura, l'entorn existent i les operacions s'haurien de considerar conjuntament des de bon començament.

No només donem suport en qüestions puntuals, sinó també quan, a partir de fragments de codi font, temes de sistemes heredats o idees de portal, ha de sorgir un projecte empresarial sòlid.

  • L'estat actual, la visió objectiu i els riscos tècnics s'avaluen conjuntament.
  • REST, l'accés a les dades, els portals i el desplegament no es releguen a fases posteriors.
  • Vostè veurà aviat quin camí és econòmicament i operativament viable.

Comparteix la publicació

Comparteix aquesta publicació directament

LinkedIn, X, XING, Facebook, WhatsApp i E-Mail estan disponibles de forma immediata. Per a Instagram preparem directament l’enllaç i un text breu.

Correu electrònic

Instagram s'obre en una pestanya nova. L'enllaç i el text curt es copien prèviament al porta-retalls.