Guide pratique

Intégration ORM : chiffrement transparent

Rendez le chiffrement complètement invisible pour vos développeurs en l'intégrant au niveau de l'ORM. Prisma, SQLAlchemy, GORM, Eloquent, ActiveRecord et Diesel : exemples complets et prêts à copier.

L'objectif : zéro friction pour les développeurs

Une fois l'ORM configuré, les développeurs écrivent du code normal sans se soucier du chiffrement. prisma.user.create({ email: 'x' }) chiffre automatiquement. prisma.user.findFirst() déchiffre automatiquement. Aucune modification du code métier requise.

Implémentation par ORM

Chaque ORM offre un mécanisme d'extension différent : middleware (Prisma), TypeDecorator (SQLAlchemy), Hooks (GORM), Cast (Eloquent), Concern (ActiveRecord), FromSql/ToSql (Diesel).

// lib/prisma.ts : Client Prisma avec middleware de chiffrement transparent
import { PrismaClient } from '@prisma/client'
import { Mafate } from '@mafate/sdk'

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

// Mapping modèle → champs à chiffrer
const ENCRYPTED_FIELDS: Record<string, string[]> = {
  User:    ['email', 'phone', 'firstName', 'lastName', 'address'],
  Patient: ['fullName', 'birthDate', 'diagnosis', 'ssn'],
  Order:   ['billingEmail', 'shippingAddress'],
}

// Clé par modèle
const MODEL_KEY: Record<string, string> = {
  User:    'key-users-pii',
  Patient: 'key-health',
  Order:   'key-financial',
}

prisma.$use(async (params, next) => {
  const fields = ENCRYPTED_FIELDS[params.model ?? '']
  const keyId  = MODEL_KEY[params.model ?? ''] ?? 'key-app-data'

  // Chiffrement à l'écriture
  if (fields) {
    const writeActions = ['create', 'update', 'upsert', 'createMany', 'updateMany']
    if (writeActions.includes(params.action)) {
      const data = params.args.data ?? params.args
      for (const field of fields) {
        if (data[field] && typeof data[field] === 'string') {
          const enc = await mafate.encryptLocal(data[field], keyId)
          data[field] = JSON.stringify(enc)
        }
      }
    }
  }

  const result = await next(params)

  // Déchiffrement à la lecture
  if (fields) {
    const readActions = ['findUnique', 'findFirst', 'findMany', 'findUniqueOrThrow']
    if (readActions.includes(params.action)) {
      const rows = Array.isArray(result) ? result : [result]
      for (const row of rows) {
        if (!row) continue
        for (const field of fields) {
          if (row[field] && typeof row[field] === 'string') {
            try {
              const enc = JSON.parse(row[field])
              row[field] = await mafate.decryptLocalToString(enc)
            } catch {
              // Valeur non-JSON = pas encore chiffrée (migration progressive)
            }
          }
        }
      }
    }
  }

  return result
})

export default prisma

// Utilisation, 100% transparent pour le développeur :
// const user = await prisma.user.create({ data: { email: '[email protected]', firstName: 'Jean' } })
// → email et firstName automatiquement chiffrés en base
// const found = await prisma.user.findUnique({ where: { id: 1 } })
// → email et firstName automatiquement déchiffrés

Points de vigilance

  • Ne jamais chiffrer les champs utilisés dans les WHERE, JOIN ou ORDER BY : utilisez un hash HMAC à la place pour la recherche.
  • Les index sur des champs chiffrés ne fonctionnent pas. Créez un champ email_hash séparé avec un index.
  • Les migrations progressives : gérez le cas où une valeur n'est pas encore chiffrée (try/catch sur JSON.parse).
  • Les performances : le chiffrement est asynchrone. Utilisez Promise.all() ou des goroutines pour chiffrer plusieurs champs en parallèle.
  • Ne pas logger les valeurs intermédiaires pendant le chiffrement/déchiffrement.