Gratuito, senza account, cifrato end-to-endRichiedi un segreto →

Scarica i vettori di test (JSON)

Ce document décrit exactement ce qu'implémente packages/crypto. Les vecteurs de test déterministes sont publiés dans packages/crypto/test-vectors.json et vérifiés à chaque exécution des tests.

Primitives

| Usage | Primitive | Implémentation | | --- | --- | --- | | Chiffrement authentifié | AES-256-GCM, IV de 96 bits, étiquette de 128 bits | WebCrypto | | Dérivation | HKDF-SHA256 (RFC 5869) | WebCrypto | | Vérificateur de PIN | HMAC-SHA256 | WebCrypto | | Empreintes | SHA-256 | WebCrypto | | Dérivation du PIN | Argon2id, m = 65 536 Kio, t = 3, p = 1, sortie 32 octets | hash-wasm (WebAssembly, MIT), chargé seulement si un PIN est utilisé | | Aléa | crypto.getRandomValues (par blocs de 64 Kio) | WebCrypto |

Aucune primitive n'est écrite à la main.

Encodages

  • Binaire → texte : base64url sans remplissage. Le décodage est strict : remplissage, caractères étrangers et encodages non canoniques sont refusés.
  • Chaînes info HKDF et données associées (AAD) : UTF-8, préfixe eclair v1 .
  • HKDF utilise un sel vide (équivalent, par la RFC 5869, à 32 octets nuls).

Création d'un secret

  1. id : 16 octets aléatoires, encodés en base64url (22 caractères).
  2. K : 32 octets aléatoires, la clé du lien.
  3. Sans PIN : Kc = HKDF(K, "eclair v1 content", 32).
  4. Avec PIN (ou phrase de passe) :
    • le PIN est normalisé en NFKC ;
    • pinSalt = HKDF(K, "eclair v1 pin-salt", 16) (dérivé, jamais transmis) ;
    • a = Argon2id(PIN, pinSalt) ;
    • Kc = HKDF(K ‖ a, "eclair v1 content", 32) ;
    • Kauth = HKDF(K, "eclair v1 pin-auth", 32) ;
    • verifier = HMAC-SHA256(Kauth, a), envoyé au serveur, qui ne stocke que SHA-256(verifier).
  5. Contenu clair : JSON canonique (clés d'objets triées récursivement) d'un des trois types :
    { "v": 1, "type": "text", "text": "…" }
    { "v": 1, "type": "structured", "template": "ftp", "fields": [{ "label": "…", "value": "…", "secret": true }] }
    { "v": 1, "type": "files", "text": "…", "files": [{ "name": "…", "mime": "…", "size": 0, "objectKey": "…", "chunkSize": 65536, "noncePrefix": "…" }] }
    
    Après déchiffrement, le JSON est validé avant tout affichage.
  6. Bourrage : u32 big-endian (longueur) ‖ JSON ‖ octets nuls, jusqu'au palier supérieur : 1 Kio, 4 Kio, 16 Kio, 64 Kio, puis multiples de 64 Kio. Le chiffré ne révèle que le palier.
  7. Chiffrement : AES-256-GCM(Kc, iv aléatoire de 12 octets, AAD = "eclair v1 secret " + id). L'AAD lie le chiffré à son identifiant : un chiffré déplacé sous un autre id ne se déchiffre pas.
  8. Libellé optionnel : Klabel = HKDF(K, "eclair v1 label", 32), bourré comme le contenu, AES-256-GCM(Klabel, iv distinct, AAD = "eclair v1 label " + id).
  9. Jeton de gestion : 32 octets aléatoires ; le client envoie SHA-256(jeton), le serveur ne connaît jamais le jeton lui-même.

Liens

https://eclair.kayzen-lyon.com/s/{id}#v1.{n|p}.{K}
https://eclair.kayzen-lyon.com/gerer#v1.{id}.{jeton}
  • n : sans PIN, p : avec PIN. Rien d'autre dans le fragment.
  • Le fragment (#…) n'est jamais envoyé par le navigateur au serveur. Aucune query string n'est utilisée.
  • Tout fragment qui n'est pas exactement de cette forme est refusé.

Ce que reçoit le serveur

| Champ | Contenu | | --- | --- | | id | Identifiant aléatoire | | ciphertext, iv | Contenu bourré et chiffré | | labelCiphertext, labelIv | Libellé chiffré (optionnel) | | hasPin | Booléen | | pinVerifier | HMAC(Kauth, Argon2id(PIN)), stocké haché | | manageTokenHash | SHA-256(jeton de gestion) |

Le serveur ne reçoit jamais K, Kc, le PIN, le jeton de gestion ni le contenu clair.

Propriétés et limites

  • Sans le lien complet, personne ne déchiffre, y compris avec un accès total au serveur et à la base.
  • Le serveur ne peut pas tester de PIN : verifier dépend de K, qu'il n'a pas.
  • Limite assumée : un attaquant qui détient le lien et une copie de la base peut tester les PIN hors ligne. Argon2id ralentit chaque essai (64 Mio de mémoire), mais un PIN de 4 à 6 chiffres reste cassable. Une phrase de passe longue protège bien mieux ; l'interface le dit.
  • La taille réelle du contenu est masquée par paliers ; le palier, lui, reste visible.
  • L'effacement des tampons en mémoire (wipe) est un effort raisonnable : JavaScript ne garantit pas l'absence de copies.

Tests

  • Vecteurs officiels : RFC 5869 (HKDF), RFC 4231 (HMAC), FIPS 180-2 (SHA-256), spécification GCM (cas 13 et 14).
  • Argon2id : déterminisme, dépendance au sel, normalisation NFKC, paramètres v1. Les vecteurs de la RFC 9106 utilisent un secret et des données associées que hash-wasm n'expose pas : la conformité d'Argon2id repose sur les tests de hash-wasm et sur nos vecteurs v1.
  • Vecteurs v1 : 3 cas (texte sans PIN, texte avec PIN et libellé, contenu structuré avec phrase de passe).
  • Couverture de packages/crypto : 100 % des lignes, branches et fonctions (seuil bloquant : 95 %).

Régénérer les vecteurs (uniquement lors d'un changement de format, qui imposerait un format v2) :

UPDATE_VECTORS=1 pnpm --filter @sesame-eclair/crypto test

Fichiers (STREAM)

  • Clé par fichier : Kf_i = HKDF(Kc, "eclair v1 file " + i), où Kc est la clé de contenu du secret (elle dépend donc aussi du PIN éventuel).
  • Blocs de 64 Kio chiffrés en AES-256-GCM, sans données associées, avec le nonce préfixe aléatoire (7 octets) ‖ compteur (4 octets, gros-boutiste) ‖ drapeau dernier bloc (1 octet).
  • Un fichier vide produit un bloc unique (marqué dernier). Toute troncature, extension, permutation ou modification d'un bloc fait échouer le déchiffrement.
  • Stockage : 64 blocs par objet (4 Mio de clair). Clés d'objet {upload}/{fichier}/{partie}, aléatoires.
  • Nom, type MIME, taille réelle et préfixe de nonce sont dans le contenu chiffré du secret. Le stockage voit la taille des objets chiffrés (proche de la taille réelle) : limite documentée.
  • Les URL présignées durent 5 minutes. Après la dernière lecture, les objets restent 15 minutes pour le téléchargement, puis sont supprimés ; ils le sont immédiatement en cas de destruction, d'expiration ou de blocage après trop d'essais de code.

Demandes de secret (X25519)

  • Le demandeur génère une paire X25519 (sk, pk) et un jeton de gestion. Le serveur ne reçoit que pk et SHA-256(jeton).
  • Lien de dépôt : /r/{id}#v1.{pk}[.{libellé en base64url}]. Le libellé ne voyage que dans le fragment.
  • Lien privé du demandeur : /demandes#v1.{id}.{jeton}.{sk}. Il contient la clé de déchiffrement.
  • Dépôt : paire éphémère (esk, epk), Kd = HKDF(ikm = X25519(esk, pk), sel = epk ‖ pk, "eclair v1 request"), AES-256-GCM(Kd, iv aléatoire, AAD = "eclair v1 request " + id), contenu bourré comme un secret.
  • Les points de faible ordre sont refusés (secret partagé nul).

Traduzione automatica, in fase di revisione.