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ésPoints 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.