peeza — protocole et cryptographie
Version 23 du document · 2026-11-06 · version du format peeza-e2e-v2
Traduction de la version anglaise (https://peeza.app/protocol). Les numéros de section et les vecteurs de test sont identiques dans les deux langues ; en cas d'écart entre les deux textes, la version anglaise fait foi, et l'écart est un problème à nous signaler (section 13).
Ce document décrit comment peeza protège un fichier entre l'appareil qui l'envoie et l'appareil qui le reçoit : les clés, le format de chiffrement, la façon dont une clé parvient au destinataire, l'association de deux appareils, et ce que le relais de peeza peut voir ou non. Il s'adresse aux personnes qui veulent examiner l'affirmation « chiffré de bout en bout » plutôt que la croire sur parole.
Ce que ce document est, et ce qu'il n'est pas.
- C'est une spécification du protocole, pour tout ce qui touche à la sécurité, assez précise pour qu'on puisse le réimplémenter. La section 12 donne des vecteurs de test ; chacun a été produit par le moteur tel qu'il est livré, puis reproduit par une implémentation indépendante écrite à partir de ce seul texte.
- Le moteur des applications natives n'est pas à code source ouvert. Vous pouvez lire le code du client web, puisque votre navigateur le reçoit ; vous pouvez capturer le trafic et le comparer à ce document ; vous ne pouvez pas lire le code source du moteur. Nous disons « le protocole et la cryptographie sont publiés », et non « vérifiez le binaire vous-même ».
- Ce n'est pas un audit indépendant, et il ne prétend pas en être un.
- Il ne décrit pas l'ordonnancement des transferts, la stratégie de reprise, ni la façon dont les appareils d'une équipe choisissent auprès de qui récupérer un fichier. Rien de cela ne change ce qu'un tiers peut lire ou falsifier, qui est le sujet ici.
La section 10 énumère les limites et les exceptions. Lisez-la avant de citer la section 1.
1. Ce que nous affirmons
- Le contenu est chiffré sur l'appareil qui envoie et déchiffré sur l'appareil qui reçoit, en AES-256-GCM, avec des clés dérivées d'une clé générée sur l'appareil qui envoie.
- Le relais ne reçoit pas cette clé dans les cas normaux : elle voyage dans le fragment de l'URL d'un lien, ou scellée pour la clé publique de l'appareil destinataire, ou elle est dérivée par deux appareils sur un réseau local sans que le relais y prenne part. La section 10.1 décrit le seul cas où une clé passe délibérément par le relais.
- Le relais ne stocke jamais le contenu d'un fichier. Quand une connexion directe est impossible, il transmet des trames chiffrées, en mémoire, entre deux pairs connectés.
- Le relais ne peut pas modifier un transfert sans que cela se voie. Il ne peut ni changer le contenu, ni le tronquer, ni changer le nom, la taille ou la forme déclarés, ni annoncer une livraison qui n'a pas eu lieu. Chacune de ces tentatives échoue à l'authentification à l'un des deux bouts.
- Un transfert direct et un transfert relayé sont aussi confidentiels l'un que l'autre. Les mêmes trames scellées circulent sur les deux chemins.
Ce que nous n'affirmons pas : que les métadonnées sont cachées (section 9), qu'un transfert fait par lien bénéficie de la confidentialité persistante (10.5), ni que le client web est protégé contre le serveur qui le livre (10.2).
L'affirmation 4 ne tenait pas pour les livraisons d'équipe dans la version 1 du format : un relais qui voyait deux accusés de réception d'un même partage d'équipe pouvait en fabriquer un troisième. La version 2 du format corrige ce point ; l'avis de sécurité est à la section 14.
2. Parties et chemins
| partie | ce que c'est |
|---|---|
| Expéditeur, destinataire | Deux appareils qui utilisent peeza : une application native (macOS, Windows, Linux, iOS/iPadOS, Android) ou le client web. Toutes les applications natives intègrent le même moteur. |
| Relais | Un serveur exploité par peeza. Il présente les appareils l'un à l'autre (signalisation), authentifie les comptes et transmet des trames chiffrées quand aucune connexion directe n'est possible. |
Un transfert emprunte l'un de quatre chemins. Le chiffrement du contenu décrit à la section 4 est identique sur les quatre et ne dépend pas du transport sous-jacent.
| chemin | transport | rôle du relais |
|---|---|---|
| Direct | Canal de données WebRTC entre les deux appareils | signalisation seulement |
| Direct, entre deux applications | QUIC (iroh) entre les deux appareils, par le relais iroh de peeza jusqu'à ce qu'un chemin direct s'ouvre (section 8) | signalisation, un ticket pour le relais iroh, et transmission jusqu'à l'ouverture d'un chemin direct |
| Relayé | WebSocket vers le relais, qui transmet les trames | signalisation et transmission |
| À proximité | WebSocket entre deux appareils du même réseau local | aucun ; fonctionne sans Internet |
3. Clés et secrets
| nom | ce que c'est | taille | durée de vie | où elle se trouve |
|---|---|---|---|---|
| Clé de transfert | Le secret dont dérive chaque clé qui scelle une charge utile partagée (4.1) | 256 bits, tirés du générateur aléatoire du système d'exploitation | un partage ; partager de nouveau crée une nouvelle clé | l'appareil de l'expéditeur ; celui du destinataire une fois livrée |
| Sous-clés | Une clé AES-256-GCM par usage, et une clé HMAC-SHA-256 pour l'accusé de réception, dérivées d'une clé de transfert ou d'une clé de session À proximité (4.1) | 256 bits chacune | celle de la clé dont elles dérivent | mémoire, dérivées au besoin |
| Clé d'identité de l'appareil | Paire de clés X25519 qui désigne une installation | courbe de 255 bits | la vie de l'installation | moitié privée sur l'appareil seulement, dans un fichier lisible par l'utilisateur seul ; moitié publique enregistrée auprès du relais quand l'appareil est connecté à un compte, et annoncée sur le réseau local quand « À proximité » est activé |
| Clé éphémère À proximité | Paire de clés X25519 | courbe de 255 bits | une connexion à proximité | mémoire |
| Clé de session À proximité | Secret dérivé par l'échange de la section 6, découpé en sous-clés comme une clé de transfert | 256 bits | une connexion à proximité | mémoire |
| Code de premier contact À proximité | Six chiffres décimaux que l'appareil qui envoie tire pour un essai de premier contact, le mot de passe du SPAKE2 de la section 6 | environ 20 bits ; un essai par code | un essai | l'écran et la mémoire de l'appareil qui envoie ; jamais envoyé |
Ceux-ci ne sont pas des clés et ne déchiffrent rien :
| nom | à quoi il sert |
|---|---|
| Jeton de lien | 8 caractères (40 bits aléatoires, base32 de Crockford). Désigne un transfert en attente sur le relais. Un seul usage, 48 heures. |
| Jeton de session de compte | 192 bits aléatoires, émis par le relais à la connexion. Le relais n'en conserve que le SHA-256. |
| Secret d'enregistrement de l'appareil | 256 bits aléatoires, générés sur l'appareil. Prouve au relais qu'un identifiant d'appareil correspond à la même installation qu'avant. Le relais n'en conserve que le SHA-256. |
Il n'y a ni séquestre de clés, ni clé de récupération, ni clé détenue par l'exploitant qui ouvrirait un transfert.
4. Chiffrement des transferts
4.1 Primitives et dérivation des clés
AES-256-GCM (FIPS 197, NIST SP 800-38D) : clé de 256 bits, nonce de 96 bits, étiquette d'authentification de 128 bits. HMAC-SHA-256 (RFC 2104) pour l'accusé de réception. HKDF-SHA-256 (RFC 5869) pour dériver les clés. Les applications natives utilisent AWS-LC (aws-lc-rs) ; le client web utilise WebCrypto (AES-GCM, HKDF, HMAC). Les deux produisent des octets identiques.
Une clé de transfert s'écrit en 43 caractères de base64url sans remplissage.
Rien n'est scellé sous la clé de transfert elle-même. Chaque usage a sa propre sous-clé :
sous-clé(usage) = HKDF-SHA-256( clé d'entrée = clé de transfert, sel = vide, info = "peeza-e2e-v2/" ‖ usage, longueur = 32 octets )
| usage | clé pour |
|---|---|
content | les blocs de contenu (4.3) |
manifest | le manifeste de la charge utile (4.4) |
receipt | l'accusé de réception, une clé HMAC (4.5) |
delta, adopt | les deux petits échanges en usage (4.6) |
from | le nom de l'appareil qui a ouvert un lien (4.6) |
tree | réservé : aucune application ne l'envoie (4.6) |
Aucun usage ne partage sa clé avec un autre : une erreur dans la façon dont l'un d'eux emploie ses nonces ne peut pas affaiblir les autres.
4.2 Nonces
Un nonce fait 12 octets. Le contenu et le manifeste utilisent une disposition déterministe :
nonce(domaine, index) = [ domaine : 1 octet ][ 0x00 0x00 0x00 ][ index : uint64 gros-boutiste ]
Les deux échanges et from utilisent un nonce aléatoire neuf pour chaque message, transporté dans le message (4.6). L'accusé de réception est un MAC et n'a pas de nonce.
| sous-clé | message scellé | nonce | données authentifiées additionnelles |
|---|---|---|---|
content | un bloc de contenu | nonce(0x00, numéro du bloc) | VERSION ‖ uint64BE(total) |
manifest | le manifeste de la charge utile | nonce(0x08, 0) | VERSION |
delta | négociation « n'envoyer que ce qui a changé » | 12 octets aléatoires | VERSION ‖ 0x02 ou 0x03, un octet par sens |
tree | réservé (négociation de la structure d'un dossier) ; aucune application ne l'envoie | — | 0x04 et 0x05 réservés |
adopt | vérification « la copie que j'ai déjà est-elle le même fichier ? » | 12 octets aléatoires | VERSION ‖ 0x06 ou 0x07 |
from | le nom qu'une application ayant ouvert un lien donne à l'expéditeur | 12 octets aléatoires | VERSION ‖ 0x02 |
Pourquoi aucun couple (clé, nonce) ne scelle deux messages différents qui quittent un appareil : une clé scelle une seule charge utile, donc un numéro de bloc scelle toujours le même bloc (4.3) — un appareil d'équipe dont la copie a été modifiée ne scelle le bloc changé que dans sa propre mémoire, pour en comparer l'étiquette, et le jette sans l'envoyer (7.3) ; une clé scelle un seul manifeste, et le moteur refuse d'en sceller un second (4.4) ; et parmi les quelques centaines de messages qu'une clé transporte, un nonce aléatoire de 96 bits se répète avec une probabilité négligeable (environ n²/2⁹⁷ pour n messages), et non jamais. Les octets de domaine 0x01 et 0x09 étaient le nom de fichier et l'accusé de la version 1 et ne servent plus.
VERSION correspond aux 12 octets ASCII peeza-e2e-v2. Elle figure dans l'info HKDF de chaque sous-clé, dans les données additionnelles de chaque message scellé et dans l'accusé de réception, de sorte que deux clients en désaccord sur le format échouent à l'authentification au lieu de mal déchiffrer. total est la longueur totale, en octets, du texte clair de la charge utile.
4.3 Contenu
La charge utile est découpée en blocs de 64 512 octets (63 × 1024) ; le dernier bloc est plus court. Le bloc i commence à l'octet i × 64512. Une trame, telle qu'elle circule :
trame = uint64BE(i) ‖ AES-256-GCM[content]( bloc_i, nonce(0x00, i), VERSION ‖ uint64BE(total) )
c'est-à-dire 8 octets d'index, le texte chiffré, et l'étiquette de 16 octets. AES-256-GCM[content] désigne AES-256-GCM sous la sous-clé content (4.1).
Comme total est lié à chaque bloc, si quoi que ce soit entre les deux appareils change la taille déclarée — pour faire passer un fichier raccourci pour complet —, aucun bloc ne s'authentifie. Le destinataire exige aussi que chaque bloc ait exactement la longueur que son index implique, avant de l'écrire.
Comme les nonces du contenu sont déterministes, sceller le même bloc de la même charge utile sous la même clé donne toujours la même trame. Deux conséquences :
- Reprendre un transfert n'exige, chez l'expéditeur, aucun autre état que la charge utile : le destinataire demande une position et reçoit les trames qu'il aurait reçues.
- Tout appareil qui détient la charge utile et la clé peut servir ces trames (section 7). Ce n'est sûr que tant que le texte clair n'a pas changé ; la section 7.3 décrit la vérification qui l'assure.
4.4 Le manifeste de la charge utile
Tout ce qu'un destinataire doit tenir pour vrai au sujet d'une charge utile est scellé en un seul message :
manifeste = JSON { "n": nom, "s": taille, "t": date-de-modification-ms?, "p": empaqueté?, "d": dossier?, "f": version-du-format? }scellé = base64url( AES-256-GCM[manifest]( manifeste, nonce(0x08, 0), VERSION ) )
p et d indiquent si la charge utile est un conteneur à déballer (un dossier ou plusieurs fichiers voyagent en un seul flux ZIP non compressé), et f quelle version de ce format de conteneur l'a produite. Les champs marqués ? sont omis quand ils sont vides.
Le destinataire tire le nom, la taille et la forme uniquement du manifeste. Le message de signalisation qui le transporte répète aussi certaines de ces valeurs en clair, pour la comptabilité du relais ; le destinataire n'en tient pas compte. Un message sans manifeste est refusé. Il n'y a aucun repli sur des valeurs non authentifiées. Le même message indique "wire": 2 ; un destinataire refuse un message qui indique autre chose, en disant que les deux côtés doivent être à jour.
Le nonce est fixe, donc une clé ne scelle qu'un seul manifeste. Le moteur le garantit : le manifeste est scellé une seule fois, quand la charge utile est figée, puis conservé et renvoyé tel quel ; si la description d'une charge utile figée a changé, le moteur refuse d'envoyer plutôt que de sceller un second manifeste ; partager de nouveau le même fichier crée une nouvelle clé. Un appareil qui sert de nouveau une charge utile (section 7) transmet le manifeste scellé d'origine et ne scelle jamais le sien.
4.5 L'accusé de réception
Un destinataire qui a reçu chaque octet, authentifié, l'indique à l'expéditeur par un accusé de réception :
accusé = base64url( HMAC-SHA-256( clé = sous-clé("receipt"), VERSION ‖ uint64BE(total) ‖ uint16BE(longueur de l'identifiant de salle) ‖ identifiant de salle ) )
L'expéditeur ne marque un transfert comme terminé que sur un accusé qui se vérifie, comparé en temps constant. Le relais transmet le message « done » mais ne peut pas produire d'accusé : il ne détient jamais la clé, et les accusés qu'il a vus pour d'autres salles ne lui apprennent rien sur celle-ci. Il ne peut donc pas annoncer une livraison qui n'a pas eu lieu. L'identifiant de salle lie l'accusé à une livraison précise quand plusieurs destinataires partagent une clé (section 7) ; ce qu'un accusé prouve, c'est qu'un détenteur de la clé dit que la charge utile est arrivée dans cette salle, pas lequel des appareils. Sur le chemin À proximité, l'identifiant de salle est vide ; la clé y est déjà propre à la connexion.
Les messages de signalisation sont aussi vérifiés selon leur sens : un message destiné à l'expéditeur est ignoré par un destinataire, et inversement. Un « done » envoyé à un destinataire n'a aucun effet.
4.6 Les petits échanges scellés
Deux échanges facultatifs accompagnent le contenu, chacun sous sa propre sous-clé, avec un nonce aléatoire neuf par message et un octet de sens dans les données additionnelles (tableau de la section 4.2) :
- message = base64url( n ‖ AES-256-GCM[usage]( JSON, n, VERSION ‖ octet de sens ) ),
n = 12 octets aléatoires
L'octet de sens empêche de renvoyer un message au côté qui l'a émis. Ces échanges permettent à un destinataire qui a déjà une version antérieure ou le même fichier d'éviter de le recevoir de nouveau. Ils transportent des empreintes de blocs et des plages, jamais de clés. La vérification « même fichier » passe par le relais quand il n'y a pas de chemin direct ; le relais peut voir qu'un échange a eu lieu, pas ce qui s'y est dit.
Un troisième usage, tree (négociation de la structure d'un dossier), est réservé : sa chaîne d'usage et ses octets de sens 0x04 et 0x05 ne sont pas réutilisés, et aucune application ne l'envoie.
Un quatrième, from, est un message seul et non un échange : une application qui a ouvert un lien public met le nom de son appareil, scellé sous la forme {"name": …}, dans le champ from de sa request passée par la salle, pour que l'appareil expéditeur puisse dire quel appareil a reçu ce passage. Il ne va que dans un sens ; son unique octet de sens 0x02 est séparé de celui de delta par sa propre sous-clé. Un navigateur n'en envoie pas, et un appareil expéditeur qui ne peut pas l'ouvrir l'ignore ; le transfert ne l'attend jamais. L'appareil destinataire n'apprend rien sur l'expéditeur.
5. Comment la clé parvient au destinataire
5.1 Un lien
https://g.peeza.app/K7Q9F2MX#k=<clé de transfert>
Le chemin est le jeton du lien. Le fragment (après le #) contient la clé. Un navigateur n'envoie jamais le fragment dans une requête HTTP, et une application native analyse le lien elle-même et ne présente que le jeton : le relais apprend donc le jeton, pas la clé.
Le lien est transmis par le moyen que l'expéditeur choisit : un message, un courriel, un code QR. Le lien est le secret. Quiconque le détient peut recevoir le fichier, une fois. Il fonctionne pour un seul destinataire et expire après 48 heures s'il n'est pas utilisé.
5.2 Adressé à un appareil
Quand vous envoyez à l'un de vos propres appareils, ou à l'appareil d'un membre de votre équipe, il n'y a pas de lien. La clé de transfert est scellée pour la clé publique d'identité de l'appareil destinataire :
enveloppe = crypto_box_seal( clé de transfert, clé publique d'identité du destinataire )
C'est la « boîte scellée » anonyme de NaCl / libsodium : une paire de clés X25519 éphémère par enveloppe, XSalsa20-Poly1305, 48 octets de surcoût. Le relais achemine l'enveloppe et la garde jusqu'à 7 jours si l'appareil est hors ligne. Seul l'appareil qui détient la clé privée peut l'ouvrir. Pendant que l'appareil qui envoie attend, le relais lui dit si l'appareil destinataire est connecté et s'il garde la livraison en attente d'une acceptation (section 9).
L'appareil qui envoie apprend la clé publique du destinataire auprès du relais. Il épingle cette clé la première fois qu'il l'utilise, et refuse ensuite de sceller pour une autre clé au nom du même appareil. Voir la section 10.3 pour ce que cela couvre et ne couvre pas.
Une livraison à un seul appareil d'un membre de l'équipe, plutôt qu'un fichier partagé avec l'équipe, nomme l'équipe et aucun élément partagé. Le relais ne la laisse passer que tant que les deux comptes sont membres actifs de cette équipe, la marque toujours comme en attente d'une acceptation sur cet appareil, et indique à cet appareil quel compte l'a envoyée — le compte propriétaire de la salle, le seul fait sur l'expéditeur que le relais a établi. Elle n'ajoute rien au catalogue de l'équipe.
5.3 À proximité
Aucune clé de transfert n'est transportée. Les deux appareils dérivent une clé de session entre eux (section 6).
6. L'association À proximité
Deux appareils sur le même réseau local, sans relais, sans compte, sans Internet.
Découverte. Chaque appareil s'annonce par multidiffusion UDP, toutes les 3 secondes, sur chacune de ses interfaces réseau, vers deux groupes : 239.255.66.66:5354 en IPv4 et [ff02::7065:657a]:5354 (portée lien local) en IPv6, chacun sur les interfaces qui ont une adresse de cette famille. L'annonce est la même dans les deux cas : identifiant de l'appareil, nom, système d'exploitation, version de l'application, clé publique d'identité, port d'écoute, et les adresses IPv4 de l'appareil qui s'annonce sur ses interfaces routées. Un appareil se connecte en retour à l'adresse annoncée qui se trouve sur l'un de ses propres réseaux, s'il y en a une ; sinon à l'adresse d'où vient l'annonce, en préférant une adresse routable à une adresse de lien local et, entre deux adresses du même genre, l'IPv4 ; une adresse IPv6 de lien local est utilisée avec l'interface par laquelle elle est arrivée, et une arrivée par un tunnel passe en dernier. Ces annonces sont en clair et non authentifiées, visibles sur le réseau local, et rien n'est tenu pour fiable sur leur seule foi : c'est l'échange ci-dessous qui authentifie l'appareil atteint. « À proximité » est désactivé tant que l'utilisateur ne l'active pas sur macOS et iOS, qui demandent l'autorisation avant qu'une application n'atteigne le réseau local. Sur Android, Windows et Linux, il est activé d'emblée ; sur Windows, il attend tant que Windows considère le réseau comme public.
Échange. Les appareils se connectent par une WebSocket ordinaire, et chacun envoie un message d'accueil avec sa clé publique d'identité I, une clé publique éphémère E toute neuve et la version du protocole À proximité, 2. Un appareil dont le message d'accueil porte une version antérieure est refusé (outdated), sans repli. Les deux calculent :
transcription = SHA-256( "peeza-lan-v2" ‖ tri(I_a, I_b) ‖ tri(E_a, E_b) )clé de session = SHA-256( "peeza-lan-key" ‖ X25519(e_moi, E_pair) ‖ transcription )preuve_moi = HMAC-SHA-256( clé = X25519(i_moi, E_pair), message = transcription )
tri place les deux clés de 32 octets dans l'ordre de leurs octets, de sorte que les deux côtés hachent la même entrée. Les lettres minuscules désignent des clés privées.
- La clé de session vient des deux clés éphémères : un vol ultérieur d'une clé d'identité ne permet donc pas de déchiffrer un transfert à proximité antérieur.
- La preuve montre que chaque côté détient la moitié privée de la clé d'identité qu'il a présentée. Le pair la vérifie avec
X25519(e_pair, I_moi), qui donne la même valeur par symétrie de l'échange, en temps constant.
Premier contact. Après les preuves, chaque côté dit s'il connaît déjà l'autre : sa clé est épinglée, ou c'est l'un des appareils de ce compte (ci-dessous). Si les deux le connaissent, le transfert commence. Sinon, l'appareil qui envoie tire un code — six chiffres décimaux, uniformes, du générateur du système d'exploitation — et l'affiche ; le code ne voyage jamais. La personne devant l'appareil qui reçoit le saisit. Les deux exécutent alors SPAKE2 sur le groupe Ed25519, avec le code pour mot de passe et l'identité de chaque côté liée à la transcription, puis confirment le résultat :
id_a, id_b = "peeza-lan-v2/sender" ‖ transcription, "peeza-lan-v2/receiver" ‖ transcriptionK = SPAKE2( code, id_a, id_b )confirm_a = HMAC-SHA-256( clé = K, message = "peeza-lan-confirm-a" ‖ transcription )confirm_b = HMAC-SHA-256( clé = K, message = "peeza-lan-confirm-b" ‖ transcription )
Le destinataire envoie son message SPAKE2 avec confirm_b ; l'expéditeur le vérifie en temps constant et répond par confirm_a, que le destinataire vérifie. Un écart fait tirer un nouveau code à l'expéditeur et laisse le destinataire réessayer ; le troisième écart met fin à l'association des deux côtés. Une confirmation prouve que les deux côtés ont calculé la même transcription, donc vu les mêmes clés d'identité et éphémères : quelqu'un qui s'interpose et substitue une clé ne peut pas confirmer sans le code. K ne sert qu'à la confirmation ; la clé de session est celle ci-dessus. Une concordance épingle la clé d'identité du pair, et la connexion suivante à un appareil épinglé ne demande aucun code. Un appareil qui n'est pas connecté à un compte épingle aussi, dans son espace hors compte, gardé à part des épingles de tout compte.
Les appareils d'un même compte. Quand les deux appareils sont connectés à un compte, chacun demande au relais la liste des appareils de son compte (5.2) au premier contact. Un pair dont l'identifiant d'appareil et la clé publique d'identité figurent tous deux sur cette liste compte comme connu, et il est épinglé sans code. Un identifiant listé avec une autre clé, une clé différente d'une épingle antérieure, ou une liste que le relais ne fournit pas en 5 secondes, ne compte pas ; si l'un des côtés ne connaît pas l'autre, les deux passent par le code. C'est la confiance que prend déjà un envoi adressé à un appareil (10.3) : entre les appareils d'un même compte, la première clé vient du relais, pas des deux personnes.
Le consentement. Une épingle dit qui est un appareil, pas que ses fichiers sont voulus. Quand un appareil épinglé qui n'est pas l'un de ceux du destinataire envoie, le destinataire lit le manifeste scellé, montre le fichier et attend l'acceptation de sa personne avant de demander le moindre bloc ; pendant ce temps il envoie held une fois par minute, et l'expéditeur attend. Un refus met fin à la session par error {reason:"declined"}. Un envoi depuis l'un des appareils du compte démarre aussitôt, sauf si le destinataire a coupé l'acceptation automatique. Saisir le code au premier contact vaut acceptation.
Contenu. La clé de session prend la place de la clé de transfert dans la section 4 : elle est découpée en les mêmes sous-clés (4.1), qui scellent le manifeste et les blocs et servent de clé à l'accusé. Le transport n'est pas chiffré ; toute la protection sur ce chemin est celle de la section 4.
Un code à six chiffres donne à un attaquant actif une chance sur un million par code. SPAKE2 ne révèle rien qui puisse être testé hors ligne : chaque essai exige un échange en direct pendant que quelqu'un saisit, et une association en permet trois avant de prendre fin.
7. Les équipes, et les charges utiles détenues par plusieurs appareils
Une équipe est un groupe de comptes qui partagent des fichiers. Un fichier partagé avec une équipe peut être récupéré plus tard auprès de n'importe quel appareil membre qui le détient, pas seulement auprès de l'appareil qui l'a partagé en premier.
7.1 Une clé par charge utile. Une charge utile partagée a une seule clé de transfert. Chaque livraison de cette charge utile à un appareil membre porte cette clé dans sa propre enveloppe scellée (5.2), scellée pour cet appareil, avec la même règle d'épinglage. Le relais garde la liste des membres de l'équipe et un catalogue de ce qui a été partagé — noms, tailles, qui l'a partagé —, et jamais une clé, sauf dans le cas que décrit la section 10.1.
7.2 Des trames identiques. Les appareils qui détiennent la charge utile et sa clé la désignent par un identifiant public :
identifiant de charge utile = base64url( SHA-256( "peeza-payload-id-v1" ‖ clé de transfert ) )
L'identifiant ne révèle rien de la clé. Un détenteur sert les mêmes trames que l'expéditeur d'origine l'aurait fait (4.3). Quand un appareil récupère un fichier auprès de plusieurs détenteurs à la fois par des connexions directes, chaque trame est précédée d'une étiquette de 8 octets (les 8 premiers octets du SHA-256 de l'identifiant de charge utile) pour que le destinataire distingue les charges utiles ; ce préfixe sert à l'acheminement, pas à la sécurité. Une trame est acceptée parce qu'elle s'authentifie, quel que soit l'appareil qui l'a envoyée.
7.3 Un détenteur doit prouver ce qu'il scelle de nouveau. La copie d'un détenteur est un fichier ordinaire dans le dossier de l'utilisateur, et elle a pu être modifiée. Sceller un contenu modifié sous la clé et le numéro de bloc d'origine réutiliserait un nonce. C'est pourquoi un appareil qui reçoit enregistre l'étiquette d'authentification de chaque bloc à son arrivée et, avant de servir un bloc, scelle de nouveau sa copie et compare l'étiquette. Le bloc scellé de nouveau reste dans la mémoire de l'appareil tant que les étiquettes ne concordent pas ; s'il ne concorde pas, il est jeté, jamais envoyé : un bloc modifié est donc scellé localement sous le nonce d'origine, mais ne quitte jamais l'appareil. Un appareil sans ce registre ne sert rien du tout. Cela protège contre un fichier modifié ; ce n'est pas une défense contre un détenteur lui-même malveillant, qui détient la clé et pourrait mal agir de toute façon.
7.4 Ce qu'implique l'appartenance. Chaque appareil membre qui a reçu une charge utile en détient la clé. Retirer un membre arrête les livraisons futures. Cela ne peut pas reprendre une clé ou un fichier qu'il a déjà.
8. Le transport sous-jacent
Ces protections s'appliquent à la connexion. Elles s'ajoutent à la section 4, et aucune des affirmations de la section 1 n'en dépend.
| tronçon | protection |
|---|---|
| Appareil ↔ relais | TLS 1.2 ou plus récent (HTTPS et WebSocket), réglages par défaut des bibliothèques, certificats validés par rapport aux autorités racines du système. Aucun paramètre n'est affaibli. |
| Appareil ↔ appareil, direct | Canal de données WebRTC : ICE, DTLS, SCTP, avec un certificat autosigné tout neuf à chaque connexion, dont l'empreinte est échangée par la signalisation du relais. |
| Appareil ↔ appareil, entre deux applications | QUIC (iroh) : TLS 1.3 dans QUIC, chaque côté authentifié par la clé Ed25519 de son point de connexion — une clé neuve pour chaque transfert, dont l'identifiant est échangé par la signalisation du relais. L'application qui envoie retient la première trame jusqu'à deux secondes pour un chemin direct ; les trames passent directement quand un chemin s'est ouvert, et par le relais iroh de peeza seulement sinon. Un chemin direct qui s'ouvre plus tard prend le relais dans la même connexion. |
| Appareil ↔ relais iroh | TLS 1.2 ou plus récent (WebSocket) quand le relais iroh est joint en HTTPS, et un ticket pour ce transfert (plus bas). Le relais iroh transmet des paquets QUIC qu'il ne peut pas déchiffrer. |
| Appareil ↔ appareil, à proximité | aucune (section 6) |
Si le relais trafique la signalisation. Avec WebRTC, quiconque transporte la signalisation peut substituer les empreintes de certificat et s'interposer au milieu de la connexion DTLS. peeza ne compte pas sur DTLS pour la confidentialité. Un relais qui ferait cela détiendrait la session DTLS et ne verrait toujours que les trames scellées de la section 4, sous une clé qu'on ne lui a jamais donnée : la clé est arrivée dans le fragment d'un lien, dans une enveloppe scellée pour une clé d'appareil épinglée, ou par un échange auquel le relais n'a pas pris part. Il pourrait supprimer ou retarder des trames. Il ne pourrait ni les lire, ni les modifier, ni les raccourcir, ni les remplacer.
Entre deux applications. Quand le relais de peeza le propose, une application demande au relais un ticket pour chaque transfert, ouvre un point de connexion QUIC sous une clé neuve, et le nomme dans la salle avant son offre WebRTC, avec le relais par lequel on le joint et ses adresses directes (le champ addrs du message iroh : adresses de l'hôte, publiques et ouvertes par le routeur, en ip:port, au plus 16, jamais de boucle locale ni de lien local ; Nearby désactivé sur un Mac ou un iPhone, aucune sur un réseau local). L'application qui reçoit nomme son propre point de connexion sans adresse : seul le côté qui compose les utilise. Une autre application joint ce point de connexion à ces adresses et par le relais iroh de peeza à la fois, ouvre un flux, y écrit l'identifiant de la salle et la position voulue ("PZ1", la longueur de l'identifiant en entier de 16 bits gros-boutiste, l'identifiant, la position en entier de 64 bits gros-boutiste), et reçoit les trames scellées de 4.3, chacune précédée de sa longueur en entier de 32 bits gros-boutiste. Un destinataire qui demande par la salle que le flux reprenne à une autre position (le request de tous les chemins, par exemple pour un lecteur qui lit en avance) reçoit les trames depuis cette position sur le même flux, qui reste ouvert jusqu'à la fin du transfert. L'accusé de réception passe toujours par le relais (4.5). Tout autre destinataire — un navigateur — ignore le point de connexion et répond à l'offre WebRTC, comme avant. Le paragraphe précédent vaut sans changement : un relais qui substituerait un identifiant de point de connexion détiendrait une session QUIC et ne verrait toujours que des trames scellées.
Le relais iroh est un service de peeza. Il n'admet une connexion qu'avec un ticket que le relais a signé pour ce côté de ce transfert (Ed25519, valable dix minutes pour l'admission), et indique à chaque appareil son adresse publique, comme le fait un serveur STUN. Ce chemin n'utilise aucun relais ni service de recherche d'adresses exploité par d'autres.
Pour trouver un chemin direct, un appareil demande à des serveurs STUN quelle est son adresse publique : celui du relais, et des serveurs publics exploités par Google et Cloudflare. Un serveur STUN voit une adresse IP et rien du transfert. peeza n'utilise aucun serveur TURN ; son propre relais sert de solution de repli.
9. Ce que voit le relais
Le chiffrement cache le contenu. Il ne cache pas qu'un transfert a eu lieu. Le relais voit ou détient :
- Pour chaque transfert qu'il aide à établir : les adresses IP des deux bouts (et, entre deux applications, les adresses directes que l'application qui envoie nomme dans la salle, comme le faisaient déjà les candidats WebRTC), l'heure, la taille, la date de modification du fichier, et si la charge utile est un conteneur. Pour un transfert relayé, aussi les trames scellées à leur passage, gardées en mémoire seulement. Le relais compare les adresses des deux bouts et dit aux deux, en un bit, s'ils sont sur le même réseau (la même adresse IPv4, ou le même /64 IPv6), pour qu'un appareil dont Nearby est coupé puisse le proposer ; il ne garde ni les adresses ni la réponse. Quand une application ouvre un lien public, sa
requestporte le nom de son appareil scellé sous la clé du lien (4.6) : le relais voit que le champ est là, et sa longueur. - Pour un compte connecté : l'identité du compte auprès de son fournisseur de connexion, ses appareils (nom, système d'exploitation, version, clé publique d'identité) et des compteurs d'utilisation.
- Pour un appareil connecté à un compte : un reflet de la liste des transferts de cet appareil, pour que le tableau de bord web du compte puisse l'afficher et la piloter. Ce reflet comprend les noms de fichiers, les tailles et les états. Il exclut les clés, les fragments de liens et les chemins des dossiers locaux. Il comprend les noms des appareils vers lesquels un transfert est allé ou dont il vient, dont celui de l'appareil qui a ouvert un lien (4.6), une fois que l'appareil expéditeur l'a ouvert. Il est gardé en mémoire seulement. Quand l'appareil se déconnecte, sa dernière liste est gardée jusqu'à 7 jours pour que le tableau de bord puisse encore l'afficher ; elle disparaît plus tôt quand le relais redémarre, quand l'appareil se reconnecte ou est retiré, ou quand le compte est supprimé.
- Pour une équipe : ses membres et leurs adresses courriel, quel membre a invité chaque adresse en attente quand ce n'est pas le propriétaire, si le propriétaire laisse les membres inviter, et le catalogue des fichiers partagés : nom, taille, qui l'a partagé, quand.
- Pour un appareil hors ligne : les enveloppes scellées qui l'attendent (5.2), que le relais ne peut pas ouvrir, pendant au plus 7 jours.
- Pour une livraison adressée à un appareil (5.2) : l'appareil auquel elle est adressée, en mémoire. Pendant que l'appareil qui envoie attend dans la salle, le relais lui dit deux choses qu'il détient déjà sur cet appareil : s'il est connecté, et si sa carte reflétée pour cette livraison est en attente d'une acceptation. Rien d'autre sur cet appareil n'est transmis, et seulement à l'expéditeur de la salle. Pour une livraison à un seul appareil d'un membre de l'équipe (5.2), l'appareil qui la reçoit apprend quel compte l'a envoyée.
- Pendant qu'un appareil se connecte à un compte : les adresses IP d'où la connexion a été lancée, d'où elle a été terminée dans un navigateur et d'où elle a été récupérée, en mémoire, pour les comparer (10.9) — pendant au plus 15 minutes, jamais écrites sur disque. Le pays et l'opérateur réseau de chacune, tirés d'une base de données que détient le relais, figurent dans les courriels décrits en 10.9 ; les adresses elles-mêmes, non. Ces courriels partent à l'adresse du compte par un fournisseur d'envoi de courriels.
- Pendant qu'un appareil s'associe par code QR (10.13) : un hachage SHA-256 du code, pendant au plus 2 minutes, et la demande du téléphone — le nom et le type qu'il déclare, et l'appareil du compte qu'il a sollicité — pendant au plus 3 minutes. En mémoire seulement, jamais écrits sur disque.
- Pour chaque salle qu'il ouvre : un hachage SHA-256 de la preuve d'expéditeur de la salle, en mémoire. La preuve est faite de 32 octets aléatoires que le relais génère quand un expéditeur ouvre la salle et ne renvoie qu'à cet expéditeur. L'appareil de l'expéditeur la garde avec le transfert (jamais dans le lien, jamais dans le reflet du tableau de bord) et la présente chaque fois qu'il rejoint la salle, pour que le relais puisse distinguer l'appareil qui a ouvert la salle de toute autre personne qui en connaît l'identifiant. Elle ne protège aucun contenu : c'est le relais qui l'a créée, et la clé de transfert n'y intervient pas. Voir la section 10.11.
- Pour un appareil qui passe par le relais sans compte : le décompte des octets qu'il a fait passer par le relais, pour le tenir à l'allocation sans compte (2 Go sur 30 jours à ce jour ; le relais publie le chiffre). Quand un tel appareil ouvre une salle, ou la rejoint, il présente son identifiant de machine, un identifiant aléatoire créé à l'installation de peeza. Le relais en garde un hachage à clé (HMAC-SHA-256 sous une clé que lui seul détient), jamais l'identifiant lui-même. L'appareil le présente aussi quand il demande ce qu'il reste de son allocation, pour que l'application dise quand elle est épuisée ; le relais répond à partir du même décompte et ne garde rien de plus. Un expéditeur qui n'en présente pas (un navigateur, ou une application antérieure à cette version) est compté par son adresse IP, en mémoire seulement — une adresse n'est jamais écrite, hachée ou non. Chaque décompte est effacé 30 jours après son premier octet. Le relais compte aussi, par adresse IP et en mémoire seulement, les octets relayés dans la journée. Les transferts directs et à proximité ne sont pas comptés.
- Pour un transfert entre deux applications par QUIC (section 8) : l'identifiant du point de connexion de chaque côté, neuf pour ce transfert, et le ticket qu'il a signé pour ce côté. Le relais iroh voit les adresses IP des deux et des paquets QUIC qu'il ne peut pas déchiffrer, et indique au relais combien d'octets il a transportés sous chaque ticket. Le relais garde un enregistrement par ticket — le compte facturé, ou le hachage à clé de l'identifiant de machine d'un appareil sans compte, les octets indiqués et quand — pendant 7 jours, jamais une adresse IP. Ces octets comptent dans les mêmes allocations que les trames relayées ; les octets passés directement entre les deux appareils ne sont pas comptés.
Donc : dans un transfert fait par lien entre deux appareils qui ne sont connectés à aucun compte, le relais voit un manifeste scellé, un contenu scellé et l'identifiant de machine de l'appareil qui envoie. Dès qu'un compte est en jeu, les noms de fichiers sont visibles du relais, dans le tableau de bord de ce compte et dans le catalogue de l'équipe. Le contenu ne l'est jamais.
Ce que le relais peut faire : refuser le service, supprimer ou retarder un transfert, et observer ce qui précède. Ce qu'il ne peut pas faire est à la section 1.
10. Limites et exceptions
10.1 Obtenir un lien depuis un autre de vos appareils fait passer cette clé par le relais. Un utilisateur connecté peut demander, depuis le tableau de bord web ou depuis un autre de ses appareils, le lien d'un transfert que l'un de ses appareils propose — pour le copier, le partager ou l'afficher en code QR. L'appareil envoie alors le lien complet, clé comprise, au relais, qui le remet au tableau de bord et cesse de le servir après 30 secondes. Pour ce transfert, à ce moment-là, le relais détient la clé. Cela n'arrive que sur cette demande explicite du titulaire du compte. Cela veut dire qu'un relais compromis, ou une session de compte volée, pourrait aussi la demander.
10.2 Le client web fait confiance au serveur qui le livre. Un navigateur reçoit son code des serveurs de peeza à chaque visite. La clé contenue dans le fragment n'est pas envoyée au serveur, mais le script de la page la lit pour déchiffrer. Un serveur compromis pourrait livrer un script qui envoie la clé ailleurs. C'est vrai de tout chiffrement fait dans un navigateur. Les applications natives n'ont pas cette propriété ; elles ont celle de la section 10.7.
10.3 La première clé d'un appareil vient du relais. L'épinglage (5.2) protège chaque envoi après le premier. Au premier envoi vers un appareil, l'expéditeur fait confiance à la clé publique que fournit le relais. Un relais qui substituerait sa propre clé lors de ce premier contact pourrait ouvrir cette enveloppe. Associer deux appareils en personne (section 6) épingle la clé sans le relais, sauf entre deux appareils connectés au même compte : ceux-là s'épinglent d'après la liste d'appareils du relais, sans code, si bien qu'un relais qui substituerait une clé sur la liste pourrait prendre la place de l'un d'eux au premier contact.
10.4 Un lien est un secret au porteur. Quiconque obtient le lien peut recevoir le fichier, une fois. Un lien copié reste dans le presse-papiers, et tout ce qui peut lire le presse-papiers, ou le canal par lequel le lien est envoyé, peut lire la clé.
10.5 Pas de confidentialité persistante pour les transferts par lien et adressés. Une clé de transfert reste la même pendant toute la vie d'un partage. Quelqu'un qui enregistre les trames scellées d'un transfert relayé et obtient plus tard la clé ou le lien peut les déchiffrer. Seul le chemin À proximité dérive une nouvelle clé à chaque connexion.
10.6 Les clés sont des fichiers sur l'appareil. Les clés d'identité, les clés épinglées et les clés des transferts encore affichés dans l'application sont stockées dans le dossier de données de l'application, lisibles par le compte de l'utilisateur et protégées par le système d'exploitation, non par un magasin de clés matériel. Tout ce qui s'exécute sous l'identité de l'utilisateur peut les lire.
10.7 Le code source du moteur est fermé. Ce document, les vecteurs et le client web vous permettent d'examiner la conception et une implémentation de celle-ci. Ils ne vous permettent pas de confirmer qu'une version native donnée n'implémente que cela. Les applications natives sont distribuées signées : notarisation d'Apple et flux de mises à jour signé sur macOS, paquet signé sur Windows, App Store et Google Play sur les téléphones, et sommes de contrôle SHA-256 publiées sur Linux.
10.8 Métadonnées. Voir la section 9. Les adresses IP, les tailles et le moment des transferts sont visibles du relais et, pour un transfert direct, de l'autre appareil.
10.9 Connecter un nouvel appareil peut encore faire l'objet d'un hameçonnage depuis votre propre réseau. Connecter un appareil à un compte ouvre une page dans un navigateur, et rien dans cette page ne la lie à l'application qui l'a demandée. Quelqu'un qui vous persuade de terminer une connexion qu'il a lancée pourrait rattacher son appareil à votre compte. Cela concerne les comptes et le tableau de bord à distance, pas le chiffrement d'un transfert, mais cela se combine avec la section 10.1 : un appareil rattaché à votre compte peut demander à vos autres appareils le lien d'un transfert.
Ce qui fait obstacle. Quand un appareil se connecte, le relais compare le réseau d'où le navigateur a terminé la connexion avec les réseaux d'où l'appareil l'a lancée et récupérée. Ils concordent quand ils partagent une adresse IPv4, ou un même /64 IPv6 ; un côté en IPv4 et l'autre en IPv6 compte comme différent. Quand ils diffèrent, l'appareil ne reçoit rien tant que la connexion n'a pas été approuvée par un lien envoyé par courriel à l'adresse du compte — que la personne qui vous a envoyé un lien ne peut pas lire. Le courriel montre les deux endroits (pays et opérateur réseau, jamais une adresse), dit qu'un RPV (VPN) ou le relais privé iCloud peut les faire différer, et dit de ne pas approuver si quelqu'un vous a envoyé un lien ou vous a demandé de vous connecter. Le navigateur qui a terminé la connexion affiche le même avertissement au lieu de sa page habituelle quand la connexion a été lancée depuis un autre réseau. Si le courriel ne peut pas partir, ou si le compte n'a pas d'adresse, la connexion est refusée ; sans réponse, elle expire après 15 minutes. Chaque connexion envoie aussi au compte un avis qui nomme l'appareil, avec un lien qui le déconnecte, une seule fois, pendant 30 jours : il révoque la session de cet appareil et le retire du compte. Retirer un appareil depuis le tableau de bord ou depuis un autre appareil fait la même chose.
Ce qui reste :
- Un hameçonnage depuis le même réseau passe. Quelqu'un sur votre Wi-Fi, dans votre bureau, ou derrière le même NAT d'opérateur que vous — ce qui peut regrouper de nombreux clients d'un même opérateur mobile — vous ressemble. L'avis et son lien Déconnecter sont ce qui reste dans ce cas.
- Approuver malgré l'avertissement rattache son appareil.
- Le nom de l'appareil dans les deux courriels est celui que l'appareil déclare, et un attaquant le choisit. Les courriels le présentent comme tel.
- Les connexions faites avant que cela existe, et les connexions au tableau de bord web, ne reçoivent aucun avis. Une connexion web renvoie sa session au navigateur même qui l'a terminée ; il n'y a donc pas de seconde partie à hameçonner.
- Les applications antérieures aux versions du 2026-10-06 n'affichent pas d'écran « Consultez vos e-mails ». Tout ce qui précède relève du relais, et s'applique à toutes les applications installées. Depuis les versions du 2026-10-06 (Mac 26.10.7, Windows 26.10.6, Linux 26.10.8, macOS 12–13 26.10.7, iOS 26.10.10, Android 26.10.8), une application affiche cet écran pendant qu'une connexion attend d'être approuvée, et envoie le nom de l'appareil pour les courriels. Une application plus ancienne affiche son écran d'attente habituel et abandonne après dix minutes, et les courriels la désignent par « un appareil ».
10.10 Versions. Un changement du format scellé change VERSION et n'est pas rétrocompatible, à dessein. Le 2026-09-28, le manifeste et l'accusé de réception sont devenus obligatoires. Le 2026-10-07, la version 2 du format (peeza-e2e-v2) a découpé chaque clé en sous-clés, fait de l'accusé un MAC et donné aux échanges des nonces aléatoires (section 14). Les applications d'une version antérieure ne peuvent pas échanger de fichiers avec les versions actuelles, et échouent avec un message disant que les deux côtés doivent être à jour plutôt que de se rabattre sur l'ancien format.
10.11 Un expéditeur qui rejoint une salle n'a pas encore à prouver qu'il l'a ouverte. Les applications et les pages antérieures à la preuve d'expéditeur (section 9) rejoignent une salle en la nommant, et le relais l'accepte encore. Quiconque connaît l'identifiant d'une salle — le destinataire compris — peut donc prendre la place de l'expéditeur dans cette salle. Il n'obtient aucune clé et ne peut produire aucune trame que le destinataire accepterait, puisque chaque trame est scellée sous la clé de transfert ; il peut perturber ce transfert-là. Le relais compte les retours sans preuve valide et les refusera dès que les applications en usage la présenteront.
10.12 Les octets transportés par le relais iroh sont comptés à leur passage. (Le relais iroh ne sert que les transferts entre deux applications.) Le relais iroh compte chaque octet qu'un appareil lui envoie pour un transfert, y compris le tramage et les accusés de QUIC lui-même ; un transfert qui reste sur ce relais compte donc un peu plus que la taille du fichier dans une allocation (8 % de plus, dans notre mesure d'un fichier de 16 Mo). Le relais WebSocket compte les trames scellées qu'il transmet. Les octets envoyés avant qu'un chemin direct s'ouvre comptent ; ceux envoyés après, non.
10.13 Associer un téléphone par code QR : l'Autoriser de l'ordinateur est la défense. Un appareil déjà connecté à un compte peut afficher un code QR qu'un autre appareil scanne pour rejoindre le même compte, sans navigateur ni fournisseur de connexion. Le code fait 128 bits aléatoires tirés par le relais, affiché comme un lien vers https://peeza.app/pair#<code> (le code est dans le fragment, si bien que le site ne le reçoit jamais). Il vit 2 minutes, ne sert qu'une fois, est remplacé tant que l'écran reste ouvert et retiré quand il se ferme ; le relais n'en garde que le hachage. Un appareil qui le présente ne reçoit encore rien : le relais demande à l'appareil qui a affiché le code, par la connexion de cet appareil, en nommant le nouvel appareil tel qu'il se déclare, et seul un Autoriser venu de la session qui a créé le code remet une session au nouvel appareil, une seule fois. Refuser, ou une minute sans réponse, le refuse. Chaque association envoie ensuite au compte le même avis qu'en 10.9, avec son lien Déconnecter. La comparaison des réseaux de 10.9 ne s'applique pas : l'Autoriser est l'approbation.
Ce qui reste :
- Qui lit un code valide peut demander à entrer. Un code envoyé en capture d'écran, ou lu sur un écran, est une demande ; il ne devient un appareil rattaché que si la personne devant l'ordinateur appuie sur Autoriser pour un appareil qu'elle n'attendait pas.
- Le nom du nouvel appareil sur l'écran Autoriser est celui qu'il déclare, et un attaquant le choisit. L'écran le présente comme tel.
11. Algorithmes et bibliothèques
| usage | algorithme | norme | implémentation |
|---|---|---|---|
| Contenu, manifeste, échanges | AES-256-GCM | FIPS 197, SP 800-38D | AWS-LC (aws-lc-rs) ; WebCrypto dans le navigateur |
| Sous-clés | HKDF-SHA-256 | RFC 5869 | AWS-LC (aws-lc-rs) ; WebCrypto dans le navigateur |
| Accusé de réception | HMAC-SHA-256 | RFC 2104 | AWS-LC (aws-lc-rs) ; WebCrypto dans le navigateur |
| Identité de l'appareil | X25519 | RFC 7748 | crypto_box |
| Accord de clés À proximité | X25519 | RFC 7748 | AWS-LC (aws-lc-rs) |
| Enveloppe pour un appareil | crypto_box_seal (X25519, XSalsa20-Poly1305) | NaCl / libsodium | crypto_box |
| Dérivation de clé À proximité, identifiant de charge utile | SHA-256 | FIPS 180-4 | AWS-LC (aws-lc-rs) |
| Preuve d'identité À proximité, confirmation du code | HMAC-SHA-256 | RFC 2104 | AWS-LC (aws-lc-rs) |
| Code de premier contact À proximité | SPAKE2, groupe Ed25519 | Abdalla et Pointcheval, 2005 | spake2 (RustCrypto) ; le code, AWS-LC (aws-lc-rs) |
| Clés aléatoires | générateur du système d'exploitation | — | clés de transfert et nonces : AWS-LC (aws-lc-rs), alimenté par le système d'exploitation ; clés d'appareil : le générateur du système d'exploitation (getrandom) ; WebCrypto getRandomValues |
| Connexion au relais | TLS | RFC 8446, RFC 5246 | rustls, sur AWS-LC ; le navigateur |
| Connexion directe | DTLS, SCTP, ICE | RFC 6347, RFC 8831, RFC 8445 | str0m (son DTLS sur AWS-LC, son SCTP sctp-proto) ; le navigateur |
| Connexion directe entre deux applications | QUIC, TLS 1.3, Ed25519 | RFC 9000, RFC 8446, RFC 8032 | iroh 1.3.0, compilé depuis une copie embarquée avec deux modifications de peeza qui ne touchent à aucune cryptographie (où une application peut émettre quand À proximité est désactivé ; la libération d'une redirection de port du routeur) ; rustls sur AWS-LC |
Toutes les applications natives intègrent le même moteur : ces bibliothèques sont donc celles de chaque application.
Aucun algorithme de chiffrement ni de hachage propriétaire n'est utilisé. Les constructions propres à peeza sont la dérivation des clés, la disposition des trames et des nonces (section 4), la dérivation À proximité (section 6) et la vérification des étiquettes (7.3), toutes décrites ici en entier.
12. Vecteurs de test
Copie lisible par machine : protocol-vectors.json, à côté de ce fichier.
12.1 Transfert
- clé (base64url)
0123456789abcdefghijklmnopqrstuvwxyzABCDEFG- clé (hex)
d35db7e39ebbf3d69b71d79f8218a39259a7a29aabb2dbafc31cb30010831051
Sous-clés (4.1) :
- content
fd56a64a4093b99e5ec425d7ba13af9cc7a7572dc866bac19292c4416dc7845f- manifest
730c07adf6cf7e1f58b41bb8477e80348c24667cd7f7be1079eabe573c3b0527- receipt
e00ca01a2ae2d409131175a91434c2aa07eaa9723a5745bdebe7bdf7e06016e8- delta
b2b94aec98c41dc6cae53f9ce52ddc0739eb0ff44e064c4e4d2b7a42231c351a- tree
8eba0b899da8a63cde00c842d60b816b1c48c3aa2be9e868178c04651fdd7187- adopt
e03607cade4705487825892ab5c5035aaa018691d64c54c7d594858ac615a5b6
tree est réservé et aucune application ne l'envoie (4.6) ; son vecteur reste pour que la dérivation de cet usage demeure fixée.
Manifeste, sous-clé manifest, nonce(0x08, 0), données additionnelles peeza-e2e-v2 :
- texte clair
{"n":"holiday photos.zip","s":6000968,"t":1789092766750,"p":true,"d":true,"f":1}- scellé
GNgoteoie4R2WzT1GoZaS9A0K_tu6q_eJjfDXkehJuJy5cJRQ5rdYGRu-pYmjzwa4OO4wPe2g_Ff9xz8wg9S506dlCAKB188GCfib30q4QmhRnFsgqnQlGMOvg0s3D3v
Accusé de réception, sous-clé receipt, total 6000968, identifiant de salle 1WMcraeO :
- accusé
jOm_VZChG4YBf732hlJaRTdNn7m614lH7It3TsuGDUs
Bloc numéro 2 d'une charge utile d'une longueur totale de 200000 octets, sous-clé content :
- texte clair
peeza test chunk- nonce
000000000000000000000002- aad
7065657a612d6532652d76320000000000030d40- trame
0000000000000002fc31fbdf72482c593b92cd5a50c2898992d7076c047892f232c67c1c22977126
Message d'un échange, sous-clé adopt, du destinataire vers l'expéditeur (octet de sens 0x06). Le nonce est aléatoire sur le réseau ; ce vecteur en fixe un :
- texte clair
{"t":"have?","chunk":64512,"total":6000968}- nonce
a0a1a2a3a4a5a6a7a8a9aaab- aad
7065657a612d6532652d763206- message
oKGio6SlpqeoqaqrwUBzu3zdCi4l6cVFeYIZzwgbVW7AqFD51XT4Q9Y9uK22jf5-DoMmiMLie37QwspXxfnGTC4Kc3TyxQY
12.2 À proximité
Les clés privées sont faites de 32 octets identiques : identité de A 0x11…, clé éphémère de A 0x22…, identité de B 0x33…, clé éphémère de B 0x44….
- A identité publique
7b4e909bbe7ffe44c465a220037d608ee35897d31ef972f07f74892cb0f73f13- A éphémère publique
0faa684ed28867b97f4a6a2dee5df8ce974e76b7018e3f22a1c4cf2678570f20- B identité publique
7b0d47d93427f8311160781c7c733fd89f88970aef490d8aa0ee19a4cb8a1b14- B éphémère publique
ff2ee45601ec1b67310c7790404585ae697331eee1c1f8cf2419731c1fff3e6b- transcription
e6b1a2e4f06d1e769400a2459be89328ba6096524a5b4baa2c7dd6c82c330ec8- clé de session
dbbf27b17e4abc643def33f775d3db66e01c30df35b963fdf931f71c13dca43d- preuve de A
114df8a11475dbb483a227755faca35f66357e2e6e700f57a744c267f43f36d0- preuve de B
5d065f64a6fe019e791d6aba2ba1464db8ca34b9667c2229248cdac1d673ad4d- sous-clé content
2893ccbfb07b59a85c509f5399c3b693ee3496785831291acfbf040e6f688b23
Les confirmations du code, avec K fixé à 32 octets 0x55 sur cette transcription (les messages de SPAKE2 sont aléatoires et n'ont donc pas de vecteur ici) :
- confirm_a
a0f3251a6ec461a44b3da17a9ed258347494f90bbe7f12a321e20181e8057a46- confirm_b
040b91ce477fcbf4cbf634845cf189117b841498477b6b94b55e83fe83145b15
13. Signaler un problème
Signalements de sécurité : security@peeza.app. La politique de divulgation et les engagements envers les chercheurs sont à https://peeza.app/security. Si ce document se trompe sur ce que font les applications, c'est aussi un signalement de sécurité.
14. Modifications
| version | date | modification |
|---|---|---|
| 1 | 2026-09-30 | Première publication. Décrit la version du format peeza-e2e-v1, avec le manifeste et l'accusé de réception obligatoires. |
| 2 | 2026-10-04 | Section 9 : le relais remet à l'expéditeur de chaque salle une preuve et en garde le hachage. 10.11 : un expéditeur qui rejoint une salle n'a pas encore à la présenter. Le format scellé ne change pas. |
| 3 | 2026-10-05 | 10.9 : connecter un appareil depuis un autre réseau que celui du navigateur qui a terminé la connexion attend une approbation par courriel, et chaque connexion fait l'objet d'un avis par courriel avec un lien qui déconnecte cet appareil ; ce qui reste est un hameçonnage depuis le même réseau, et la part des applications n'est pas encore publiée. Section 9 : les adresses comparées, gardées en mémoire pendant 15 minutes, et le fournisseur de courriels. Le format scellé ne change pas. La traduction française est publiée avec cette version. |
| 4 | 2026-10-07 | Version du format peeza-e2e-v2, avec l'avis de sécurité ci-dessous. 4.1 : chaque usage a sa propre sous-clé HKDF. 4.2, 4.6 : les échanges prennent un nonce aléatoire par message. 4.5 : l'accusé de réception est un HMAC. 4.4 : un message « meta » indique wire: 2, et un second manifeste sous une même clé est refusé. Le nom de fichier scellé hérité disparaît. 7.1 renvoie à 10.1. Les sections 11 et 12 suivent. |
| 5 | 2026-10-07 | L'avis consigne le nouveau test de la version 2 par l'évaluateur. 4.2 et 7.3 : un appareil d'équipe qui vérifie une copie modifiée scelle le bloc changé en mémoire avant de le jeter, et un nonce aléatoire se répète avec une probabilité négligeable plutôt que jamais ; la phrase de l'avis sur le test dit ce qu'il couvre. Le format scellé ne change pas. |
| 6 | 2026-10-07 | 10.9 : la part des applications (l'écran « Consultez vos e-mails » et le nom de l'appareil dans les courriels) est publiée depuis les versions du 2026-10-06 ; ce qui reste concerne les applications plus anciennes. Le format scellé ne change pas. |
| 7 | 2026-10-12 | Section 9 : un appareil qui passe par le relais sans compte présente son identifiant de machine quand il ouvre ou rejoint une salle, et le relais en garde un hachage à clé avec le décompte (un expéditeur sans identifiant est compté par adresse IP, en mémoire seulement) des octets relayés, pendant 30 jours, pour le tenir à l'allocation sans compte. Le format scellé ne change pas. |
| 8 | 2026-10-12 | 5.2 et section 9 : pendant qu'un appareil qui envoie attend une livraison adressée à un appareil, le relais lui dit si cet appareil est connecté et s'il garde la livraison en attente d'une acceptation, à partir de ce que le relais détient déjà. Le format scellé ne change pas. |
| 9 | 2026-10-18 | Section 6 : la découverte « À proximité » s'annonce aussi en IPv6, vers le groupe de lien local ff02::7065:657a sur le même port, et le document dit à quelle adresse un appareil se connecte en retour. L'annonce et le format scellé ne changent pas. |
| 10 | 2026-10-18 | Section 8 : entre deux applications sur le nouveau moteur de peeza — pas encore dans une application publiée — un transfert passe par QUIC (iroh), par le relais iroh de peeza jusqu'à ce qu'un chemin direct s'ouvre, avec les trames scellées de la section 4 inchangées. Section 9 : ce que voit ce relais, et l'enregistrement que le relais garde par ticket. 10.12 : comment ses octets sont comptés. La section 11 suit. Le format scellé ne change pas. |
| 11 | 2026-10-18 | 5.2 et section 9 : une livraison à un seul appareil d'un membre de l'équipe, plutôt qu'un fichier partagé avec l'équipe, est toujours en attente d'une acceptation sur cet appareil, et le relais indique à cet appareil quel compte l'a envoyée. Le format scellé ne change pas. |
| 12 | 2026-10-20 | Section 8 : sur le flux QUIC du nouveau moteur, un request du destinataire par la salle déplace le flux à une autre position, et le flux reste ouvert jusqu'à la fin du transfert. Le format scellé ne change pas. |
| 13 | 2026-10-22 | Section 9 : quand un appareil connecté à un compte se déconnecte, le relais garde en mémoire sa dernière liste de transferts reflétée jusqu'à 7 jours, pour que le tableau de bord puisse l'afficher pendant son absence. Le format scellé ne change pas. |
| 14 | 2026-10-22 | Toutes les applications utilisent désormais le moteur Rust que la version 10 appelait nouveau, si bien que les sections 4.1, 8, 9, 10.12 et 11 ne le disent plus non publié ; tree est réservé et aucune application ne l'envoie (4.1, 4.2, 4.6) ; la section 11 nomme les bibliothèques de ce moteur. Le format scellé ne change pas. |
| 15 | 2026-10-23 | 6 et 10.3 : deux appareils à proximité connectés au même compte s'épinglent d'après la liste d'appareils du relais, sans code, quand l'identifiant d'appareil et la clé d'identité concordent tous deux. Le format scellé ne change pas. |
| 16 | 2026-10-23 | 6 : au premier contact, l'appareil qui envoie affiche un code aléatoire et la personne devant l'appareil qui reçoit le saisit ; les deux le confirment par SPAKE2 sur la transcription de l'échange, et trois écarts mettent fin à l'association. Le code n'est plus dérivé de la transcription, et une association demande le code dès qu'un des côtés ne connaît pas l'autre. Le protocole À proximité passe en version 2 (peeza-lan-v2) ; un appareil en version 1 est refusé. 11 et 12.2 suivent. Le format scellé ne change pas. |
| 17 | 2026-10-25 | 10.13 : un appareil connecté à un compte peut afficher un code QR à usage unique qu'un autre appareil scanne pour rejoindre le compte ; le relais le demande à l'appareil qui l'affiche, et seul son Autoriser remet une session, suivie de l'avis de connexion de 10.9. Section 9 : le hachage du code et la demande, en mémoire pendant au plus 3 minutes. Le format scellé ne change pas. |
| 18 | 2026-10-28 | Section 8 : entre deux applications, le message iroh de l'application qui envoie nomme les adresses directes de son point de connexion, l'application qui reçoit les joint aussitôt, et l'application qui envoie retient la première trame jusqu'à deux secondes pour un chemin direct avant que le relais iroh ne la porte. Section 9 : ces adresses passent par le relais. Le format scellé ne change pas. |
| 19 | 2026-10-29 | Section 9 : pour une équipe, le relais garde aussi quel membre a invité chaque adresse en attente quand ce n'est pas le propriétaire, et si le propriétaire laisse les membres inviter. Le format scellé ne change pas. |
| 20 | 2026-10-31 | La section 2 liste le chemin QUIC entre deux applications à côté des trois autres. La section 11 dit quel iroh est compilé, et que les deux modifications de peeza n'y touchent à aucune cryptographie. Le format scellé ne change pas. |
| 21 | 2026-11-04 | Section 9 : le relais compare les deux bouts d'un transfert et dit aux deux, en un bit, s'ils sont sur le même réseau ; il ne garde ni les adresses ni la réponse. Le format scellé ne change pas. |
| 22 | 2026-11-05 | 4.1, 4.2, 4.6 : un nouvel usage, from : une application qui ouvre un lien public donne à l'appareil expéditeur le nom de son appareil, scellé, dans sa request. Section 9 : le relais voit la longueur de ce champ scellé, et le reflet de la liste des transferts d'un expéditeur connecté nomme cet appareil comme il nomme déjà les autres. Le format scellé du contenu et des manifestes ne change pas ; les applications plus anciennes ignorent le champ. |
| 23 | 2026-11-06 | 6 : un appareil qui n'est pas connecté à un compte garde l'épingle qu'une concordance crée, et sa connexion suivante à cet appareil ne demande aucun code. Le format ne change pas ; le champ anon du bonjour est toujours envoyé, et les applications plus anciennes le lisent pour n'épingler rien. |
Avis de sécurité, 2026-10-07 : l'accusé de réception et trois échanges scellés réutilisaient des nonces AES-GCM.
Le problème. Dans la version du format peeza-e2e-v1, chaque message scellé utilisait la clé de transfert elle-même, et quatre sortes de messages pouvaient sceller deux entrées différentes sous une même clé et un même nonce :
- L'accusé de réception (4.5 de la version 3) scellait un texte fixe sous un nonce fixe ; seul l'identifiant de salle, dans les données additionnelles, changeait. Toutes les livraisons d'un même partage d'équipe utilisent une même clé (7.1) : deux livraisons terminées faisaient donc deux messages AES-GCM sous un même nonce et une même clé. Le relais transmet les deux. À partir de deux tels accusés, sans la clé, on peut fabriquer un accusé que l'expéditeur accepte pour une troisième salle — une livraison annoncée qui n'a pas eu lieu. L'affirmation 4 ne tenait pas pour les livraisons d'équipe.
- Les trois petits échanges (4.6) comptaient leur nonce à partir de zéro à chaque connexion, sous une clé qui survit à toutes les connexions : une reconnexion, un redémarrage ou une livraison sœur pouvait donc sceller un autre message sous un nonce déjà utilisé. La vérification « même fichier » passe par le relais quand il n'y a pas de chemin direct.
- Le nom de fichier scellé hérité, qu'un appareil d'équipe scellait de nouveau en décrivant une charge utile ; une copie renommée à l'arrivée (« photo 2.jpg ») scellait un autre nom sous le nonce de l'original.
Réutiliser un nonce AES-GCM sous une même clé avec des entrées différentes expose la sous-clé d'authentification de cette clé. Comme les accusés et le contenu étaient scellés sous la même clé, quiconque détenait deux accusés d'un même partage d'équipe pouvait, au pire, modifier aussi sans être détecté les trames de contenu relayées de ce partage. Cela n'exposait ni le contenu ni les clés : rien de ce qui était scellé ne pouvait être lu. Nous n'avons pas mesuré si un parcours livré répétait un nonce d'échange avec un contenu différent, et nous supposons que c'était possible.
Versions concernées. Toute application et tout client web qui parle peeza-e2e-v1 avec l'accusé de réception obligatoire : depuis la publication du 2026-09-28 — Mac 0.2.66, macOS 12–13 1.0.0-beta.68, Windows 1.0.6, Linux 0.0.3, iOS et Android build 2786, et le client web servi par le relais ce jour-là — jusqu'à la dernière version publiée avant le correctif.
Correctif dans la version du format peeza-e2e-v2 (ce document) : chaque usage a sa propre clé, l'accusé de réception est un HMAC sans nonce, les échanges prennent un nonce aléatoire par message, le nom hérité disparaît, et un test enregistre chaque scellement des livraisons d'équipe, des reconnexions et des redémarrages qu'il scénarise, et échoue dès qu'un couple clé et nonce scelle deux entrées différentes. Versions corrigées : la publication qui porte la version du format peeza-e2e-v2, et le client web servi par le relais déployé avec elle ; leurs numéros figurent sur https://peeza.app/security. Une application v1 ne peut pas échanger de fichiers avec une application v2, et indique que les deux côtés doivent être à jour.
Vérifié. Le même évaluateur a refait sa reproduction contre la version 2 le 2026-10-07, au commit source 2f29172e : aucun accusé forgé n'a été accepté, ni par le moteur ni par l'expéditeur web, sur 100 essais de chaque variante (une autre salle, un autre total, des rejeux, des accusés de la version 1), alors que la version 1 en acceptait 100 sur 100. Ce nouveau test porte sur la construction de l'accusé et sur le code source ; il ne certifie pas les versions publiées.
Remerciements. Trouvé par une évaluation externe du produit et de la sécurité de peeza, menée avec OpenAI Codex (rapport du 2026-10-07). Les cas des échanges et du nom de fichier ont été trouvés en corrigeant le premier.
peeza était en bêta privée, sans utilisateurs extérieurs, quand le problème a été trouvé.