feat(android): container crypto unit plus a known-answer test vector
PBKDF2 is implemented in Pascal rather than through javax.crypto SecretKeyFactory: PBEKeySpec takes a char[] and the provider decides how those chars become bytes, so a non-ASCII sync password would derive a different key than the browser and fail as 'wrong password'. AES-GCM does go through JNI - one call, hardware-backed, and WebCrypto's tag-appended layout is exactly what Cipher expects. The vector is generated with WebCrypto itself and uses a non-ASCII password on purpose; an ASCII-only vector would have hidden that bug. It records the derived key separately from the plaintext so a failure points at one stage instead of the whole chain.
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
# Vecteur de test — conteneur `pm-encrypted-export-v1`
|
||||
|
||||
`testvector.json` est produit par `make-testvector.js` avec **WebCrypto**, la
|
||||
primitive exacte du navigateur. Sel et IV sont figés, donc le fichier est
|
||||
reproductible : une différence signale un vrai changement de comportement, pas
|
||||
du bruit.
|
||||
|
||||
> Le fichier de test réutilise un couple sel/IV fixe. C'est acceptable pour un
|
||||
> vecteur, **jamais** dans l'application : réutiliser un IV avec la même clé
|
||||
> détruit la sécurité de GCM.
|
||||
|
||||
## Paramètres
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Mot de passe | `sync-test-password-éàü` |
|
||||
| KDF | PBKDF2-SHA256, 600 000 itérations, sel de 32 octets |
|
||||
| Chiffrement | AES-256-GCM, IV de 12 octets, tag de 16 octets **en fin de `ciphertext`** |
|
||||
|
||||
Le mot de passe est **volontairement non-ASCII**. C'est le seul cas qui
|
||||
distingue une implémentation correcte d'une implémentation approximative : Java
|
||||
`PBEKeySpec` prend un `char[]` et laisse le fournisseur choisir l'encodage, ce
|
||||
qui donne une clé différente pour `é`, `à`, `ü`. Un vecteur en pur ASCII aurait
|
||||
laissé passer le bug.
|
||||
|
||||
## Valeurs attendues
|
||||
|
||||
Vérifier **dans cet ordre** — chaque étage isole une cause :
|
||||
|
||||
**1. Clé dérivée** (`DeriveKeyPBKDF2`, avant tout déchiffrement)
|
||||
|
||||
```
|
||||
6bd7683ea78e163062f02681b03f2906fcf5a3a25025005af4e71982e6e2b286
|
||||
```
|
||||
|
||||
Si elle diffère, le problème est dans PBKDF2 ou dans l'encodage du mot de passe
|
||||
— surtout pas dans AES. Contrôler d'abord que le mot de passe est bien encodé
|
||||
en UTF-8 (`sync-test-password-éàü` fait **25 octets**, pas 22 caractères).
|
||||
|
||||
**2. Texte clair** (`AesGcmDecrypt` puis `DecryptContainer`)
|
||||
|
||||
```
|
||||
longueur UTF-8 : 572 octets
|
||||
SHA-256 : ddaee44f8ac8374f1ece6d9ee2abd104d59cd283556f1c4c07afa95ae1dbd2a7
|
||||
début : {"version":1,"exported_at":"2026-08-14T12:00:00.000Z","username":"test
|
||||
```
|
||||
|
||||
Si la clé est bonne et que le déchiffrement échoue, chercher du côté de la
|
||||
longueur du tag (128 **bits**) ou d'un tag qui aurait été séparé du corps —
|
||||
WebCrypto le concatène, `javax.crypto` l'attend concaténé, il n'y a rien à
|
||||
découper.
|
||||
|
||||
## Contenu
|
||||
|
||||
2 entrées, dont **une en corbeille** (`deleted: 1` avec son `deleted_at`), et
|
||||
un dossier avec sa couleur. De quoi vérifier que le client mobile affiche la
|
||||
corbeille séparément au lieu de la mélanger à la liste.
|
||||
|
||||
## Régénérer
|
||||
|
||||
```bash
|
||||
node make-testvector.js > testvector.json
|
||||
```
|
||||
|
||||
En PowerShell, la redirection ajoute un **BOM** qui rend le JSON illisible.
|
||||
Utiliser `cmd`, ou écrire le fichier sans BOM.
|
||||
Reference in New Issue
Block a user