feat(android): adopt the platform PBKDF2, record the timing and what follows from it
Measured on device: Android derives the same key as WebCrypto on a non-ASCII password, so PBEKeySpec does encode as UTF-8 - the doubt that justified the Pascal implementation was unfounded. It is barely faster though, 3869 ms against 4139 ms: PBKDF2 gets no hardware acceleration. So 4 seconds per container is incompressible, and the iteration count belongs to the desktop format. The answer is not to speed up the derivation but to stop repeating it: derive once per snapshot, then keep the vault locally under an Android Keystore key. Written up in android-client/README.md.
This commit is contained in:
@@ -374,6 +374,13 @@ begin
|
||||
if (Length(LSalt) = 0) or (Length(LIv) = 0) or (Length(LCt) = 0) then
|
||||
raise EPMACrypto.Create('Container is missing salt, iv or ciphertext');
|
||||
|
||||
// Android's provider first — measured identical to WebCrypto on a
|
||||
// non-ASCII password (so it does encode as UTF-8), and it is platform
|
||||
// code rather than ours. Barely faster though: 3869 ms against 4139 ms,
|
||||
// PBKDF2 gets no hardware acceleration. The Pascal path stays as the
|
||||
// fallback, and is the only one outside Android.
|
||||
LKey := DeriveKeyPBKDF2Native(APassword, LSalt, LIterations, 32);
|
||||
if LKey = nil then
|
||||
LKey := DeriveKeyPBKDF2(APassword, LSalt, LIterations, 32);
|
||||
LPlain := AesGcmDecrypt(LKey, LIv, LCt);
|
||||
Result := TEncoding.UTF8.GetString(LPlain);
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# Client Android — état et décisions
|
||||
|
||||
Lecture seule de l'instantané chiffré du coffre. La crypto est **validée sur
|
||||
appareil réel** contre un vecteur produit par WebCrypto.
|
||||
|
||||
## Ce qui est mesuré, pas supposé
|
||||
|
||||
| Question | Réponse | Mesure |
|
||||
|---|---|---|
|
||||
| Le conteneur s'ouvre-t-il en Delphi ? | oui | clair de 572 octets, empreinte identique |
|
||||
| AES-GCM par JNI fonctionne-t-il ? | oui | `javax.crypto`, tag concaténé, rien à découper |
|
||||
| Android encode-t-il le `char[]` en UTF-8 ? | **oui** | clé identique sur un mot de passe non-ASCII |
|
||||
| Le PBKDF2 natif est-il plus rapide ? | **à peine** | 3869 ms contre 4139 ms |
|
||||
|
||||
Le natif est adopté — c'est du code de plateforme plutôt que le nôtre — avec le
|
||||
Pascal en repli, seul disponible hors Android.
|
||||
|
||||
## La conséquence architecturale
|
||||
|
||||
**4 secondes pour ouvrir un instantané, et c'est incompressible.** PBKDF2 ne
|
||||
bénéficie d'aucune accélération matérielle, et on ne peut pas baisser les
|
||||
600 000 itérations : elles sont inscrites dans le conteneur produit par le
|
||||
poste Windows.
|
||||
|
||||
Le piège serait de payer ces 4 secondes à **chaque ouverture de
|
||||
l'application**. Ce n'est pas nécessaire, parce que le coût est lié au
|
||||
**conteneur**, pas à la session — et chaque export génère un **nouveau sel**,
|
||||
donc une mise en cache par mot de passe ne servirait à rien.
|
||||
|
||||
L'architecture qui en découle :
|
||||
|
||||
1. **À la synchronisation** — télécharger, dériver (4 s, une fois), déchiffrer.
|
||||
2. **Puis stocker le coffre localement** chiffré sous une clé du **Android
|
||||
Keystore**, adossée à la biométrie. AES est accéléré matériellement : la
|
||||
relecture est instantanée.
|
||||
3. **À l'ouverture** — biométrie, puis lecture locale. Aucune dérivation.
|
||||
|
||||
Les 4 secondes ne se paient donc qu'à l'arrivée d'un instantané **nouveau**,
|
||||
ce qui est acceptable et compréhensible pour l'utilisateur.
|
||||
|
||||
> Le coffre déchiffré contient **tous les mots de passe en clair**. Il ne doit
|
||||
> jamais toucher le disque autrement que chiffré sous la clé du Keystore, et la
|
||||
> sauvegarde automatique Android doit être désactivée pour l'application.
|
||||
|
||||
## Fichiers
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| `PMA.Crypto.pas` | PBKDF2 (Pascal + natif JNI), AES-GCM, ouverture du conteneur |
|
||||
| `PMA.TestVector.pas` | le vecteur embarqué, pour le banc d'essai |
|
||||
| `make-testvector.js` | régénère le vecteur avec WebCrypto |
|
||||
| `testvector.md` | valeurs attendues, étage par étage |
|
||||
|
||||
Le banc d'essai est `mainApk.pas`, à la racine du dépôt.
|
||||
|
||||
## Piège de compilation rencontré
|
||||
|
||||
Cette installation de Delphi ne produisait **aucun** APK FireMonkey
|
||||
fonctionnel : `fmx.dex.jar` avait été renommé sur disque **et** retiré de la
|
||||
configuration de l'EDI. Symptôme : l'application s'installe puis se ferme
|
||||
aussitôt, avec `ClassNotFoundException: FMXNativeActivity` dans `adb logcat`.
|
||||
Le projet référence désormais explicitement `fmx.dex.jar` dans `EnabledSysJars`.
|
||||
|
||||
Après un tel changement, **supprimer les `classes*.dex` intermédiaires** : la
|
||||
compilation les réutilise sans les régénérer, et le correctif semble sans effet.
|
||||
Reference in New Issue
Block a user