Sécurité de Cyora
Modèle de menace, algorithmes utilisés, et comment vérifier toi-même que les promesses de confidentialité sont respectées.
Modèle de menace
Le serveur de stockage de blobs Cyora (Supabase) est considéré comme honest-but-curious : on ne lui fait pas confiance pour respecter la confidentialité du contenu, mais on suppose qu'il ne supprime pas les blobs aléatoirement.
Conséquence : les attaquants externes qui prendraient le contrôle du serveur (par exploit, fuite de credentials, ordre judiciaire abusif) ne pourraient lire aucun contenu car ils n'ont pas la passphrase utilisatrice. Cyora elle-même ne peut pas lire le contenu, pour la même raison.
Algorithmes utilisés
| Étape | Algorithme | Paramètres |
|---|---|---|
| Dérivation de clé depuis passphrase | Argon2id | memory 64 MiB, 3 itérations, 1 lane, hash 256 bits |
| Chiffrement symétrique | AES-256 GCM | nonce 12 bytes, tag MAC 16 bytes |
| Génération de sel et nonce | Random.secure() | 16 bytes sel, 12 bytes nonce, par blob |
| Authentification serveur | Supabase Auth (JWT) | email + mot de passe, expiration 1h refreshable |
| Permissions stockage | Postgres Row Level Security | auth.uid()::text = (storage.foldername(name))[1] |
Argon2id a été choisi parce que c'est l'algorithme officiel de hashing de mots de passe, gagnant du Password Hashing Competition. Résistant aux attaques par GPU et ASIC. Les paramètres (64 MiB, 3 itérations) sont dimensionnés pour ~1-2 secondes de dérivation sur mobile, suffisamment lent pour rendre le brute-force coûteux mais acceptable pour l'UX.
AES-256-GCM est le standard NIST pour le chiffrement authentifié. Le mode GCM intègre un MAC : toute altération du blob (même un bit flip) provoque un échec de déchiffrement, pas une corruption silencieuse.
Format du blob chiffré
┌──────────┬─────────────┬─────────────┬─────────────────────────┐
│ version │ sel │ nonce │ ciphertext + MAC │
│ 1 byte │ 16 bytes │ 12 bytes │ variable │
└──────────┴─────────────┴─────────────┴─────────────────────────┘
La version permet de migrer vers de nouveaux algorithmes plus tard sans casser la compatibilité avec les anciens blobs (ex. passage à Argon2id v1.4 ou ChaCha20-Poly1305).
Sel et nonce sont stockés en clair en préfixe du blob — c'est sans risque, ils sont publics par construction (un sel n'a pas besoin d'être secret, juste unique).
Pipeline upload (depuis ton appareil vers le serveur)
- L'app sérialise le journal corporel local en JSON UTF-8.
EncryptionCodec.encrypt(passphrase, plaintext):- Génère un sel aléatoire de 16 bytes.
- Dérive une clé de 256 bits via Argon2id depuis (passphrase + sel).
- Génère un nonce aléatoire de 12 bytes.
- Chiffre le plaintext en AES-256-GCM.
- Concatène
[v1][salt][nonce][cipher+mac].
- Le blob est uploadé sur
journal-blobs/<ton_user_id>/snapshot.bin. - La RLS Supabase rejette tout autre user qui tenterait de lire ce blob.
Pipeline download (depuis un autre appareil)
- L'app télécharge le blob (RLS valide la permission via le JWT du caller).
EncryptionCodec.decrypt(passphrase, blob):- Lit version, sel, nonce dans le préfixe.
- Dérive la clé via Argon2id depuis (passphrase + sel) — donne la même clé qu'à l'upload si la passphrase est correcte.
- Tente le déchiffrement AES-GCM.
- Si MAC valide → plaintext exposé. Si invalide → exception, aucune donnée déchiffrée.
- Le plaintext JSON est re-désérialisé et hydraté dans la base SQLite locale.
Données stockées localement (sans sync)
Si tu n'actives pas la sync, tes données ne quittent jamais ton appareil. La base SQLite est dans le sandbox app Android (/data/data/com.cyora.cyora/databases/), accessible uniquement par l'app elle-même, et chiffrée par le système si ton téléphone a un mot de passe d'écran de verrouillage (vrai sur quasi tous les Android 6+).
Cyora désactive aussi explicitement la sauvegarde automatique vers Google Drive via android:allowBackup="false" et dataExtractionRules. Donc les données ne fuitent pas dans ton compte Google.
Sous-traitants
Cyora utilise les services suivants uniquement si tu actives les fonctions correspondantes :
| Service | Pays | Rôle | Données reçues |
|---|---|---|---|
| Supabase | Région UE Stockholm | Auth + stockage blobs chiffrés | Email + mot de passe Supabase, blobs opaques |
| Stripe | Irlande / USA | Paiement Premium | Email, moyen de paiement, statut abonnement |
| Cloudflare | Mondial | Hébergement de cyora.app | Logs HTTP minimaux |
Aucun de ces services ne reçoit le contenu de ton journal en clair.
Limites assumées
Pour qu'il n'y ait pas de surprise, voici ce que Cyora ne peut pas protéger contre.
Perte de la passphrase
Si tu oublies ta passphrase, tes données synchronisées sont perdues définitivement. C'est l'invariant cryptographique : on n'a pas de mécanisme de récupération côté serveur, sinon on aurait l'accès en clair. Note ta passphrase quelque part de sûr (gestionnaire de mots de passe, papier dans un coffre).
Compromis physique de l'appareil
Si un attaquant a accès physique à ton téléphone déverrouillé, il peut lire la base SQLite locale. Aucune app ne peut empêcher ça sans hardware-backed encryption qui n'est pas disponible uniformément sur tous les Android.
Brute-force d'une passphrase faible
Argon2id rend l'attaque coûteuse (~1-2 secondes par tentative sur du matériel haute performance), mais une passphrase « 1234 » reste vulnérable à un attaquant motivé. Choisis une passphrase de ≥ 12 caractères, idéalement une phrase mémorable (ex. « le-ciel-est-bleu-12-fois »).
Attaque sur l'app elle-même
Si un attaquant parvient à modifier le binaire de l'app installée sur ton téléphone (par root + injection), il peut intercepter ta passphrase au moment où tu la tapes. C'est un scénario qui dépasse Cyora — pratiquement aucune app ne peut s'en protéger.
Comment vérifier toi-même
Le code source est public sur github.com/jpbenoit/cyora sous licence AGPL-3.0. Fichiers à auditer :
lib/data/sync/encryption_codec.dart— implémentation Argon2id + AES-GCM.lib/data/sync/supabase_sync_service.dart— pipeline upload / download.supabase/functions/— Edge Functions Deno (création de session Stripe, webhook, suppression de compte). Code lisible, pas obfusqué.android/app/src/main/AndroidManifest.xml— permissions Android demandées (limitées àPOST_NOTIFICATIONSpour les rappels etINTERNETpour la sync).pubspec.yaml— dépendances Dart (vérifie l'absence defirebase_analytics,sentry_flutter,mixpanel_flutter,amplitude_flutter,appsflyer_sdk, etc.).
Tu peux aussi builder l'app toi-même à partir des sources et la comparer avec celle distribuée sur le Play Store (build reproductible).
Contact sécurité
Si tu trouves une vulnérabilité, contacte security@cyora.app (ou support@cyora.app). On ne propose pas de bug bounty financière au lancement, mais on accuse réception en 48 h et on fixe les bugs critiques rapidement. Tout chercheur sécurité responsable est crédité dans nos release notes (avec son consentement).