Guide pratique

Protéger les données financières

Scénario réel : une fintech stocke des IBANs, des tokens de carte et des montants de transaction. Ce guide montre comment chiffrer chaque type de données avec une clé dédiée, afficher des données masquées et exporter les journaux d'audit pour l'ACPR (conformité DORA).

Conformité DORA & DSP2

DORA (règlement UE 2022/2554) impose aux entités financières des mesures de protection des données et une traçabilité complète. Ce pattern utilise des clés séparées par type de données pour satisfaire les exigences d'isolation et d'audit.

1. Stratégie de clés par type de données

key-iban

IBANs & BICs

Coordonnées bancaires

key-cards

Tokens carte

Tokens PSP (jamais le PAN complet)

key-transactions

Montants

Montants & références

2. Schéma de la base de données

-- Schéma PostgreSQL : données financières chiffrées
CREATE TABLE bank_accounts (
  id              SERIAL PRIMARY KEY,
  user_id         INTEGER      NOT NULL REFERENCES users(id),
  iban            JSONB        NOT NULL,  -- chiffré avec key-iban
  iban_last4      TEXT         NOT NULL,  -- 4 derniers caractères (affichage masqué)
  bic             JSONB        NOT NULL,  -- chiffré avec key-iban
  created_at      TIMESTAMPTZ  DEFAULT NOW()
);

CREATE TABLE payment_cards (
  id              SERIAL PRIMARY KEY,
  user_id         INTEGER      NOT NULL REFERENCES users(id),
  card_token      JSONB        NOT NULL,  -- token PSP chiffré avec key-cards
  last4           TEXT         NOT NULL,  -- 4 derniers chiffres (affichage)
  exp_month       INTEGER      NOT NULL,
  exp_year        INTEGER      NOT NULL,
  brand           TEXT         NOT NULL,  -- Visa, Mastercard…
  created_at      TIMESTAMPTZ  DEFAULT NOW()
);

CREATE TABLE transactions (
  id              SERIAL PRIMARY KEY,
  user_id         INTEGER       NOT NULL REFERENCES users(id),
  amount_enc      JSONB         NOT NULL,  -- chiffré avec key-transactions
  currency        TEXT          NOT NULL,
  direction       TEXT          NOT NULL,  -- credit | debit
  reference       TEXT          NOT NULL,
  created_at      TIMESTAMPTZ   DEFAULT NOW()
);

3. Chiffrement des IBANs

L'IBAN est chiffré avant insertion. Seuls les 4 derniers caractères sont stockés en clair pour l'affichage masqué (ex: FR•• •••• ••89). Le déchiffrement complet n'est réalisé que pour les virements.

import { Mafate } from '@mafate/sdk'

const mafate = new Mafate({ apiKey: process.env.MAFATE_API_KEY })

// Enregistrer un IBAN chiffré
async function storeIban(userId: number, iban: string, bic: string) {
  // Normaliser : supprimer espaces, mettre en majuscules
  const normalizedIban = iban.replace(/\s/g, '').toUpperCase()

  // Chiffrer avec la clé dédiée aux données bancaires
  const [encIban, encBic] = await Promise.all([
    mafate.encryptLocal(normalizedIban, 'key-iban'),
    mafate.encryptLocal(bic,            'key-iban'),
  ])

  // Garder les 4 derniers caractères pour affichage masqué
  const ibanLast4 = normalizedIban.slice(-4)

  await db.query(
    `INSERT INTO bank_accounts (user_id, iban, iban_last4, bic)
     VALUES ($1, $2, $3, $4)`,
    [userId, JSON.stringify(encIban), ibanLast4, JSON.stringify(encBic)]
  )
}

// Affichage masqué sans déchiffrement
async function getMaskedAccounts(userId: number) {
  const rows = await db.query(
    'SELECT id, iban_last4, created_at FROM bank_accounts WHERE user_id = $1',
    [userId]
  )
  // → "FR•• •••• •••• •••• •••• •••X XXXX" (seulement les 4 derniers)
  return rows.rows.map(r => ({
    id: r.id,
    maskedIban: `FR•• •••• •••• •••• ••${r.iban_last4}`,
    createdAt: r.created_at,
  }))
}

// Déchiffrer l'IBAN complet (ex: pour un virement)
async function getFullIban(accountId: number): Promise<string> {
  const row = await db.query(
    'SELECT iban FROM bank_accounts WHERE id = $1',
    [accountId]
  )
  return await mafate.decryptLocalToString(JSON.parse(row.rows[0].iban))
}

4. Export audit pour l'ACPR

MAFATE journalise chaque opération de chiffrement et déchiffrement. Exportez les journaux pour répondre aux exigences DORA de traçabilité sur 5 ans.

-- Export d'audit pour l'ACPR (Autorité de contrôle prudentiel)
-- Toutes les opérations sur les données financières sur 90 jours
SELECT
  al.id,
  al.operation,       -- encrypt | decrypt | rotate
  al.key_id,
  al.performed_at,
  al.api_key_id,
  al.source_ip,
  al.metadata->>'userId' AS user_id
FROM audit_logs al
WHERE al.key_id IN ('key-iban', 'key-cards', 'key-transactions')
  AND al.performed_at >= NOW() - INTERVAL '90 days'
ORDER BY al.performed_at DESC;

5. Rotation des clés financières

Recommandation : rotation automatique tous les 180 jours pour key-iban et key-cards. Après rotation, les anciens ciphertexts restent déchiffrables grâce au key_version stocké, aucune migration de données requise.