Un regard pratique sur les idées de sécurité par construction de Daniel J. Bernstein — de qmail à Curve25519 — et ce que signifie en pratique une « crypto simple et vérifiable ».

La sécurité par construction consiste à construire un système de sorte que les erreurs courantes soient difficiles à commettre — et que les dégâts des erreurs inévitables soient limités. Plutôt que de dépendre d’une longue liste de vérifications (« pensez à valider X, nettoyer Y, configurer Z… »), vous concevez le logiciel de sorte que le chemin le plus sûr soit aussi le plus simple.
Pensez-y comme un emballage « enfant sûr » : il ne suppose pas que tout le monde sera parfaitement précautionneux ; il suppose que les humains sont fatigués, occupés et parfois enclins à l’erreur. Un bon design réduit la part de « comportement parfait » demandée aux développeurs, aux opérateurs et aux utilisateurs.
Les problèmes de sécurité se cachent souvent dans la complexité : trop de fonctionnalités, trop d’options, trop d’interactions entre composants. Chaque bouton supplémentaire peut créer un nouveau mode de défaillance — une façon inattendue pour le système de se briser ou d’être détourné.
La simplicité aide de deux façons pratiques :
Il ne s’agit pas de minimalisme pour le plaisir. Il s’agit de garder l’ensemble des comportements suffisamment petit pour qu’on puisse réellement le comprendre, le tester et raisonner sur ce qui se passe quand quelque chose tourne mal.
Cet article utilise le travail de Daniel J. Bernstein comme exemples concrets de sécurité par construction : comment qmail visait à réduire les modes de défaillance, comment la pensée « temps constant » évite des fuites invisibles, et comment Curve25519/X25519 et NaCl poussent vers une cryptographie moins sujette aux erreurs d’usage.
Ce qu’il ne fera pas : fournir une histoire complète de la cryptographie, prouver mathématiquement la sécurité d’algorithmes, ou prétendre qu’il existe une unique « meilleure » bibliothèque pour chaque produit. Il ne prétendra pas non plus que de bons primitives résolvent tout — les systèmes réels échouent encore à cause de la gestion des clés, d’erreurs d’intégration et de lacunes opérationnelles.
L’objectif est simple : montrer des motifs de conception qui rendent les résultats sécurisés plus probables, même quand vous n’êtes pas un spécialiste en cryptographie.
Daniel J. Bernstein (souvent « DJB ») est un mathématicien et informaticien dont les travaux reviennent régulièrement dans l’ingénierie de sécurité pratique : systèmes de mail (qmail), primitives et protocoles cryptographiques (notamment Curve25519/X25519), et bibliothèques qui empaquettent la crypto pour un usage réel (NaCl).
On cite DJB non pas parce qu’il aurait écrit la seule « bonne » façon de faire de la sécurité, mais parce que ses projets partagent un ensemble cohérent d’instincts d’ingénierie qui réduisent le nombre de façons dont les choses peuvent mal tourner.
Un thème récurrent est des interfaces plus petites et plus resserrées. Si un système expose moins de points d’entrée et moins d’options de configuration, il est plus facile à relire, plus facile à tester et plus difficile à utiliser par erreur.
Un autre thème est des hypothèses explicites. Les défaillances de sécurité surviennent souvent à partir d’attentes non dites — sur le hasard, le comportement temporel, le traitement des erreurs ou le stockage des clés. Les écrits et implémentations de DJB tendent à rendre le modèle de menace concret : qu’est-ce qui est protégé, contre qui et dans quelles conditions.
Enfin, il y a une préférence pour des paramètres par défaut sûrs et la correction ennuyeuse. Beaucoup de conceptions dans cette tradition cherchent à éliminer les arêtes vives qui mènent à des bugs subtils : paramètres ambigus, modes optionnels, et raccourcis de performance qui fuient de l’information.
Cet article n’est pas une histoire de vie ni un débat sur des personnalités. C’est une lecture d’ingénierie : quels motifs on observe dans qmail, la pensée temps-constant, Curve25519/X25519 et NaCl, et comment ces motifs se traduisent en systèmes plus simples à vérifier et moins fragiles en production.
qmail a été construit pour résoudre un problème peu glamour : livrer les emails de façon fiable tout en traitant le serveur de mail comme une cible à haute valeur. Les systèmes de mail sont exposés sur Internet, reçoivent des entrées hostiles toute la journée et manipulent des données sensibles (messages, identifiants, règles de routage). Historiquement, un bug dans un démon de mail monolithique pouvait signifier une compromission totale du système — ou une perte silencieuse de messages que personne ne remarque avant qu’il ne soit trop tard.
Une idée clé dans qmail est de découper la « livraison du mail » en petits programmes qui font chacun une seule tâche : réception, mise en file, livraison locale, livraison distante, etc. Chaque pièce a une interface étroite et des responsabilités limitées.
Cette séparation importe parce que les défaillances deviennent locales :
C’est la sécurité par construction en forme pratique : concevoir le système de sorte qu’« une erreur » ait moins de chances de devenir une « défaillance totale ».
qmail modèle aussi des habitudes qui se traduisent bien au-delà de la messagerie :
La conclusion n’est pas « utilisez qmail ». C’est que vous pouvez souvent obtenir de grands gains de sécurité en repensant l’architecture autour de moins de modes de défaillance — avant d’écrire plus de code ou d’ajouter plus de boutons.
La « surface d’attaque » est la somme de tous les endroits où votre système peut être pincé, sondé ou trompé pour faire la mauvaise chose. Une analogie utile est une maison : chaque porte, fenêtre, ouvre-porte de garage, clé de rechange et fente de livraison est un point d’entrée potentiel. Vous pouvez mettre de meilleures serrures, mais vous gagnez aussi en sécurité en ayant moins de points d’entrée.
Le logiciel est identique. Chaque port ouvert, format de fichier accepté, point d’administration exposé, bouton de configuration ajouté et crochet de plugin supporté augmente le nombre de façons dont les choses peuvent échouer.
Une « interface serrée » est une API qui fait moins, accepte moins de variations et refuse les entrées ambiguës. Cela semble souvent contraignant — mais c’est plus facile à sécuriser parce qu’il y a moins de chemins de code à auditer et moins d’interactions surprenantes.
Considérez deux conceptions :
La seconde conception réduit ce que les attaquants peuvent manipuler. Elle réduit aussi ce que votre équipe peut mal configurer par accident.
Les options multiplient les tests. Si vous supportez 10 bascules, vous n’avez pas 10 comportements — vous avez des combinaisons. Beaucoup de bugs de sécurité se cachent dans ces plis : « ce flag désactive un contrôle », « ce mode saute la validation », « ce réglage legacy contourne les limites de débit ». Les interfaces serrées transforment la « sécurité choisissez votre aventure » en un chemin bien éclairé.
Utilisez ceci pour repérer la surface d’attaque qui grandit discrètement :
Quand vous ne pouvez pas réduire l’interface, rendez-la stricte : validez tôt, rejetez les champs inconnus et placez les « fonctionnalités puissantes » derrière des endpoints séparés et clairement limités.
Le comportement « temps constant » signifie qu’un calcul prend (à peu près) le même temps quel que soit la valeur d’un secret comme une clé privée, un nonce ou des bits intermédiaires. Le but n’est pas d’être rapide ; c’est d’être ennuyeux : si un attaquant ne peut pas corréler le temps d’exécution avec des secrets, il a beaucoup plus de mal à extraire ces secrets en observant.
Les fuites temporelles comptent parce que les attaquants n’ont pas toujours besoin de casser les mathématiques. S’ils peuvent lancer la même opération plusieurs fois (ou l’observer sur du matériel partagé), de petites différences — microsecondes, nanosecondes, voire effets de cache — peuvent révéler des motifs qui s’accumulent en récupération de clé.
Même du code « normal » peut se comporter différemment selon les données :
if (secret_bit) { ... } change le flux de contrôle et souvent le temps d’exécution.Vous n’avez pas besoin de lire de l’assembleur pour tirer de la valeur d’un audit :
La pensée temps-constant est moins une question d’héroïsme qu’une discipline : concevoir le code pour que les secrets ne puissent pas orienter le timing.
L’échange de clés sur courbe elliptique permet à deux appareils de créer le même secret partagé alors qu’ils n’envoient que des messages « publics » sur le réseau. Chaque côté génère une valeur privée (gardée secrète) et une valeur publique correspondante (sans danger à envoyer). Après échange des valeurs publiques, les deux côtés combinent leur valeur privée avec la valeur publique de l’autre pour obtenir un même secret partagé. Un eavesdropper voit les valeurs publiques mais ne peut pas de manière réaliste reconstruire le secret partagé : les deux parties peuvent alors dériver des clés symétriques et communiquer en privé.
Curve25519 est la courbe sous-jacente ; X25519 est la fonction d’échange de clés standardisée, « faites exactement ceci ». Leur attrait tient largement à la sécurité par construction : moins de pièges, moins de choix de paramètres et moins de façons de sélectionner par erreur un réglage non sûr.
Elles sont aussi rapides sur une large gamme de matériel, ce qui compte pour des serveurs qui gèrent beaucoup de connexions et pour des téléphones qui cherchent à économiser de la batterie. La conception favorise des implémentations plus faciles à rendre en temps constant (aide à résister aux attaques temporelles), ce qui réduit le risque qu’un attaquant astucieux extraie des secrets en mesurant de petites différences de performance.
X25519 vous donne un accord de clé : il aide deux parties à dériver un secret partagé pour du chiffrement symétrique.
Il ne fournit pas l’authentification par lui-même. Si vous exécutez X25519 sans vérifier qui se trouve de l’autre côté (par exemple via certificats, signatures ou clé pré-partagée), vous pouvez toujours être trompé et finir par communiquer en toute sécurité avec la mauvaise partie. Autrement dit : X25519 empêche l’écoute, mais n’empêche pas l’usurpation à lui seul.
NaCl (la « Networking and Cryptography library ») a été conçue autour d’un objectif simple : rendre difficile pour les développeurs d’applications d’assembler par erreur de la cryptographie non sécurisée. Plutôt que d’offrir un buffet d’algorithmes, de modes, de règles de padding et de boutons de configuration, NaCl vous oriente vers un petit ensemble d’opérations haut-niveau déjà câblées de façon sûre.
Les API de NaCl sont nommées d’après ce que vous voulez faire, pas d’après les primitives que vous allez assembler.
crypto_box (« box ») : chiffrement authentifié à clé publique. Vous lui fournissez votre clé privée, la clé publique du destinataire, un nonce et un message. Vous obtenez un ciphertext qui (a) cache le message et (b) prouve qu’il vient de quelqu’un qui connaît la bonne clé.crypto_secretbox (« secretbox ») : chiffrement authentifié à clé partagée. Même idée, mais avec une clé secrète partagée.L’avantage clé est que vous ne choisissez pas séparément « mode de chiffrement » et « algorithme MAC » puis n’espérez pas les avoir combinés correctement. Les valeurs par défaut de NaCl imposent des compositions modernes résistantes aux mauvaises utilisations (encrypt-then-authenticate), si bien que des modes d’échec communs — comme oublier la vérification d’intégrité — sont moins probables.
La rigidité de NaCl peut sembler limitante si vous avez besoin de compatibilité avec des protocols hérités, des formats spécialisés ou des algorithmes imposés par la réglementation. Vous échangez « je peux régler chaque paramètre » contre « je peux livrer quelque chose de sûr sans être expert en cryptographie ».
Pour beaucoup de produits, c’est exactement le but : contraindre l’espace de conception pour que moins de bugs puissent exister. Si vous avez vraiment besoin de personnalisation, vous pouvez descendre aux primitives bas-niveau — mais vous vous exposez alors à nouveau aux arêtes vives.
« Sûr par défaut » signifie que l’option la plus sûre et la plus raisonnable est ce que vous obtenez si vous ne faites rien. Si un développeur installe une bibliothèque, copie un exemple rapide ou utilise les paramètres du framework, le résultat doit être difficile à mal utiliser et difficile à affaiblir accidentellement.
Les valeurs par défaut comptent parce que la plupart des systèmes réels tournent avec elles. Les équipes vont vite, la documentation est survolée et la configuration croît organiquement. Si le défaut est « flexible », cela se traduit souvent par « facile à mal configurer ».
Les échecs crypto ne sont pas toujours causés par une « mauvaise math ». Ils viennent souvent du choix d’un réglage dangereux parce qu’il était disponible, familier ou facile.
Pièges courants des valeurs par défaut :
Privilégiez des stacks qui rendent le chemin sûr le plus simple : primitives vetées, paramètres conservateurs, et APIs qui ne vous demandent pas de prendre des décisions fragiles. Si une bibliothèque vous force à choisir entre dix algorithmes, cinq modes et multiples encodages, on vous demande d’ingénier la sécurité par configuration.
Quand c’est possible, choisissez des bibliothèques et des designs qui :
La sécurité par construction, c’est en partie refuser de transformer chaque décision en un menu déroulant.
« Vérifiable » ne signifie pas « formellement prouvé » dans la plupart des équipes produit. Ça veut dire que vous pouvez bâtir la confiance rapidement, de façon répétable et avec moins de possibilités de mal comprendre ce que fait le code.
Un code devient plus vérifiable quand :
Chaque branche, mode et fonctionnalité optionnelle multiplie ce que les relecteurs doivent raisonner. Des interfaces plus simples réduisent l’ensemble des états possibles, ce qui améliore la qualité de la revue de deux façons :
Restez ennuyeux et répétable :
Cette combinaison ne remplace pas l’expertise, mais elle élève le plancher : moins de surprises, détection plus rapide et code que l’on peut réellement analyser.
Même si vous choisissez des primitives bien considérées comme X25519 ou une API minimaliste style NaCl « box »/« secretbox », les systèmes cassent encore dans les parties désordonnées : intégration, encodage et opérations. La plupart des incidents réels ne sont pas « la mathématique était fausse » mais « la mathématique a été mal utilisée ».
Les erreurs de gestion des clés sont fréquentes : réutiliser des clés longue durée là où une clé éphémère est attendue, stocker des clés dans le contrôle de source, ou confondre "clé publique" et "clé secrète" parce qu’elles sont toutes deux des tableaux d’octets.
La mauvaise utilisation des nonces est un récidiviste. Beaucoup de schémas de chiffrement authentifiés exigent un nonce unique par clé. Dupliquer un nonce (souvent via des remises à zéro de compteur, des courses multi-processus ou des hypothèses « suffisamment aléatoires ») et vous pouvez perdre confidentialité ou intégrité.
Les problèmes d’encodage et de parsing créent des échecs silencieux : base64 vs hex, suppression de zéros en tête, endianness incohérente ou acceptation de multiples encodages qui se comparent différemment. Ces bugs peuvent transformer une « signature vérifiée » en « quelque chose d’autre vérifié ».
La gestion des erreurs peut être dangereuse dans les deux sens : retourner des erreurs détaillées qui aident les attaquants, ou ignorer les échecs de vérification et continuer quand même.
Les secrets fuient via logs, rapports de crash, analyses et endpoints de debug. Les clés se retrouvent aussi dans des sauvegardes, images VM et variables d’environnement partagées trop largement. Parallèlement, les mises à jour de dépendances (ou leur absence) peuvent vous laisser sur une implémentation vulnérable même si la conception était solide.
De bonnes primitives ne produisent pas automatiquement un produit sûr. Plus vous exposez de choix — modes, paddings, encodages, adaptations maison — plus vous multipliez les façons pour les équipes de construire accidentellement quelque chose de fragile. Une approche sécurité par construction commence par choisir une voie d’ingénierie qui réduit les points de décision.
Utilisez une bibliothèque haut-niveau (APIs one-shot comme « chiffrer ce message pour ce destinataire ») quand :
Composez des primitives bas-niveau (AEADs, hashes, échanges de clés) seulement quand :
Règle utile : si votre design doc contient « on choisira le mode plus tard » ou « on fera attention aux nonces », vous avez déjà trop de boutons.
Demandez des réponses concrètes, pas du marketing :
Traitez la crypto comme du code critique pour la sécurité : réduisez la surface API, fixez les versions, ajoutez des known-answer tests, et exécutez du fuzzing sur le parsing/sérialisation. Documentez ce que vous ne supporterez pas (algorithmes, formats legacy) et construisez des migrations plutôt que des « boutons de compatibilité » qui restent pour toujours.
La sécurité par construction n’est pas un outil que vous achetez — c’est un ensemble d’habitudes qui rendent certaines catégories de bugs plus difficiles à créer. Le fil conducteur dans l’ingénierie à la DJB : garder les choses suffisamment simples pour être raisonnables, resserrer les interfaces pour contraindre les usages erronés, écrire du code qui se comporte de la même façon même sous attaque, et choisir des valeurs par défaut qui échouent en sécurité.
Si vous voulez une checklist structurée pour ces étapes, pensez à ajouter une page interne « inventaire crypto » à côté de vos docs sécurité (par exemple /security).
Ces idées ne se limitent pas aux bibliothèques crypto — elles s’appliquent à la façon dont vous construisez et déployez le logiciel. Si vous utilisez un workflow de génération rapide (par exemple Koder.ai, où vous créez des apps web/serveur/mobile via chat), les mêmes principes se retrouvent comme contraintes produit : garder un petit nombre de stacks supportés (React pour le web, Go + PostgreSQL pour le backend, Flutter pour le mobile), insister sur la planification avant de générer des changements, et rendre les rollbacks bon marché.
En pratique, des fonctionnalités comme mode planification, snapshots et rollback, et export du code source aident à réduire le "rayon d’explosion" des erreurs : vous pouvez relire l’intention avant le déploiement, revenir rapidement en arrière si quelque chose casse, et vérifier que ce qui tourne correspond à ce qui a été généré. C’est le même instinct de sécurité par construction que la compartimentation de qmail — appliqué aux pipelines de livraison modernes.
La sécurité par construction consiste à concevoir un logiciel de sorte que le chemin le plus sûr soit aussi le plus simple. Plutôt que de compter sur des listes de contrôle longues, on contraint le système pour que les erreurs courantes soient difficiles à commettre et que les erreurs inévitables aient un impact limité (un « rayon d’explosion » réduit).
La complexité crée des interactions cachées et des cas limites difficiles à tester et faciles à mal configurer.
Gains pratiques de la simplicité :
Une interface serrée fait moins de choses et accepte moins de variations. Elle évite les entrées ambiguës et réduit les modes facultatifs qui créent de la « sécurité par configuration ».
Approche pratique :
qmail divise le traitement du courrier en petits programmes (réception, mise en file, livraison locale, livraison distante, etc.) avec des responsabilités étroites. Cela réduit les modes de défaillance parce que :
Le comportement en temps constant vise à faire en sorte que la durée d’exécution (et souvent les motifs d’accès mémoire) ne dépendent pas des valeurs secrètes. C’est important parce que des attaquants peuvent parfois déduire des secrets en mesurant le temps, les effets de cache ou les différences "fast path vs slow path" sur de nombreuses exécutions.
Il s’agit d’empêcher des fuites « invisibles », pas seulement de choisir des algorithmes solides.
Commencez par identifier ce qui est secret (clés privées, secrets partagés, clés MAC, tags d’authentification), puis cherchez les endroits où les secrets influencent le contrôle de flux ou les accès mémoire.
Signaux d’alarme :
if sur des données secrètesVérifiez aussi que votre dépendance cryptographique revendique explicitement un comportement en temps constant pour les opérations que vous utilisez.
X25519 est une fonction d’accord de clé spécifique construite sur Curve25519. Elle est populaire parce qu’elle réduit les pièges : moins de paramètres à choisir, bonnes performances et une conception qui facilite des implémentations en temps constant.
C’est une voie par défaut plus sûre pour l’échange de clés — à condition de gérer correctement l’authentification et la gestion des clés.
Non. X25519 fournit un accord de clé (secret partagé) mais ne prouve pas l’identité de l’autre partie.
Pour empêcher l’usurpation, associez-le à une authentification :
Sinon, vous pouvez vous retrouver à « parler en toute sécurité » avec la mauvaise partie.
NaCl réduit les erreurs en proposant des opérations de haut niveau déjà correctement composées, plutôt qu’un buffet d’algorithmes et de modes.
Briques usuelles :
crypto_box : chiffrement authentifié à clé publique (vous + clé publique du destinataire + nonce → ciphertext)crypto_secretbox : chiffrement authentifié à clé partagéeLe bénéfice pratique est d’éviter des erreurs de composition courantes (par exemple chiffrer sans protection d’intégrité).
Même de bons primitives échouent quand l’intégration et l’exploitation sont négligées. Pièges courants :
Mitigations : documenter l’unicité des nonces et tester leur réutilisation ; imposer un encodage canonique et rejeter le reste ; échouer fermé avec des messages génériques ; stocker les clés dans un gestionnaire de secrets et restreindre/rotater l’accès.