Aller au contenu

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 :

  1. Dériver les clés à partir du password (Argon2id)
  2. Chiffrer les documents avant upload (AES-256-GCM)
  3. Déchiffrer les documents téléchargés
  4. Calculer les hash (SHA3-256)
  5. 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', () => {
    // ...
  });
});

Liens