Entrixy — chiffrement de bout en bout

Une architecture à connaissance nulle : le serveur ne détient de clés de déchiffrement que pour les identifiants techniques. Tout champ porteur de sens — numéros de téléphone, adresses, coordonnées, secrets de webhook, BSSID Wi-Fi, messages d’accueil — est chiffré sur l’appareil du propriétaire et repose sur le serveur sous forme de blob opaque. La clé de déchiffrement parvient à l’invité dans le lien d’invitation, dans le fragment d’adresse, que le navigateur n’envoie jamais au serveur.

Décidé le 19/04/2026. Le travail avance par étapes — voir la section Phases .

1. Objectifs et limites

2. Primitive cryptographique

Algorithm: AES-256-GCM. Disponible d’origine sur la JVM (via javax.crypto), et pris en charge par l’Android Keystore. Il apporte à la fois confidentialité et authentification.

Format du texte chiffré

v1:<base64url(nonce || ciphertext || tag)>

Source d’entropie

SecureRandom() (celui du système). Sur Android, c’est /dev/urandom + le prng de Linux.

3. Hiérarchie des clés

K_master — la clé maîtresse du propriétaire

K_obj — la clé de l’objet

K_guest — la clé du lot invité

Changing K_guest n’est possible qu’en émettant un nouveau lien. C’est précisément là-dessus que repose la révocation : le serveur supprime l’enregistrement de la clé et l’invité ne reçoit plus de bundle_cipher à jour.

4. Ce qui est stocké où

Sur l’appareil du propriétaire (EncryptedSharedPreferences)

KeyValeur
master_key32 octets en clair (SharedPreferences les chiffre de toute façon).
obj_key_{id}La K_obj de l’objet, enveloppée par K_master.
obj_plain_{id}(facultatif) un cache du JSON déchiffré, pour que l’interface s’affiche vite.

Sur le serveur

Table / champSommaire
numbers.data_cipherJSON chiffré avec K_obj : {phone, url, secret, geo_lat, geo_lon, share_lat, share_lon, snapshot, welcome, label}.
numbers.id / typeEn clair — nécessaire au routage et à l’affichage du type d’icône.
keys.bundle_cipherJSON chiffré avec K_guest : {obj_ids: [1,2,3], obj_keys: {1: K_obj1, 2: K_obj2, ...}, welcome_cipher: ..., ...}.
keys.user_key / mode / force_when_busyEn clair — pour le routage et une vérification rapide de l’accès.
Le message d’accueil de l’invité est chiffré à part dans le lot : le serveur ne le voit pas non plus.
https://entrixy.com/key#<user_key(32)><K_guest_base64url(43)>

6. Principaux parcours

Créer un objet

  1. Le propriétaire appuie sur Ajouter — cela génère K_obj.
  2. Les champs sont empaquetés en JSON et chiffrés avec K_objdata_cipher.
  3. POST /api/number_add avec data_cipher plus le type. Le serveur renvoie un id.
  4. Stocké localement : obj_key_{id} = encrypt_k_master(K_obj).

Mettre à jour un objet

Construire le nouveau JSON localement, le chiffrer avec la K_objPOST /api/number_update. K_obj existante, qui ne change pas.

Créer une clé invité

  1. Le propriétaire a choisi [obj1, obj2, obj3]. Cela génère K_guest.
  2. Le JSON du lot est assemblé : {obj_ids: [...], obj_keys: {1: K_obj1, ...}, welcome_cipher: ...}.
  3. Chiffré avec K_guestbundle_cipher.
  4. POST /api/key_create avec bundle_cipher, obj_ids (en clair — pour la matrice d’accès).
  5. Le serveur renvoie user_key.
  6. Le propriétaire voit un QR code avec le lien entrixy.com/key#{user_key}{base64url(K_guest)}.

L’invité accepte la clé

  1. L’invité ouvre le QR code ou le lien. La page /key reads location.hash et transmet la clé à l’application ou à la version web.
  2. Le client demande POST /api/key_bundle.php avec user_key → reçoit bundle_cipher et la liste des obj_ids.
  3. Déchiffre le lot avec K_guest→ obtenant {obj_keys, welcome}.
  4. K_guest et les déchiffrés obj_keys sont stockés dans EncryptedSharedPreferences du client invité.
  5. Ensuite, à chaque appel : GET du chiffré data_cipher de l’objet → déchiffrement avec la obj_key_{id}.

Révoquer une clé invité

Le propriétaire appuie sur Supprimer → POST /api/key_revoke.php → le serveur supprime l’enregistrement de la clé. À la prochaine demande de lot, l’invité est refusé et perd l’accès.

Faire tourner la clé d’un objet

Le propriétaire appuie sur Régénérer la clé dans les réglages de l’objet. Cela génère K_obj_new, et tous les bundle_cipher contenant cet objet sont rechiffrés. Le client ne les connaît pas par cœur, aussi le client du propriétaire reconstruit les lots : il récupère le bundle_cipher de chacune de ses clés, le déchiffre, insère K_obj_newet rechiffre. Les nouveaux blobs partent au serveur en un seul lot.

7. Sauvegarde et restauration

Cette section décrit un plan qui n’est pas encore réalisé : il n’y a pas d’export de la clé maîtresse dans l’application aujourd’hui.

Losing K_master signifie perdre l’accès à tous les objets et à toutes les clés, d’où la nécessité d’un export.

Sans phrase secrète, le QR code de sauvegarde n’est qu’une K_master, c’est pourquoi la phrase secrète est obligatoire.

8. Phases de déploiement

Phase 1 — le coffre cryptographique côté client

Add Crypto.kt : une enveloppe AES-256-GCM, génération et stockage de K_master, des utilitaires pour K_obj, sérialisation du format v1:<base64url>. Tests unitaires : chiffrer → déchiffrer → comparer. Rien ne part encore au serveur — la tuyauterie est simplement prête.

Phase 2 — migration du schéma serveur (fait)

Tous les champs sensibles ont migré vers numbers.data_cipher et user_keys.bundle_cipher. Les anciennes colonnes en clair (phone, label, radius, time_*, geo_*, wifi_*, share_*, has_avatar, security_level, user_keys.label, hosts.label, pending_actions.phone, hosts.last_ip) ont été supprimées de la base (phase 7). webhook_url et webhook_secret restent en clair UNIQUEMENT quand webhook_mode='server' — sans eux le serveur ne peut pas envoyer la requête HTTP. Avec webhook_mode='phone' ils sont chiffrés dans data_cipher.

Phase 3 — le client lit et écrit le blob

À la création ou à la mise à jour, le client chiffre le JSON et envoie data_cipher. À la synchronisation, il lit le blob et le déchiffre. Les nouveaux objets vivent entièrement dans le blob ; les anciens sans K_obj continuent d’utiliser les champs en clair pour la rétrocompatibilité.

Phase 4 — le lot invité

La création d’une clé génère K_guest, assemble le lot et envoie bundle_cipherau serveur. Le lien porte la clé dans son fragment ; la page /key lit le hash et transmet la clé à l’application.

Phase 5 — sauvegarde et restauration

Un écran de réglages avec Exporter la clé maîtresse : phrase secrète → Argon2id → QR code. Restauration via le scanner, plus des tests de migration entre appareils. Non réalisé.

Phase 6 — migration des données existantes

Une routine unique dans le client au premier lancement de la nouvelle version :

  1. Lire tous ses objets depuis le serveur (les anciens champs en clair).
  2. Générer une K_obj pour chacun d’eux.
  3. Chiffrer les champs et envoyer data_cipher.
  4. Le serveur vide les anciennes colonnes (phone=NULL, …) — mais seulement après confirmation.
Une fois la migration réussie, les champs obsolètes sont supprimés par un ALTER.

Phase 7 — audit

Un audit de sécurité, externe ou interne. On vérifie que le serveur ne voit vraiment pas les données sensibles : un dump de la base ne doit contenir que des blobs.

9. Risques et mesures

RiskMeasure
Perte de K_masterUn parcours de sauvegarde obligatoire (phase 5). Tant qu’aucune copie n’existe, un bandeau d’avertissement s’affiche.
Compromission de l’appareil du propriétairePas totalement évitable : l’entrée dans l’application est protégée par un code PIN ou la biométrie, avec EncryptedSharedPreferences et l’Android Keystore.
Faire tourner la clé d’un objet impose de reconstruire tous les lotsLe client du propriétaire prend tous les bundle_cipher, en local ou depuis le serveur, les reconstruit et les envoie en un lot. Opération rare.
Le serveur altère un lotL’étiquette AES-GCM ne correspondra pas et le client signalera une clé corrompue.
Fuite de K_guest par une capture d’écran du QR codeTechniquement inévitable : la clé est le code lui-même. Ne montrez le QR code qu’à un invité de confiance.
Le fragment d’adresse reste dans l’historique du navigateurLa page /key appelle, juste après avoir lu le hash, history.replaceState(..., '#'), ce qui efface le fragment.

Dernière mise à jour : 19 avril 2026