Aller au contenu

Transformer mots de passe et secrets applicatifs en clés

Transformer mots de passe et secrets applicatifs en clés

Section intitulée « Transformer mots de passe et secrets applicatifs en clés »

Un mot de passe, une clé, un jeton d’accès et un secret d’application se ressemblent dans un fichier de configuration : quelques dizaines de caractères qu’il ne faut montrer à personne. Ils n’ont pourtant ni la même origine, ni la même quantité de hasard, ni le même traitement. Cette fiche explique ce qui les distingue, comment une KDF (Key Derivation Function, fonction de dérivation de clé) fabrique des clés à partir d’un secret machine, comment une fonction de hachage de mots de passe range un secret humain sans le conserver, et comment vivent les secrets des applications.

→ Lire l’explication complète de la marmotte

L’entropie mesure, en bits, le nombre de tentatives qu’un attaquant doit faire dans le pire des cas : n bits d’entropie signifient 2^n possibilités. Une clé AES (Advanced Encryption Standard) de 128 bits tirée par un générateur cryptographique en offre 2^128, un nombre qu’aucun parc de machines terrestre ne parcourra. Un mot de passe de huit lettres minuscules tirées au hasard offre 26^8, soit environ 2^37,6 : une seule carte graphique en vient à bout en peu de temps si la fonction de stockage est rapide. Et un mot de passe choisi par un humain offre beaucoup moins, car les humains choisissent Marmotte2024! bien plus souvent que qzkvwpth. Les dictionnaires de fuites et les règles de mutation (majuscule initiale, année, point d’exclamation) épuisent ces choix dans l’ordre de leur probabilité.

La conséquence est structurante : un mot de passe ne peut jamais servir directement de clé, et aucune transformation ne lui ajoute d’entropie. Elle peut seulement rendre chaque essai plus coûteux. Une clé, elle, se produit avec le générateur du système, comme le détaille la fiche produire un aléa utilisable.

Un token (jeton) est une valeur émise par un système pour représenter une autorisation : jeton de session, jeton d’API (Application Programming Interface), lien de réinitialisation. Il doit être aléatoire et assez long, par exemple 32 octets produits par secrets.token_urlsafe(32) en Python, et il se stocke côté serveur sous forme de condensat rapide, puisque son entropie rend la force brute inutile.

Un secret applicatif est une valeur qu’une application présente à une autre : mot de passe de base de données, clé d’API d’un fournisseur, clé de signature. Il n’est saisi par personne et doit pourtant être distribué, stocké et renouvelé.

Le sel est une valeur aléatoire, unique par compte, ajoutée au mot de passe avant le hachage et stockée en clair avec le résultat. Il n’est pas secret : son rôle est d’empêcher qu’un même mot de passe donne le même condensat sur deux comptes, et donc d’empêcher les tables précalculées et l’attaque groupée de toute la base. Le pepper est au contraire un secret unique pour toute l’application, conservé hors de la base : dans un coffre, une variable injectée au démarrage ou un module matériel. Une base volée sans le pepper ne permet aucun essai hors ligne.

Quand le secret d’entrée est déjà fort, une clé de session issue d’un échange Diffie-Hellman ou une clé maîtresse aléatoire, il n’y a rien à ralentir : il faut seulement le mettre en forme. HKDF (HMAC-based Key Derivation Function, RFC 5869, Request for Comments) le fait en deux étapes construites sur HMAC (Hash-based Message Authentication Code).

L’extraction concentre l’entropie d’une entrée éventuellement mal répartie en une clé pseudo-aléatoire de taille fixe : PRK = HMAC(sel, IKM), où IKM désigne le matériau d’entrée. L’expansion produit ensuite autant de sous-clés que nécessaire, chacune liée à une chaîne de contexte appelée info : OKM = Expand(PRK, info, L), dans la limite de 255 fois la taille du condensat (8 160 octets avec SHA-256, Secure Hash Algorithm). Le paramètre info est la clé de voûte de la séparation d’usage : deux libellés différents donnent deux clés indépendantes, et la compromission de l’une ne dit rien de l’autre.

L’atelier consiste à dériver deux sous-clés d’une même clé maîtresse :

import os
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
maitre = os.urandom(32)
def derive(info: bytes) -> bytes:
return HKDF(algorithm=hashes.SHA256(), length=32,
salt=None, info=info).derive(maitre)
k_chiffre = derive(b"tortuetech/v1/fichiers/chiffrement")
k_mac = derive(b"tortuetech/v1/fichiers/authentification")
assert k_chiffre != k_mac

Un objet HKDF de la bibliothèque cryptography ne sert qu’une fois, d’où la petite fonction. Les libellés gagnent à contenir l’application, la version et l’usage : changer v1 en v2 produit une nouvelle famille de clés sans toucher à la clé maîtresse. Les chaînes de dérivation prolongent l’idée, une clé par locataire dérivée de la clé maîtresse, puis une clé par fichier dérivée de la clé du locataire, à condition de documenter l’arbre : une sous-clé dont personne ne sait plus de quoi elle descend est une sous-clé qu’on n’ose plus faire tourner.

HKDF n’est en revanche pas une fonction de stockage de mots de passe. Elle est rapide par conception, et appliquée à un mot de passe elle offrirait à l’attaquant des milliards d’essais par seconde.

Un serveur n’a pas besoin de connaître le mot de passe, seulement de vérifier qu’on le lui redonne. Il le passe donc dans une fonction volontairement coûteuse, salée, et ne garde que le résultat. Le coût doit pénaliser l’attaquant bien plus que le serveur : le serveur calcule un hachage par connexion, l’attaquant en calcule des milliards.

Argon2id, lauréat de la PHC (Password Hashing Competition) en 2015 et décrit par la RFC 9106 (2021), est le choix par défaut. Il exige de la mémoire autant que du temps processeur, ce qui neutralise une grande partie de l’avantage des cartes graphiques et des circuits spécialisés. Ses trois paramètres sont la mémoire m en Kio, le nombre de passes t et le parallélisme p. L’OWASP (Open Worldwide Application Security Project) recommande au minimum m=19456 (19 Mio), t=2, p=1, ou des combinaisons équivalentes avec plus de mémoire et moins de passes. La RFC 9106 propose, quand la mémoire est disponible, t=1, p=4, m=2 Gio, et à défaut t=3, p=4, m=64 Mio. Le bon réglage se mesure : viser quelques centaines de millisecondes par vérification sur le matériel de production, en tenant compte du nombre de connexions simultanées, car 64 Mio multipliés par 200 connexions font 12,5 Gio.

scrypt (RFC 7914) est lui aussi gourmand en mémoire ; l’OWASP cite N=2^17, r=8, p=1. bcrypt, conçu en 1999, ne consomme que 4 Kio mais reste acceptable avec un facteur de coût d’au moins 10 ; il tronque silencieusement l’entrée à 72 octets, piège qui ressort dès qu’on lui fournit une phrase de passe ou un pepper concaténé. PBKDF2 (Password-Based Key Derivation Function 2) n’a que le temps comme levier ; il s’impose quand une conformité FIPS (Federal Information Processing Standards) est exigée, avec au moins 600 000 itérations en HMAC-SHA-256 selon l’OWASP.

Le résultat se stocke dans un format autodescriptif, qui garde l’algorithme et les paramètres à côté du sel et du condensat :

$argon2id$v=19$m=19456,t=2,p=1$c2VsYWxlYXRvaXJlMTY$<condensat en base64>

Avec la bibliothèque Python argon2-cffi, hachage, vérification et migration tiennent en quelques lignes :

from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
ph = PasswordHasher(time_cost=2, memory_cost=19456, parallelism=1)
def connexion(compte, mot_de_passe):
try:
ph.verify(compte.hash, mot_de_passe)
except VerifyMismatchError:
return False
if ph.check_needs_rehash(compte.hash):
compte.hash = ph.hash(mot_de_passe)
compte.save()
return True

La migration au login exploite le seul moment où le serveur tient le mot de passe en clair : après une vérification réussie, si les paramètres stockés sont plus faibles que la politique courante, il recalcule et remplace. Les comptes dormants ne migrent jamais par ce chemin ; pour une base héritée en MD5 (Message Digest 5) ou en SHA-1 sans sel, on enveloppe immédiatement chaque ancien condensat dans Argon2id, argon2id(md5(mdp)), puis on remplace l’enveloppe par un hachage direct à la connexion suivante. La base cesse ainsi d’être fragile le jour même, sans attendre le retour des utilisateurs partis.

La politique de rotation ne concerne plus le mot de passe de l’utilisateur, dont le changement périodique forcé n’est plus recommandé en l’absence de compromission, mais les paramètres : les revoir quand le matériel évolue, et le pepper, dont la rotation suppose de garder un identifiant de version à côté de chaque condensat.

Le code source et l’image de conteneur sont exclus : un secret qui y entre en ressort par chaque copie, chaque fork et chaque cache de compilation (voir prévention des secrets dans le code source). Les variables d’environnement sont un moindre mal plutôt qu’une solution : elles sont héritées par tous les processus enfants, lisibles dans /proc/<pid>/environ par le même utilisateur et par root, et recopiées avec entrain par les rapports de plantage et les pages de diagnostic. Un coffre (gestionnaire de secrets d’un fournisseur de nuage, Vault ou équivalent) centralise le stockage chiffré, délivre les secrets à l’exécution à une identité authentifiée, et journalise chaque lecture. L’architecture d’ensemble est décrite dans gestion des secrets en architecture distribuée.

La rotation n’est sûre que si deux versions peuvent coexister : le fournisseur accepte l’ancienne et la nouvelle valeur, les consommateurs basculent, puis l’ancienne est révoquée. Un secret qu’on ne sait pas faire tourner sans interruption ne tournera pas, et finira par avoir l’âge du projet. Les secrets dynamiques, créés à la demande avec une durée de vie de quelques heures, suppriment le problème à la racine pour les bases de données et les comptes de nuage. L’audit répond à la question qu’on se pose toujours trop tard : qui a lu ce secret, quand, depuis où. L’accès minimal limite chaque identité aux seuls secrets de son service, et sépare strictement production et développement. La fiche exploiter les clés et leur rotation détaille le cycle de vie.

Les journaux sont le canal de fuite le plus banal : un en-tête Authorization affiché en mode débogage, une chaîne de connexion dans une trace d’exception, un corps de requête de connexion journalisé en entier. L’atelier consiste à chercher ces motifs dans les journaux d’une campagne de tests :

Fenêtre de terminal
grep -rEn 'AKIA[0-9A-Z]{16}|eyJ[A-Za-z0-9_-]+\.eyJ|Authorization: (Bearer|Basic)|password=' logs/

Le motif AKIA suivi de 16 caractères correspond aux identifiants de clés d’accès AWS (Amazon Web Services), eyJ au début encodé de tout JWT (JSON Web Token). Un résultat positif appelle trois gestes : révoquer et remplacer le secret, purger les copies du journal, et corriger le code qui l’a écrit, en ajoutant un filtre de masquage au niveau du journaliseur plutôt qu’au cas par cas.

Chiffrer les mots de passe au lieu de les hacher les rend tous lisibles à quiconque obtient la clé, qui se trouve en général sur le même serveur. Un sel fixe pour toute la base n’est qu’un pepper stocké au mauvais endroit, et rend à nouveau visibles les mots de passe partagés. Stocker le pepper dans la base qu’il est censé compléter revient à cacher la clé sous le paillasson de la porte qu’elle ouvre. Copier un secret de production en développement étend son exposition à des postes, des journaux et des personnes que personne n’a audités.

KDF et password hashing partagent un nom de famille, pas un métier : HKDF met en forme un secret déjà fort, rapidement ; Argon2id protège un secret faible, lentement. Sel et nonce sont tous deux publics et uniques, mais le sel distingue des condensats, le nonce distingue des chiffrements sous une même clé. Pepper et clé conservée dans un HSM (Hardware Security Module, module matériel de sécurité) diffèrent par l’exposition : le pepper est généralement chargé en mémoire par l’application, la clé de HSM n’en sort jamais, et un pepper réalisé comme un HMAC calculé dans le HSM combine les deux. Token opaque et JWT s’opposent par leur lecture : le premier est une référence aléatoire que le serveur vérifie dans sa base et peut révoquer instantanément ; le second porte ses propres affirmations, signées (voir JWS), et reste valable jusqu’à son expiration sauf mécanisme de révocation ajouté.

Un mot de passe chiffré plutôt que haché tombe en bloc avec sa clé : une seule fuite de configuration rend lisible l’ensemble des comptes, anciens compris. Avec un sel fixe, deux utilisateurs au même mot de passe partagent le même condensat, ce qui désigne d’emblée les mots de passe les plus courants dans une base volée. Sans séparation de contexte, une même clé dérivée sert à chiffrer et à authentifier, si bien que la compromission d’un usage emporte l’autre. Côté applications, un secret copié dans un environnement de développement ou dans un journal de test reste valable jusqu’à la prochaine rotation, et sans audit des accès, nul ne peut dire qui l’a lu entre-temps.

Un mot de passe choisi par une personne contient peu de hasard : il est court et prévisible. Une clé de chiffrement, au contraire, doit être une longue suite de bits tirés au hasard. Pour passer de l’un à l’autre, on utilise une fonction de dérivation de clé, c’est-à-dire un calcul qui transforme une entrée en clé de la bonne taille. Pour stocker un mot de passe, un site ne le chiffre pas, car toute personne qui obtiendrait la clé retrouverait tous les mots de passe. Il le passe dans une fonction volontairement lente et gourmande en mémoire, par exemple Argon2id ou bcrypt, après y avoir ajouté un sel : une valeur aléatoire différente pour chaque compte, stockée avec le résultat. Le sel empêche de casser tous les comptes d’un coup. Un pepper, valeur secrète commune conservée hors de la base, oblige en plus l’attaquant à voler deux choses. Quand les réglages de lenteur augmentent, le site recalcule le résultat à la connexion suivante, puisqu’il a alors le mot de passe sous la main. Pour les programmes, qui partent déjà d’une clé aléatoire, une fonction de dérivation rapide suffit : elle extrait le hasard puis fabrique plusieurs sous-clés distinctes à partir d’étiquettes différentes, une par usage. Les secrets des applications se rangent dans un coffre dédié, avec des accès minimaux, une rotation régulière, et jamais dans les journaux.

À lire aussi

Pages voisines

: les deux pages se citent l’une l’autre.