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.