Générer correctement une clé secrète d'application
Publié le 12/08/2026 · 14 min de lecture · Outils texte & langage
Daniel Okonkwo — Développeur front-end et rédacteur Tech chez OneKitly
Performance web · Formats de fichiers
Vérifié à partir de 4 sources
Trois questions tranchent. Un : quelle source aléatoire l'outil appelle-t-il ? Un générateur solide appelle l'aléa cryptographique de la plateforme — crypto.getRandomValues dans un navigateur, secrets en Python, random_bytes en PHP. Un générateur douteux appelle Math.random, documenté comme impropre à la sécurité : V8 l'implémente avec xorshift128+, et quelques sorties consécutives suffisent à reconstituer l'état interne et à prédire la suite. Cet outil passe : son randStr appelle rngInt, qui remplit un Uint32Array avec crypto.getRandomValues ; la branche Math.random ne s'exécute que si crypto est absent, ce qu'aucun navigateur n'est. Deux : combien d'entropie la sortie porte-t-elle ? Multiplie la longueur par le logarithme en base deux de la taille de l'alphabet. Le jeu Django est ici exactement les cinquante caractères qu'utilise le get_random_secret_key de Django — minuscules, chiffres et !@#$%^&*(-_=+) — donc une clé de 50 caractères porte 50 × log2(50) = 282 bits, contre les quelque 128 bits pris d'ordinaire comme plancher. L'option alphanumérique donne 297 bits. Le volet WordPress génère les huit constantes de wp-config sur 64 caractères tirés d'un jeu de 92, qui est octet pour octet wp_generate_password avec ses deux drapeaux de caractères spéciaux : 418 bits chacune. Trois : où va la clé ensuite ? Aucune des deux réponses ne sert une fois qu'elle a été collée dans une conversation, un ticket ou un commit — un secret partagé est un secret dépensé, et le seul remède est d'en générer un nouveau et de faire la rotation.
Comment distinguer un générateur de clé secrète solide d'un générateur douteux, avec celui-ci comme exemple travaillé : quelle source aléatoire il appelle, comment calculer l'entropie soi-même, et ce qui casse vraiment le jour où l'on change la clé.
Première question : quelle source aléatoire
Chaque langage possède deux générateurs de nombres aléatoires, et ils ne sont pas interchangeables. L'un est rapide, déterministe à partir d'une graine, et destiné à mélanger une playlist ou à décaler une nouvelle tentative. L'autre est plus lent, puise dans la réserve d'entropie du système, et sert à tout ce qu'un attaquant aimerait deviner. En JavaScript ce sont Math.random et crypto.getRandomValues ; en Python, random et secrets ; en PHP, mt_rand et random_bytes. Employer le premier là où le second s'impose est la façon la plus courante de rater un générateur de clés.
Ce qui disqualifie Math.random n'est pas que sa sortie paraisse régulière — elle ne l'est pas. C'est que le générateur est une petite machine déterministe dont on peut retrouver l'état. V8, le moteur de Chrome et de Node, utilise xorshift128+, un état de 128 bits avancé par des décalages et des ou-exclusifs. À partir d'une courte suite de sorties consécutives, cet état se reconstitue, et dès lors toutes les valeurs futures sont connues et toutes les passées rejouables. Le moteur n'a d'ailleurs aucune obligation de l'amorcer de manière imprévisible, et les navigateurs l'ont historiquement amorcé à partir de choses qu'un attaquant peut observer ou influencer.
Cet outil appelle le bon. En lisant le code depuis le bouton vers l'intérieur : le gestionnaire de génération construit une chaîne caractère par caractère avec randStr, randStr choisit chaque index avec rngInt, et rngInt alloue un Uint32Array d'un élément et le remplit avec crypto.getRandomValues. Il existe bien un repli sur Math.random, mais il est derrière un test d'existence de crypto et de crypto.getRandomValues, et getRandomValues est disponible dans tout contexte de navigation, y compris non sécurisé — contrairement à crypto.subtle. En pratique, ce repli est du code inatteignable.
Deuxième question : combien de bits, et comment les compter soi-même
L'arithmétique tient sur une ligne. Si chaque caractère est tiré indépendamment et uniformément d'un alphabet de n caractères, chaque caractère porte log2(n) bits, et une clé de longueur L porte L × log2(n). C'est tout. Il n'y a pas de correction pour « contient des symboles » ou « a l'air compliqué » : la taille de l'alphabet et la longueur sont les seules entrées, ce qui explique qu'une longue clé en minuscules batte une clé courte pleine de ponctuation.
Applique-la au réglage par défaut. Le jeu Django est composé des vingt-six minuscules, des dix chiffres et des quatorze caractères !@#$%^&*(-_=+) — cinquante au total, sans doublon, ce qui compte car un caractère répété rétrécirait discrètement l'alphabet effectif. log2(50) vaut 5,644 bits par caractère, donc la clé de 50 caractères par défaut porte 282,2 bits. Passer à l'option alphanumérique porte l'alphabet à soixante-deux et la clé à 297,7 bits ; l'option complète le porte à soixante-seize et 312,4 bits. Les trois sont énormes à côté des 128 bits couramment cités comme suffisants — vingt-trois caractères du jeu Django les dépasseraient déjà.
Le volet WordPress se mesure pareil. Il émet les huit constantes de wp-config — les quatre clés et les quatre sels — de 64 caractères chacune, tirées d'un jeu de 92 : les soixante-deux lettres et chiffres, puis !@#$%^&*() et enfin -_ []{}<>~`+=,.;:/?|. C'est caractère pour caractère ce que produit le wp_generate_password de WordPress avec ses deux arguments de caractères spéciaux, et 64 × log2(92) vaut 417,5 bits par constante. Deux détails de ce jeu méritent l'attention : il contient une espace, ce qui est légal et un peu surprenant, et il ne contient ni apostrophe ni antislash, ce qui explique que la valeur générée se dépose dans define('AUTH_KEY', '…'); sans le moindre échappement.
Le biais de modulo, dit honnêtement
Il y a un défaut dans l'implémentation, et il mérite d'être énoncé précisément, car une formulation vague serait plus alarmante que le défaut lui-même. Pour transformer un nombre aléatoire de 32 bits en index dans un alphabet de 50 caractères, le code prend le reste : index égale valeur modulo 50. Deux puissance trente-deux vaut 4 294 967 296, et cela ne se divise pas par cinquante. Le reste est quarante-six : quarante-six des cinquante caractères peuvent donc être produits par 85 899 346 des valeurs possibles, et les quatre autres par 85 899 345.
Les caractères favorisés sont donc plus probables que les autres dans un rapport d'une part sur 85 899 345, environ 1,2 pour cent millions. Pour le jeu WordPress de 92 caractères, le reste est douze et le rapport environ 2,1 pour cent millions. Un générateur qui corrigerait cela utiliserait un échantillonnage par rejet — retirer un nombre chaque fois que la valeur tombe dans la queue irrégulière — et gagnerait une différence qu'aucune mesure sur une clé de 50 caractères ne saurait détecter. C'est une note de bas de page, pas un avertissement.
Le biais disparaît complètement quand la taille de l'alphabet est une puissance de deux, car la division est alors exacte — un alphabet de 64 caractères ou de 256 valeurs n'en a aucun. C'est la raison élégante pour laquelle une grande partie du matériel cryptographique s'exprime en hexadécimal ou en base64 : l'arithmétique est exacte et il n'y a plus rien à discuter.
À quoi sert la clé, ce qui décide de ce que sa rotation casse
La documentation de Django énumère exactement ce qui dépend de SECRET_KEY : chaque session, sauf si tu utilises le backend de session par cache et que tu as remplacé le hash d'authentification de session par défaut ; chaque message stocké dans un cookie ; chaque jeton de réinitialisation de mot de passe ; et chaque usage du framework de signature qui ne fournit pas sa propre clé. La conséquence est écrite sur la même page : quand une clé cesse d'être SECRET_KEY et ne figure pas dans SECRET_KEY_FALLBACKS, tout cela est invalidé d'un coup.
En pratique, cela signifie que tout le monde est déconnecté et que chaque lien de réinitialisation déjà dans une boîte mail cesse de fonctionner — exactement ce que tu veux après une fuite, et exactement ce que tu ne veux pas un mardi après-midi sans raison. SECRET_KEY_FALLBACKS est le moyen documenté d'avoir les deux : mets la nouvelle clé dans SECRET_KEY, garde l'ancienne dans la liste de repli assez longtemps pour que les sessions et les liens en cours expirent, puis retire-la. Django précise que les mots de passe des utilisateurs ne dérivent pas de la clé et ne sont affectés dans aucun des deux cas.
WordPress n'offre aucun délai de grâce équivalent, et sa propre documentation le dit en une phrase : tu peux changer les clés à tout moment pour invalider tous les cookies existants, ce qui signifie que chaque utilisateur devra se reconnecter. Il n'y a pas de liste de repli ; le changement est immédiat et total. La même page note que les quatre clés sont obligatoires et les quatre sels seulement recommandés, car WordPress génère lui-même les sels si aucun n'est défini — une raison de plus pour coller les huit plutôt que la moitié.
Où une clé bien générée se brûle quand même
Le générateur est la partie facile. Presque tous les incidents réels commencent après. Une clé finit dans un fichier de configuration commité, dans une capture d'écran collée dans un ticket, dans un message à un collègue, dans un journal de build, dans une variable d'environnement affichée par un endpoint de débogage, ou dans une couche d'image de conteneur qui survit à la branche d'où elle vient. Aucun de ces cas n'est un échec cryptographique ; tous mettent fin à l'utilité de la clé tout aussi complètement.
Le cas du contrôle de version mérite sa propre phrase, parce que l'instinct est mauvais. Supprimer la ligne et commiter la suppression ne retire pas la clé : elle est toujours dans l'historique, toujours dans chaque clone déjà fait, toujours dans chaque fork et chaque miroir du dépôt. Réécrire l'historique n'aide que si tu invalides aussi chaque copie déjà récupérée, ce que tu ne peux pas faire. La seule réponse solide est de considérer la clé comme publique et d'en changer.
Générer dans le navigateur convient très bien, et mieux que cela n'en a l'air. La clé est produite par la page déjà ouverte, à partir de l'aléa du système d'exploitation, et elle n'existe que dans cet onglet jusqu'à ce que tu la copies. Rien n'est envoyé nulle part, parce que rien n'a besoin de l'être : tirer cinquante index et assembler cinquante caractères tient en quelques lignes, et un serveur ne ferait qu'ajouter un endroit, non inspectable, où la clé aurait existé.
| Réglage | Alphabet | Entropie |
|---|---|---|
| Django par défaut, 50 caractères | 50 — minuscules, chiffres, !@#$%^&*(-_=+) | 282,2 bits — octet pour octet le get_random_secret_key de Django |
| Django, alphanumérique, 50 caractères | 62 — lettres des deux casses et chiffres | 297,7 bits — sûr dans une variable shell, rien à protéger |
| Django, jeu complet, 50 caractères | 76 — le jeu alphanumérique plus les quatorze symboles | 312,4 bits — 30 bits de plus que le défaut, sans gain pratique |
| Django, 100 caractères | 50 — le même jeu, deux fois la longueur | 564,4 bits — le réglage existe, le besoin non |
| WordPress jeu complet, 64 caractères, huit constantes | 92 — identique à wp_generate_password avec ses deux drapeaux | 417,5 bits chacune ; contient une espace, ni apostrophe ni antislash |
| WordPress alphanumérique seul, 64 caractères | 62 — lettres et chiffres | 381,1 bits — 36 bits de moins, et rien à échapper nulle part |
| N'importe quel générateur appelant Math.random | Peu importe ce qu'il annonce | Effectivement nulle — l'état se retrouve à partir de quelques sorties |
Questions fréquentes
- Une clé générée dans mon navigateur est-elle aussi sûre qu'une clé générée sur un serveur ?
- Plutôt plus sûre. crypto.getRandomValues puise dans la même entropie du système d'exploitation qu'utiliserait un serveur : l'aléa est de même qualité. La différence est le trajet de la clé : générée dans la page, elle existe dans un onglet et ne va nulle part tant que tu ne la copies pas, alors qu'un générateur côté serveur signifie que la clé a existé sur une machine que tu ne contrôles pas, dans un processus que tu ne peux pas inspecter, peut-être dans une ligne de journal que tu ne verras jamais. Recharge la page ou ferme l'onglet et la valeur a disparu. La seule chose dont un navigateur ne peut pas te protéger, c'est le presse-papiers, et ce dans quoi tu colles ensuite.
- Quelle longueur faut-il vraiment pour une clé secrète ?
- Prends la valeur par défaut du framework et cesse d'y penser. La fonction de Django renvoie 50 caractères, soit 282 bits pour son alphabet de 50 ; WordPress émet 64 caractères tirés de 92, soit 418 bits. Le seuil couramment cité pour un secret symétrique tourne autour de 128 bits, et 23 caractères de l'alphabet Django le dépassent déjà. La longueur supplémentaire ne coûte rien et n'apporte rien ; les réglages à 80 ou 100 caractères existent par exhaustivité, pas parce que quelqu'un en a besoin. La longueur ne devient le mauvais levier que si l'alphabet est minuscule : une clé de 20 caractères faite de chiffres seuls porte 66 bits, moins de la moitié de ce que porte une clé de 20 caractères du jeu Django.
- Le biais de modulo signifie-t-il que je ne devrais pas utiliser ce générateur ?
- Non. Le biais est réel et vaut 1,2 pour cent millions sur l'alphabet Django : quarante-six des cinquante caractères sont atteignables depuis 85 899 346 des quatre milliards de valeurs 32 bits possibles, et les quatre autres depuis 85 899 345. Réparti sur cinquante tirages indépendants, il déplace l'entropie d'une clé d'une quantité très en dessous de toute unité qu'on imprimerait. Ce à quoi le biais sert vraiment, c'est de marqueur quand tu lis le générateur de quelqu'un d'autre : voir un modulo nu t'apprend que personne n'a pensé à l'échantillonnage par rejet, indice sur le reste du code, et voir un échantillonnage par rejet t'apprend que quelqu'un y a pensé. Ni l'un ni l'autre n'est une raison de rejeter une clé déjà profonde de 282 bits.
- Qu'est-ce qui casse au moment où je change la clé ?
- Dans Django : chaque session qui n'est pas dans le backend de cache, chaque message stocké dans un cookie, et chaque jeton de réinitialisation de mot de passe, y compris les liens déjà présents dans les boîtes mail. La documentation le dit clairement et donne l'atténuation — déplace l'ancienne clé dans SECRET_KEY_FALLBACKS, laisse-l'y assez longtemps pour que les sessions et les liens en cours expirent naturellement, puis supprime-la. Les mots de passe des utilisateurs ne dérivent pas de la clé et ne sont pas touchés. Dans WordPress, il n'y a pas de liste de repli : changer l'une des huit constantes invalide immédiatement tous les cookies existants, et sa documentation dit noir sur blanc que tous les utilisateurs devront se reconnecter. Programme un changement de clé à une heure calme, sauf si tu réponds à une fuite, auquel cas fais-le maintenant et assume les déconnexions.
- J'ai commité ma clé dans git. Supprimer la ligne suffit-il ?
- Non. Un commit qui supprime une ligne laisse le commit précédent intact : la clé est toujours dans l'historique et toujours dans chaque clone, fork et miroir déjà existant. Réécrire l'historique la retire de ta copie, et de quiconque récupère ensuite, mais pas de quiconque a récupéré avant — et sur un dépôt public, il faut supposer que cela inclut des scanners automatisés, qui surveillent les nouveaux commits précisément pour cela. Considère la clé comme publiée : génères-en une nouvelle, mets l'ancienne dans la liste de repli si ton framework en a une, et retire-la une fois la fenêtre de transition passée. Rends ensuite la fuite structurellement impossible en gardant la valeur dans une variable d'environnement ou un coffre à secrets, et en ne commitant qu'un fichier qui nomme la variable.
Articles qui pourraient t'intéresser
Tous les guides →Outils similaires
Tout ce qui suit décrit ce que font ces quatre outils aujourd'hui, vérifié en exécutant leur propre code sur les entrées exactes reproduites dans chaque article — et non ce qu'une norme leur imposerait. Quand un outil se trompe sur un cas, c'est dit franchement plutôt que contourné, et rien n'a été modifié pour qu'un article se lise mieux. Deux conséquences. Passe toute transformation sur une copie d'abord et compare les deux bouts : un outil de texte qui supprime quelque chose ne le dit pas. Et considère un secret comme partagé dès qu'il quitte la page — le coller dans une conversation, un ticket ou un dépôt le brûle, aussi bien ait-il été généré.
Sources
- Django Software Foundation — Settings reference — SECRET_KEY and SECRET_KEY_FALLBACKS
- Django (source) — django/core/management/utils.py — get_random_secret_key(), the 50-character alphabet
- WordPress — wp-config.php — Security Keys: changing them invalidates all existing cookies
- MDN Web Docs — Crypto.getRandomValues() — cryptographically strong values, and why Math.random() is not
Tu as repéré une erreur dans cet article ?