Chiffrement symétrique

Le chiffrement symétrique utilise la même clé pour chiffrer et déchiffrer. L'idée la plus importante de cette page est la différence entre un chiffrement simple et un chiffrement AEAD (Authenticated Encryption with Associated Data). AES-CBC ou AES-CTR seuls n'apportent que la confidentialité et rien d'autre : un attaquant incapable de lire le texte en clair peut tout de même inverser des bits dans le texte chiffré, et le destinataire déchiffrera le résultat altéré sans aucune indication que quelque chose ne va pas. AES-GCM et ChaCha20-Poly1305 authentifient aussi bien qu'ils chiffrent, de sorte que toute altération est détectée et le déchiffrement échoue nettement. Privilégiez un chiffrement AEAD à moins qu'un besoin spécifique n'impose un mode simple.

Toutes les fonctions résident dans sgcCrypto_AES, sgcCrypto_Modes, sgcCrypto_ChaCha et sgcCrypto_Poly1305. Ne réutilisez jamais un IV ou un nonce avec la même clé : pour CTR et les chiffrements AEAD, la réutilisation ne fait pas qu'affaiblir la confidentialité, pour GCM en particulier elle rompt purement et simplement la garantie d'authentification. Générez-en un nouveau avec sgcRandomBytes pour chaque message.

AES-GCM (recommandé)

Le mode AEAD utilisé par défaut par la plupart des protocoles. Le nonce fait 12 octets ; générez-le aléatoirement pour chaque message. aAAD est authentifié mais non chiffré, ce qui permet de lier un texte chiffré à son contexte, par exemple un identifiant de message ou une version de protocole, sans cacher ce contexte à quiconque peut voir le texte chiffré.


function sgcAES_GCM_Encrypt(const aKey, aIV, aPlain, aAAD: TBytes; out aTag: TBytes): TBytes;
function sgcAES_GCM_Decrypt(const aKey, aIV, aCipher, aAAD, aTag: TBytes; out aPlain: TBytes): Boolean;

var
  oKey, oIV, oCipher, oTag, oPlain, oAAD: TBytes;
begin
  oIV := sgcRandomBytes(12);
  oCipher := sgcAES_GCM_Encrypt(oKey, oIV, oPlain, oAAD, oTag);
  { send oIV, oCipher and oTag to the recipient }
  if not sgcAES_GCM_Decrypt(oKey, oIV, oCipher, oAAD, oTag, oPlain) then
    { the ciphertext, the tag or the AAD was altered: reject it };
end;

ChaCha20-Poly1305 et XChaCha20-Poly1305

La paire AEAD de la RFC 8439. Là où AES-GCM s'appuie sur l'accélération matérielle AES, ChaCha20-Poly1305 fonctionne à vitesse constante en logiciel pur, ce qui explique pourquoi TLS et WireGuard l'utilisent par défaut sur les appareils mobiles et embarqués dépourvus d'AES-NI. Le nonce fait 12 octets, même restriction que pour GCM. XChaCha20-Poly1305 étend le nonce à 24 octets, suffisamment grand pour être choisi aléatoirement pendant toute la durée de vie d'une clé sans avoir à suivre un compteur, ce qui importe lorsque de nombreux rédacteurs indépendants partagent une même clé et ne peuvent pas coordonner un compteur entre eux.


function sgcChaCha20Poly1305_Encrypt(const aKey, aNonce, aPlain, aAAD: TBytes; out aTag: TBytes): TBytes;
function sgcChaCha20Poly1305_Decrypt(const aKey, aNonce, aCipher, aAAD, aTag: TBytes; out aPlain: TBytes): Boolean;
function sgcXChaCha20Poly1305_Encrypt(const aKey, aNonce, aPlain, aAAD: TBytes; out aTag: TBytes): TBytes;
function sgcXChaCha20Poly1305_Decrypt(const aKey, aNonce, aCipher, aAAD, aTag: TBytes; out aPlain: TBytes): Boolean;

AES-CCM

Counter with CBC-MAC, RFC 3610 et NIST SP 800-38C. C'est l'AEAD que privilégient les piles contraintes et l'IoT, car il ne nécessite que le chiffrement par bloc lui-même et aucune table GHASH : Zigbee, 802.15.4, Bluetooth et les suites TLS CCM le spécifient tous. Là où la place ne manque pas, GCM reste le meilleur choix, puisque CCM effectue deux passes sur les données et ne peut pas être pipeliné.

Le nonce fait de 7 à 13 octets et sa longueur n'est pas un choix libre : 15 moins la longueur du nonce donne la taille du champ de longueur, ce qui fixe la taille maximale du message, et un nonce de 13 octets plafonne le texte en clair à 65535 octets. Un nonce ne doit jamais se répéter sous une même clé, exactement comme pour GCM. aTagLen vaut 4, 6, 8, 10, 12, 14 ou 16 octets.


function sgcAES_CCM_Encrypt(const aKey, aNonce, aPlain, aAAD: TBytes; aTagLen: Integer; out aTag: TBytes): TBytes;
function sgcAES_CCM_Decrypt(const aKey, aNonce, aCipher, aAAD, aTag: TBytes; out aPlain: TBytes): Boolean;

var
  oKey, oNonce, oCipher, oTag, oPlain, oAAD: TBytes;
begin
  oNonce := sgcRandomBytes(12);
  oCipher := sgcAES_CCM_Encrypt(oKey, oNonce, oPlain, oAAD, 16, oTag);
  { send oNonce, oCipher and oTag to the recipient }
  if not sgcAES_CCM_Decrypt(oKey, oNonce, oCipher, oAAD, oTag, oPlain) then
    { the ciphertext, the tag or the AAD was altered: reject it };
end;

AES-GMAC

GMAC est exactement AES-GCM exécuté avec un texte en clair vide et le message entier passé en donnée additionnelle, c'est ainsi que la norme le définit. Il hérite donc de toutes les règles de nonce de GCM : aIV fait normalement 12 octets et ne doit jamais se répéter sous une même clé. Réutiliser un IV avec GMAC ne se contente pas de l'affaiblir, cela divulgue la sous-clé de hachage et permet à un attaquant de forger des tags directement, la défaillance est donc totale plutôt que progressive. AES-CMAC, sur la page Hachage & authentification des messages, ne nécessite aucun IV et constitue le choix le plus sûr à moins que GCM ne soit déjà présent dans le protocole. Le tag fait 16 octets.


function sgcAES_GMAC(const aKey, aIV, aData: TBytes): TBytes;
function sgcAES_GMAC_Verify(const aKey, aIV, aData, aTag: TBytes): Boolean;

Modes de bloc AES

Ceux-ci n'apportent que la confidentialité, sans authentification : associez-les à un MAC de la page Hachage & authentification des messages si le texte en clair nécessite l'intégrité, ou utilisez plutôt AES-GCM ci-dessus.


function sgcAES_CBC_Encrypt(const aKey, aIV, aPlain: TBytes): TBytes;
function sgcAES_CBC_Decrypt(const aKey, aIV, aCipher: TBytes): TBytes;
function sgcAES_CTR(const aKey, aIV, aData: TBytes): TBytes;
function sgcAES_CFB_Encrypt(const aKey, aIV, aPlain: TBytes): TBytes;
function sgcAES_CFB_Decrypt(const aKey, aIV, aCipher: TBytes): TBytes;
function sgcAES_OFB(const aKey, aIV, aData: TBytes): TBytes;
function sgcAES_ECB_Encrypt(const aKey, aPlain: TBytes): TBytes;
function sgcAES_ECB_Decrypt(const aKey, aCipher: TBytes): TBytes;

sgcAES_CTR et sgcAES_OFB sont leur propre inverse : appelez à nouveau la même fonction avec la même clé et le même IV pour déchiffrer. ECB n'est inclus que pour interopérer avec d'anciens formats qui le spécifient ; des blocs de texte en clair identiques produisent des blocs de texte chiffré identiques, ce qui laisse fuir la structure des données, il ne doit donc pas être choisi pour quoi que ce soit de nouveau.

CBC est également disponible sans aucun remplissage, le mode que Java écrit AES/CBC/NoPadding. La longueur de l'entrée doit être un multiple exact de 16, et une exception est levée si ce n'est pas le cas, plutôt que l'appel ne remplisse silencieusement les données et ne produise un texte chiffré que l'autre partie ne peut pas lire. Ce n'est sûr que là où le protocole garantit déjà des données alignées sur la taille de bloc.


function sgcAES_CBC_Encrypt_NoPad(const aKey, aIV, aPlain: TBytes): TBytes;
function sgcAES_CBC_Decrypt_NoPad(const aKey, aIV, aCipher: TBytes): TBytes;

Le vol de texte chiffré (ciphertext stealing) évite le surcoût de remplissage de CBC pour des données qui ne sont pas un multiple de la taille de bloc :


function sgcAES_CTS_Encrypt(const aKey, aIV, aPlain: TBytes): TBytes;
function sgcAES_CTS_Decrypt(const aKey, aIV, aCipher: TBytes): TBytes;

Schémas de remplissage

Un mode de bloc exige que le texte en clair soit un multiple de 16 octets, et le schéma de remplissage est ce qui permet d'y parvenir. PKCS#7 est le seul bon choix pour quelque chose de nouveau ; les six autres existent pour lire et écrire des données provenant d'un système qui a déjà fait un autre choix. padPKCS5 ne figure pas dans la liste car, pour un bloc de 16 octets, il est identique à PKCS#7, les deux noms ne différant que par les tailles de bloc pour lesquelles ils ont été définis.


type
  TsgcPadding = (padNone, padPKCS7, padZero, padANSIX923, padISO7816_4,
    padISO10126_2, padTBC);

function sgcPad_Add(const aData: TBytes; aBlockSize: Integer; aPadding: TsgcPadding): TBytes;
function sgcPad_Remove(const aData: TBytes; aBlockSize: Integer; aPadding: TsgcPadding; out aPlain: TBytes): Boolean;

function sgcAES_CBC_EncryptPad(const aKey, aIV, aPlain: TBytes; aPadding: TsgcPadding): TBytes;
function sgcAES_CBC_DecryptPad(const aKey, aIV, aCipher: TBytes; aPadding: TsgcPadding): TBytes;
function sgcAES_ECB_EncryptPad(const aKey, aPlain: TBytes; aPadding: TsgcPadding): TBytes;
function sgcAES_ECB_DecryptPad(const aKey, aCipher: TBytes; aPadding: TsgcPadding): TBytes;

var
  oKey, oIV, oPlain, oCipher: TBytes;
begin
  oCipher := sgcAES_CBC_EncryptPad(oKey, oIV, oPlain, padANSIX923);
end;

sgcAES_CBC_Encrypt et sgcAES_ECB_Encrypt, plus haut, en sont le raccourci PKCS#7. sgcPad_Remove renvoie False sur un remplissage malformé plutôt que de lever une exception, et emprunte le même chemin pour chaque type d'échec, car les distinguer est précisément ce dont un oracle de remplissage a besoin. Faites particulièrement attention à padZero : il ne peut pas distinguer un octet de remplissage d'un zéro final légitime du texte en clair, il supprime donc tous les zéros finaux, et des données pouvant légitimement se terminer par un zéro reviennent tronquées.

Accélération matérielle AES (AES-NI)

Sur les builds Windows Win32 et Win64 réalisés avec Delphi, sgcCrypto_AES vérifie le processeur au moment de l'exécution et, lorsqu'il signale AES-NI, PCLMULQDQ et SSSE3, exécute le chiffrement par bloc unique, AES-CTR, AES-GCM (le mode compteur et GHASH) et le chiffrement AES-CBC sur ces instructions, qui sont en temps constant par construction. Sans elles, et sous Linux64, macOS, iOS et Android, le noyau logiciel en temps constant est utilisé comme auparavant. Le déchiffrement CBC utilise toujours le noyau logiciel. Le moteur TLS natif en profite automatiquement.

sgcAES_HardwareAvailable renvoie True lorsque ce build embarque le chemin matériel et que le processeur le prend en charge. La variable globale sgcAES_UseHardware, True par défaut, réglée sur False force le noyau logiciel, par exemple pour comparer les deux. Définissez-la une seule fois au démarrage.

var sgcAES_UseHardware: Boolean = True;
function sgcAES_HardwareAvailable: Boolean;

if sgcAES_HardwareAvailable then
  Memo1.Lines.Add('AES-NI in use');

AES Key Wrap

L'encapsulation de clé protège une clé, pas un message : elle ne nécessite pas d'IV, elle est déterministe, et elle authentifie la clé encapsulée. C'est la méthode standard pour stocker une clé de chiffrement de données sous une clé maîtresse, par exemple une clé maîtresse HSM ou KMS encapsulant une clé par fichier. La RFC 3394 exige que la clé encapsulée soit un multiple de 8 octets ; la RFC 5649 ajoute un remplissage pour des clés de longueur quelconque.


function sgcAES_KeyWrap(const aKEK, aPlainKey: TBytes): TBytes;
function sgcAES_KeyUnwrap(const aKEK, aWrapped: TBytes; out aPlainKey: TBytes): Boolean;
function sgcAES_KeyWrapPad(const aKEK, aPlainKey: TBytes): TBytes;
function sgcAES_KeyUnwrapPad(const aKEK, aWrapped: TBytes; out aPlainKey: TBytes): Boolean;

ChaCha20 et Salsa20 (chiffrements de flux bruts)

Les chiffrements de flux derrière les constructions AEAD ci-dessus, fournis directement pour les protocoles qui spécifient le chiffrement brut. Aucun des deux n'authentifie : presque toujours, les versions AEAD Poly1305 plus haut sur cette page sont ce qu'il vous faut à la place. Salsa20 est le prédécesseur de ChaCha20, conservé pour la compatibilité avec les formats de l'ère NaCl ; choisissez ChaCha20 pour tout ce qui est nouveau.


function sgcChaCha20(const aKey, aNonce, aData: TBytes; aCounter: Cardinal): TBytes;
function sgcXChaCha20(const aKey, aNonce, aData: TBytes; aCounter: Cardinal): TBytes;
function sgcSalsa20(const aKey, aNonce, aData: TBytes; aCounter: Int64): TBytes;
function sgcXSalsa20(const aKey, aNonce, aData: TBytes; aCounter: Int64): TBytes;

Hérité : DES et NTLM

sgcCrypto_Legacy fournit également le DES simple en mode ECB et la dérivation de clé NTLM v1 basée sur DES. DES a une clé effective de 56 bits, cassable par force brute sur du matériel moderne en quelques heures ; il n'existe que pour interopérer avec des protocoles qui le spécifient par son nom (l'authentification NTLM en étant la raison habituelle). Ne le choisissez pas pour quoi que ce soit de nouveau ; utilisez AES à la place.


function sgcDES_EncryptECB(const aKey, aData: TBytes): TBytes;
function sgcDES_DecryptECB(const aKey, aData: TBytes): TBytes;
function sgcDES_NTLM7to8(const aKey7: TBytes): TBytes;