@mostajs/crypto-box
v0.3.0
Published
Les primitives cryptographiques de l'écosystème, une seule maison : AEAD EN FLUX (AES-GCM / ChaCha20-Poly1305), chiffrement par ENVELOPPE (X25519, modèle age), signature Ed25519, HKDF, scrypt, comparaison à temps constant. Enveloppe MINCE et opinionée sur
Maintainers
Readme
@mostajs/crypto-box
Auteur : Dr Hamid MADANI [email protected] · Licence : AGPL-3.0-or-later
Niveau : N0 (primitif) · Zéro dépendance (node:crypto uniquement)
Les primitives cryptographiques de l'écosystème. Une seule maison.
Pourquoi ce module existe
En consolidant les URLs signées (13/07/2026), on a trouvé la même comparaison de signature
implémentée deux fois : « best effort » (boucle JS) dans @mostajs/url — le kernel,
utilisé partout — et correcte (timingSafeEqual) dans @mostajs/storage, une simple feuille.
Le durcissement n'avait jamais circulé.
C'est ça, le vrai coût d'un doublon cryptographique : ce n'est pas la place qu'il prend, c'est que les corrections ne remontent pas. Une seule maison = un seul endroit à durcir, un seul endroit à auditer.
Ce que ce module n'est PAS
Ce n'est pas une « bibliothèque de crypto générique ». C'est une enveloppe mince et
opinionée sur node:crypto :
- pas de choix d'algorithme,
- pas de mode dangereux,
- pas d'IV fourni par l'appelant.
Un module de crypto qui offre des options est la manière classique d'écrire du chiffrement cassé. Le périmètre est dicté par la demande réelle — on l'enrichira au prochain besoin constaté, pas avant.
API
| Fonction | Réclamée par |
|---|---|
| sha256 · sha256Bytes | blob-store (SigV4, checksums) |
| hmacSha256 · hmacSha256Bytes | @mostajs/url (URLs signées), SigV4 |
| equalsConstantTime | toute comparaison de secret, signature ou jeton |
| aeadEncrypt · aeadDecrypt | @mostajs/backup (archives chiffrées) |
| deriveKey · randomKey | @mostajs/backup (clé depuis une phrase) |
import { deriveKey, aeadEncrypt, aeadDecrypt } from '@mostajs/crypto-box';
// Le client ne retient qu'une phrase. Le sel se conserve à côté du chiffré (il n'est pas secret).
const { key, salt } = await deriveKey('la phrase du client');
const chiffre = aeadEncrypt(key, archive);
// Plus tard, ailleurs :
const { key: k2 } = await deriveKey('la phrase du client', { salt });
const restaure = aeadDecrypt(k2, chiffre); // lève si la clé est fausse OU si UN BIT a changéAES-256-GCM — chiffrement authentifié : confidentialité et intégrité. Le format est
autoportant (MJSC1 | iv | tag | chiffré) : rien d'autre à conserver que la clé.
Dérivation par scrypt (lente et gourmande à dessein) : un simple SHA-256 d'une phrase se
casse par force brute à des millions d'essais par seconde.
Ce dont ce module ne répond pas
La garde de la clé est une décision produit, pas une affaire de module. Pour les sauvegardes TicketFlow : la clé reste chez le client, la console ne stocke que du chiffré qu'elle ne peut pas lire. Corollaire assumé, à contractualiser : clé perdue = archive irrécupérable.
Tests
npm test # 18 tests mjs-unitEn cryptographie, un test qui prouve que « ça marche » ne prouve presque rien : du chiffrement cassé chiffre et déchiffre parfaitement. Ces tests prouvent ce qui tue :
- un seul bit altéré fait échouer le déchiffrement (tag GCM) — un déchiffrement qui « réussirait » sur des octets corrompus rendrait une sauvegarde silencieusement fausse ;
- l'IV n'est jamais réutilisé (deux chiffrés du même clair diffèrent) ;
- une clé fausse échoue, jamais de sortie silencieuse ;
equalsConstantTimene plante pas sur des longueurs différentes —timingSafeEqualde Node lève, et un appel naïf tomberait sur une signature tronquée, c'est-à-dire sur une entrée d'attaquant.
Vecteurs vérifiés indépendamment : SHA-256 (coreutils), HMAC (RFC 4231, cas 1). Jamais recopiés depuis le module — un test qui compare la sortie à elle-même ne teste rien.
