peeza — protocole et cryptographie

Read in English

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.

La section 10 énumère les limites et les exceptions. Lisez-la avant de citer la section 1.

1. Ce que nous affirmons

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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

partiece que c'est
Expéditeur, destinataireDeux 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.
RelaisUn 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.

chemintransportrôle du relais
DirectCanal de données WebRTC entre les deux appareilssignalisation seulement
Direct, entre deux applicationsQUIC (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 tramessignalisation et transmission
À proximitéWebSocket entre deux appareils du même réseau localaucun ; fonctionne sans Internet

3. Clés et secrets

nomce que c'esttailledurée de vieoù elle se trouve
Clé de transfertLe 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'exploitationun partage ; partager de nouveau crée une nouvelle clél'appareil de l'expéditeur ; celui du destinataire une fois livrée
Sous-clésUne 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 chacunecelle de la clé dont elles dériventmémoire, dérivées au besoin
Clé d'identité de l'appareilPaire de clés X25519 qui désigne une installationcourbe de 255 bitsla vie de l'installationmoitié 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 X25519courbe de 255 bitsune 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 transfert256 bitsune 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 6environ 20 bits ; un essai par codeun essail'é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 lien8 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 compte192 bits aléatoires, émis par le relais à la connexion. Le relais n'en conserve que le SHA-256.
Secret d'enregistrement de l'appareil256 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 )
usageclé pour
contentles blocs de contenu (4.3)
manifestle manifeste de la charge utile (4.4)
receiptl'accusé de réception, une clé HMAC (4.5)
delta, adoptles deux petits échanges en usage (4.6)
fromle nom de l'appareil qui a ouvert un lien (4.6)
treeré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énoncedonnées authentifiées additionnelles
contentun bloc de contenunonce(0x00, numéro du bloc)VERSION ‖ uint64BE(total)
manifestle manifeste de la charge utilenonce(0x08, 0)VERSION
deltanégociation « n'envoyer que ce qui a changé »12 octets aléatoiresVERSION ‖ 0x02 ou 0x03, un octet par sens
treeréservé (négociation de la structure d'un dossier) ; aucune application ne l'envoie—0x04 et 0x05 réservés
adoptvérification « la copie que j'ai déjà est-elle le même fichier ? »12 octets aléatoiresVERSION ‖ 0x06 ou 0x07
fromle nom qu'une application ayant ouvert un lien donne à l'expéditeur12 octets aléatoiresVERSION ‖ 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 :

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.

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çonprotection
Appareil ↔ relaisTLS 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, directCanal 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 applicationsQUIC (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 irohTLS 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 :

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 :

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 :

11. Algorithmes et bibliothèques

usagealgorithmenormeimplémentation
Contenu, manifeste, échangesAES-256-GCMFIPS 197, SP 800-38DAWS-LC (aws-lc-rs) ; WebCrypto dans le navigateur
Sous-clésHKDF-SHA-256RFC 5869AWS-LC (aws-lc-rs) ; WebCrypto dans le navigateur
Accusé de réceptionHMAC-SHA-256RFC 2104AWS-LC (aws-lc-rs) ; WebCrypto dans le navigateur
Identité de l'appareilX25519RFC 7748crypto_box
Accord de clés À proximitéX25519RFC 7748AWS-LC (aws-lc-rs)
Enveloppe pour un appareilcrypto_box_seal (X25519, XSalsa20-Poly1305)NaCl / libsodiumcrypto_box
Dérivation de clé À proximité, identifiant de charge utileSHA-256FIPS 180-4AWS-LC (aws-lc-rs)
Preuve d'identité À proximité, confirmation du codeHMAC-SHA-256RFC 2104AWS-LC (aws-lc-rs)
Code de premier contact À proximitéSPAKE2, groupe Ed25519Abdalla et Pointcheval, 2005spake2 (RustCrypto) ; le code, AWS-LC (aws-lc-rs)
Clés aléatoiresgé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 relaisTLSRFC 8446, RFC 5246rustls, sur AWS-LC ; le navigateur
Connexion directeDTLS, SCTP, ICERFC 6347, RFC 8831, RFC 8445str0m (son DTLS sur AWS-LC, son SCTP sctp-proto) ; le navigateur
Connexion directe entre deux applicationsQUIC, TLS 1.3, Ed25519RFC 9000, RFC 8446, RFC 8032iroh 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

versiondatemodification
12026-09-30Première publication. Décrit la version du format peeza-e2e-v1, avec le manifeste et l'accusé de réception obligatoires.
22026-10-04Section 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.
32026-10-0510.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.
42026-10-07Version 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.
52026-10-07L'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.
62026-10-0710.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.
72026-10-12Section 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.
82026-10-125.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.
92026-10-18Section 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.
102026-10-18Section 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.
112026-10-185.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.
122026-10-20Section 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.
132026-10-22Section 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.
142026-10-22Toutes 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.
152026-10-236 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.
162026-10-236 : 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.
172026-10-2510.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.
182026-10-28Section 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.
192026-10-29Section 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.
202026-10-31La 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.
212026-11-04Section 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.
222026-11-054.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.
232026-11-066 : 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 :

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é.