Modèle Cryptographique Application Mobile¶
Ce document décrit les aspects cryptographiques spécifiques à l'application mobile. Voir le modèle crypto global pour la vue d'ensemble.
Rôle de l'application¶
Dans l'architecture Zero-Knowledge, l'application est responsable de :
- Dériver les clés à partir du password (Argon2id)
- Chiffrer les documents avant upload (AES-256-GCM)
- Déchiffrer les documents téléchargés
- Calculer les hash (SHA3-256)
- Stocker de manière sécurisée les clés (Secure Store)
Hiérarchie des clés (côté client)¶
Password (entrée utilisateur)
│
▼ Argon2id (64 MiB, t=3, p=4)
│
K_encryption (32 bytes)
│
▼ AES-256-GCM decrypt
│
K_master_user (32 bytes) ──── stocké chiffré (Secure Store)
│
├──▶ HKDF-SHA3-256(doc_id) ──▶ K_doc (32 bytes)
│ │
│ ▼ AES-256-GCM
│ │
│ Document chiffré
│
└──▶ HKDF-SHA3-256(share_id) ──▶ K_share (partage)
Implémentation des primitives¶
Argon2id (keyDerivation.ts)¶
import argon2 from 'argon2-browser';
export async function deriveEncryptionKey(
password: string,
salt: Uint8Array
): Promise<Uint8Array> {
const result = await argon2.hash({
pass: password,
salt: salt,
time: 3, // iterations
mem: 65536, // 64 MiB
parallelism: 4,
hashLen: 32,
type: argon2.ArgonType.Argon2id,
secret: new TextEncoder().encode('ProbatioVault_Encryption_v1'),
});
return new Uint8Array(result.hash);
}
HKDF-SHA3-256 (hkdf.ts)¶
import { sha3_256 } from 'js-sha3';
export async function deriveDocumentKey(
kMaster: Uint8Array,
docId: string
): Promise<Uint8Array> {
const salt = uuidToBytes(docId);
const info = new TextEncoder().encode('ProbatioVault::K_doc::v1');
// HKDF-Extract
const prk = hmacSha3(salt, kMaster);
// HKDF-Expand
return hkdfExpand(prk, info, 32);
}
AES-256-GCM (encryption.ts)¶
export async function encryptGCM(
plaintext: Uint8Array,
key: Uint8Array
): Promise<{ ciphertext: Uint8Array; iv: Uint8Array; tag: Uint8Array }> {
const iv = await generateIV(12);
const cryptoKey = await crypto.subtle.importKey(
'raw', key, 'AES-GCM', false, ['encrypt']
);
const result = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
cryptoKey,
plaintext
);
// GCM: derniers 16 bytes = tag
const ciphertext = new Uint8Array(result.slice(0, -16));
const tag = new Uint8Array(result.slice(-16));
return { ciphertext, iv, tag };
}
SHA3-256 (hashing.ts)¶
import { sha3_256 } from 'js-sha3';
export function hashDocument(data: Uint8Array): string {
return sha3_256(data);
}
export function isValidHashFormat(hash: string): boolean {
return /^[a-f0-9]{64}$/.test(hash);
}
Stockage sécurisé¶
Secure Store (iOS Keychain / Android Keystore)¶
import * as SecureStore from 'expo-secure-store';
// Stockage Master Envelope (chiffrée)
await SecureStore.setItemAsync('pv_keystore_v4', JSON.stringify({
v: 'pv-keystore-v4',
kdf: { alg: 'Argon2id', salt: base64(salt) },
encMaster: { iv: base64(iv), ct: base64(ct), tag: base64(tag) },
}));
// Récupération
const keystoreJson = await SecureStore.getItemAsync('pv_keystore_v4');
const keystore = JSON.parse(keystoreJson);
Données stockées¶
| Clé | Contenu | Protection |
|---|---|---|
pv_keystore_v4 | Master Envelope | Keychain/Keystore |
pv_jwt | JWT Token | Keychain/Keystore |
pv_user_salt | Salt utilisateur | Keychain/Keystore |
Gestion mémoire¶
Zeroize¶
export function zeroize(buffer: Uint8Array): void {
// Écraser avec des zéros
buffer.fill(0);
// Forcer le garbage collector (best effort)
if (typeof global.gc === 'function') {
global.gc();
}
}
Lifecycle des clés¶
// ❌ MAUVAIS : K_doc persiste
const kDoc = await deriveDocumentKey(kMaster, docId);
const plaintext = await decrypt(ciphertext, kDoc);
// kDoc reste en mémoire !
// ✅ BON : K_doc effacé après usage
const kDoc = await deriveDocumentKey(kMaster, docId);
try {
return await decrypt(ciphertext, kDoc);
} finally {
zeroize(kDoc);
}
Tests et validation¶
Vecteurs de test¶
// RFC 9106 - Argon2id
describe('Argon2id', () => {
it('should match RFC 9106 test vector', async () => {
const result = await deriveEncryptionKey(
'password',
hexToBytes('02020202020202020202020202020202')
);
expect(bytesToHex(result)).toBe('0d640df5...'); // vecteur RFC
});
});
// RFC 5869 - HKDF
describe('HKDF', () => {
it('should match RFC 5869 test vector', () => {
// ...
});
});