Net-Base Magazine

30.05.2026

Chiffrement AES dans Delphi : un extrait de code source robuste avec IV, sel, en-tête et streaming

Un extrait de code source Delphi pragmatique pour le chiffrement AES avec sel et IV aléatoires, une structure d'en-tête de fichier claire, dérivation de clé PBKDF2 et streaming — incluant les écueils typiques liés aux formats hérités, à l'intégrité et à l'exploitation.

30.05.2026

Du thème du magazine à la pratique des projets

Pages de services et techniques pertinentes pour l'article

Dans la pratique, les problèmes liés à Chiffrement AES Delphi ne proviennent que rarement de « l’AES en lui‑même », mais des conditions périphériques : les données doivent être traitées en flux (fichiers, BLOBs, sauvegardes), les anciens formats doivent rester lisibles, et en exploitation on a besoin de débogabilité (header, versioning) et de valeurs par défaut sûres (Salt/IV aléatoires, pas de réutilisation). Ce fragment de code source montre donc non seulement « Encrypt/Decrypt », mais un petit format robuste avec header, version, Salt et IV – plus PBKDF2 pour la dérivation de clé et un endroit où l’intégrité peut être ajoutée de façon pertinente.

Pourquoi « chiffrer une chaîne AES » est rarement suffisant

Dans les logiciels d’entreprise sur mesure, le chiffrement apparaît typiquement à trois endroits : (1) configuration/secrets (p. ex. identifiants), (2) fichiers d’échange/export et (3) données au repos (p. ex. archives, conteneurs de documents). L’approche naïve « mot de passe → clé AES → chaîne entrée/sortie » montre vite ses limites :

  • Réutilisation de l’IV : pour des modes comme CBC ou GCM, le vecteur d’initialisation (IV) doit être unique par chiffrement. Un IV constant constitue une fuite, même si le mot de passe est fort.
  • Clé issue du mot de passe sans KDF : utiliser un mot de passe directement comme clé (ou le hacher une fois) facilite les attaques hors ligne. Une KDF (Key Derivation Function) comme PBKDF2 ralentit efficacement les attaquants.
  • Absence de version de format : sans header/version, il est difficile de modifier ultérieurement le nombre d’itérations, l’algorithme ou les paramètres sans rendre les anciennes données « orphelines ».
  • Aucune intégrité : AES‑CBC chiffre, mais n’empêche pas la manipulation. Sans authentification (p. ex. HMAC ou AEAD comme GCM), vous êtes exposé aux problèmes de bitflipping/padding et à des erreurs difficiles à diagnostiquer.

Le cœur de cet article : un petit format conteneur qui prend en charge le streaming, est versionnable et évite les erreurs courantes.

Chiffrement AES Delphi avec header, Salt, IV et PBKDF2

Nous définissons un format conteneur simple qui peut également être utilisé dans des BLOBs de base de données ou des payloads de messages :

  • Magic : 4 octets, p. ex. NBAE (contrôle rapide « est‑ce notre format ?»)
  • Version : 1 octet (permet la migration)
  • Paramètres KDF : nombre d’itérations (4 octets)
  • Salt : 16 octets (aléatoire par fichier)
  • IV : 16 octets (aléatoire par fichier pour AES‑CBC)
  • Ciphertext : données utiles chiffrées (compatible streaming)

Important : Salt et IV ne sont pas secrets. Ils doivent seulement être nouveaux pour chaque chiffrement. Le mot de passe reste secret ; la clé dérivée de celui‑ci n’est pas stockée.

Chiffrement AES Delphi en flux : écrire/lire le conteneur

Le code est volontairement conçu comme un « plan » : fonctions clairement séparées, headers vérifiables, pas de variables globales cachées. Pour AES et PBKDF2, de nombreuses équipes utilisent une bibliothèque crypto éprouvée (p. ex. DEC). Le fragment illustre le format et le pattern de streaming ; les appels AES/PBKDF2 sont encapsulés de manière à pouvoir être remplacés selon la bibliothèque utilisée.

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

// --- Dépendances à implémenter selon la pile crypto ---

procedure FillRandomBytes(var B: TBytes);
begin
  // Pour l'aléa cryptographique : utiliser un CSPRNG du système (par ex. Windows BCryptGenRandom,
  // Linux getrandom/urandom). Ici intentionnellement placé en tant que placeholder.
  raise ENbCryptoError.Create('FillRandomBytes: CSPRNG non implémenté');
end;

function PBKDF2_HMAC_SHA256(const APassword: string; const ASalt: TBytes;
  const AIterations, AKeyLen: Cardinal): TBytes;
begin
  // Implémentation p.ex. avec DEC (PBKDF2) ou une autre bibliothèque.
  // Résultat : AKeyLen octets.
  raise ENbCryptoError.Create('PBKDF2_HMAC_SHA256: non implémenté');
end;

procedure AES256_CBC_EncryptStream(const AKey, AIV: TBytes; const AIn, AOut: TStream);
begin
  // Implémentation via une bibliothèque :
  // - KeyLen = 32 Bytes
  // - IVLen  = 16 Bytes
  // - PKCS#7 Padding
  // Important : traiter en mode flux, ne pas tout charger en mémoire.
  raise ENbCryptoError.Create('AES256_CBC_EncryptStream: non implémenté');
end;

procedure AES256_CBC_DecryptStream(const AKey, AIV: TBytes; const AIn, AOut: TStream);
begin
  raise ENbCryptoError.Create('AES256_CBC_DecryptStream: non implémenté');
end;

// --- Fonctions utilitaires ---

procedure WriteHeaderV1(const AOut: TStream; const H: TNbHeaderV1);
begin
  if AOut.Write(H, SizeOf(H)) <> SizeOf(H) then
    raise ENbCryptoError.Create('Impossible d''écrire l''en-tête');
end;

function ReadHeaderV1(const AIn: TStream): TNbHeaderV1;
var
  H: TNbHeaderV1;
begin
  if AIn.Read(H, SizeOf(H)) <> SizeOf(H) then
    raise ENbCryptoError.Create('En-tête 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('Conteneur non valide (Magic ne correspond pas)');

  if H.Version <> CVersion then
    raise ENbCryptoError.CreateFmt('Version de conteneur inconnue: %d', [H.Version]);

  if (H.Iterations < 10000) or (H.Iterations > 5000000) then
    raise ENbCryptoError.Create('Nombre d''itérations hors limites 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('Le mot de passe ne peut pas être vide');

  // Générer Salt et IV
  SetLength(Salt, CSaltLen);
  SetLength(IV, CIvLen);
  FillRandomBytes(Salt);
  FillRandomBytes(IV);

  // Remplir l'en-tête
  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);

  // Dériver la clé (32 octets pour AES-256)
  Key := PBKDF2_HMAC_SHA256(APassword, Salt, AIterations, 32);

  // Chiffrer les données utiles (le ciphertext suit directement l'en-tête)
  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('Le mot de passe ne peut pas être vide');

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

  // Déchiffrer à partir de la position actuelle du flux (après l'en-tête)
  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.

Objet : Un conteneur minimal adapté aux fichiers et aux BLOBs, incluant la gestion des versions et les paramètres KDF. Contraintes : Vous devez intégrer une véritable connexion CSPRNG (générateur de nombres pseudo-aléatoires cryptographiquement sûr fourni par le système d’exploitation) et une implémentation AES/PBKDF2 robuste en dessous. Écueils : N’utilisez pas n’importe quel Random (pas de Random()), pas d’IV fixes, et prévoyez un traitement d’erreur distinct au déchiffrement (mot de passe incorrect vs données corrompues). Variantes : plutôt que CBC, préférer AEAD (voir ci‑dessous), ou étendre l’en-tête avec un ID d’algorithme et un HMAC.

Intégrité : pourquoi AES-CBC seul est trop risqué en production

AES-CBC est encore présent dans de nombreux contextes legacy et peut fonctionner si vous utilisez en plus une garantie d’intégrité. Sans intégrité, un attaquant peut manipuler le texte chiffré ; même sans attaquant actif, des erreurs de transmission ou des couches de stockage défaillantes peuvent produire des erreurs de « padding » difficiles à diagnostiquer.

Options pragmatiques :

  • Encrypt-then-HMAC : Après le texte chiffré, écrire un HMAC (p. ex. HMAC-SHA-256) sur l’en-tête + le texte chiffré. À la lecture, vérifier d’abord le HMAC, puis déchiffrer. Pour cela, dérivez idéalement deux clés depuis PBKDF2 (par ex. 64 octets : 32 pour AES, 32 pour HMAC), au lieu de réutiliser la même clé deux fois.
  • AES-GCM : mode AEAD (Authenticated Encryption with Associated Data). Produit le texte chiffré + tag d’authentification. C’est aujourd’hui souvent le choix le plus propre si votre bibliothèque Delphi prend en charge GCM de manière stable. Les champs d’en-tête peuvent être authentifiés en tant que « AAD » sans que vous ayez à les chiffrer.

Si vous devez rester sur CBC (par ex. pour des raisons d’interopérabilité), Encrypt-then-HMAC est le complément robuste. Pour de nouveaux formats, GCM vaut la peine car il fournit l’authentification et rend les scénarios d’erreur plus clairs.

Particulièrement important : « hasard cryptographique » et pourquoi System.Hash ne suffit pas

Un réflexe legacy fréquent dans les projets Delphi : « On fait juste un SHA256 sur un horodatage + n’importe quoi et on a du Random. » Ce n’est pas une base fiable. Pour le salt et les IV, vous avez besoin d’un CSPRNG (Cryptographically Secure Pseudo Random Number Generator) du système d’exploitation. Sous Windows il s’agit typiquement de l’API BCrypt (CNG), sous Linux d’un générateur noyau comme getrandom() ou /dev/urandom. La différence est pratique : un CSPRNG est conçu pour qu’à partir de valeurs observées il soit impossible de prédire d’autres valeurs.

Astuce d’architecture : encapsulez cela dans une petite unité « RandomProvider » que vous pouvez mocker dans les tests. Ainsi vous traitez deux cas limites à la fois : des tests reproductibles (avec un seed fixe dans le mock) et une vraie sécurité en production (avec le CSPRNG du système). Cela évite qu’un hotfix n’introduise à nouveau « vite fait » un Random() parce que c’est plus rapide.

Débogage et migration legacy : la gestion de versions n’est pas un luxe

L’en-tête n’est pas seulement pour la « beauté cryptographique », mais pour la maintenabilité :

  • Réglage des itérations : les nombres d’itérations PBKDF2 évoluent au fil des années. Avec un champ d’en-tête vous pouvez augmenter ces valeurs plus tard, sans rendre les anciennes données illisibles.
  • Changement de format : la version 2 pourrait par ex. passer à AES-GCM ou ajouter un HMAC.
  • Diagnostic sur le terrain : Magic/Version permettent des vérifications rapides dans les logs et les outils, sans déchiffrer les données.

Conseil pratique : implémentez un petit „Inspector“, qui ne lit que l’en-tête (Magic/Version/Iterations) et l’écrit dans un Log. Ainsi vous résolvez de nombreux cas de support („Quelle version est ici ?“) sans gestion des mots de passe.

Migrer proprement : „Read old, write new“ au lieu du Big Bang

Lorsque vous remplacez un ancien format (p. ex. IV fixe, pas de KDF, Blowfish/3DES, ou XOR artisanal), un schéma s’est avéré efficace dans les projets Delphi : à la lecture vous reconnaissez plusieurs formats (Magic/Version ou heuristique de repli), à la écriture vous ne produisez que le nouveau format. En outre, vous pouvez, après un déchiffrement réussi, ré-encrypter en arrière-plan (« lazy migration ») si cela s’intègre au processus. Cela réduit le risque lors du déploiement et évite la maintenance « tout re-chiffrer en une fois » comme fenêtre d’intervention.

Threading und Streaming: typische Kanten in Delphi

Le chiffrement s’exécute souvent dans des threads worker (p. ex. lors d’un export, d’un upload vers un portail client, ou lors de l’écriture de gros archives). Deux points reviennent régulièrement dans les projets Delphi :

  • Positions des flux : avant le chiffrement/déchiffrement définissez des contrats clairs : le flux d’entrée (Input-Stream) est lu à partir de la position courante, le flux de sortie (Output-Stream) est écrit à partir de la position courante. En cas de réutilisation des streams, veillez à positionner explicitement Position := 0.
  • Pics mémoire : évitez de « tout charger en To ». L’approche par flux est essentielle pour les fichiers volumineux. Si votre bibliothèque crypto n’accepte que des tableaux d’octets, il vaut la peine de fournir l’effort supplémentaire pour passer à une implémentation compatible stream ou pour construire un adaptateur bufferisé.

Si vous chiffrez dans des services (Windows- ou Linux-Services), soignez également le logging des exceptions : « mot de passe incorrect », « header corrompu », « Tag/HMAC invalide » sont des cas d’exploitation différents et doivent être distinguables. Important : les messages d’erreur ne doivent pas être trop détaillés vers l’extérieur (pas de « padding incorrect dans le bloc 7 » comme erreur d’API), mais peuvent l’être dans le log interne.

Quand l’approche vaut la peine – et où elle peut basculer

Pertinent si vous : (a) stockez de manière durable des données d’export/import chiffrées, (b) exploitez plusieurs versions du programme en parallèle, (c) traitez les données sous forme de flux, ou (d) avez besoin d’une interface crypto propre pour plusieurs modules (Client/Server/Tooling).

Défaillit si vous tentez d’en faire « la » solution à tout : le transport relève de TLS, pas d’un wrapper AES maison. Pour les secrets (mots de passe, tokens), un OS-Secret-Store ou un vault est souvent plus adapté. Et si vous devez assurer l’interopérabilité avec d’autres langages, documentez précisément le header, l’endianness et l’encodage (ou utilisez un format établi).

Conclusion : AES in Delphi est moins un algorithme, plus de l’ingénierie

Le véritable gain de cet extrait n’est pas « AES qui fonctionne », mais un format opérationnel : salt et IV aléatoires, header versionné, paramètres PBKDF2 dans le payload et traitement compatible flux. Pour les nouveaux formats, ajoutez de préférence l’intégrité (AES-GCM ou Encrypt-then-HMAC). Ainsi, « on chiffre n’importe quoi » devient un composant qui RESTe maintenable et migrable dans des solutions d’entreprise numériques, même après des années.

Si vous devez intégrer un tel conteneur dans un Delphi-paysage établi ou migrer proprement depuis un format hérité, un bref contrôle d’architecture (gestion des clés, versions de format, exploitation/journalisation) s’impose. Nous clarifions les détails, si nécessaire, lors d’un entretien :

Dans le contexte métier, Delphi Aes et Pbkdf2 Delphi jouent également un rôle important lorsque les intégrations, les flux de données et l’évolution doivent s’articuler proprement.

Discuter d’un projet ou d’une initiative de modernisation avec Net-Base.

Étape suivante

Lorsque ce sujet devient un projet concret, l'architecture, l'existant et l'exploitation doivent être examinés ensemble dès le départ.

Nous n'intervenons pas seulement sur des questions ponctuelles, mais aussi lorsque des fragments de code source, des problématiques liées aux systèmes legacy ou des concepts de portail doivent se transformer en un projet d'entreprise robuste.

  • L'état des lieux, l'état cible et les risques techniques sont évalués conjointement.
  • REST, l'accès aux données, les portails et le déploiement ne sont pas repoussés en tant que conséquences ultérieures.
  • Vous identifiez tôt quelle voie est viable sur le plan économique et opérationnel.

Partager l'article

Partager directement cette publication

LinkedIn, X, XING, Facebook, WhatsApp et e-mail sont immédiatement disponibles. Pour Instagram, nous préparons directement le lien et un court texte.

Courriel

Instagram s'ouvre dans un nouvel onglet. Le lien et le court texte sont préalablement copiés dans le presse-papiers.