Sécurité des données médicales : Un impératif
Les données de santé sont ultra-sensibles. Une fuite peut avoir des conséquences dramatiques :
Pour Keneya, la sécurité n'est pas optionnelle.
Chiffrement des données
1. Chiffrement en transit (TLS 1.3)
nginx
# nginx.conf
server {
listen 443 ssl http2;
server_name keneya.com;
ssl_certificate /etc/letsencrypt/live/keneya.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/keneya.com/privkey.pem;
ssl_protocols TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers on;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}2. Chiffrement au repos (AES-256)
typescript
import crypto from 'crypto'
class EncryptionService {
private algorithm = 'aes-256-gcm'
private key: Buffer
constructor() {
this.key = Buffer.from(process.env.ENCRYPTION_KEY, 'hex')
}
encrypt(text: string): { encrypted: string; iv: string; tag: string } {
const iv = crypto.randomBytes(16)
const cipher = crypto.createCipheriv(this.algorithm, this.key, iv)
let encrypted = cipher.update(text, 'utf8', 'hex')
encrypted += cipher.final('hex')
const tag = cipher.getAuthTag()
return {
encrypted,
iv: iv.toString('hex'),
tag: tag.toString('hex')
}
}
decrypt(encrypted: string, iv: string, tag: string): string {
const decipher = crypto.createDecipheriv(
this.algorithm,
this.key,
Buffer.from(iv, 'hex')
)
decipher.setAuthTag(Buffer.from(tag, 'hex'))
let decrypted = decipher.update(encrypted, 'hex', 'utf8')
decrypted += decipher.final('utf8')
return decrypted
}
}3. Chiffrement des dossiers médicaux
typescript
// medical-record.service.ts
async saveMedicalRecord(patientId: string, data: MedicalRecordData) {
// Chiffrer les données sensibles
const encrypted = this.encryptionService.encrypt(
JSON.stringify({
diagnosis: data.diagnosis,
prescriptions: data.prescriptions,
notes: data.notes
})
)
await this.medicalRecordRepo.create({
patientId,
encryptedData: encrypted.encrypted,
iv: encrypted.iv,
tag: encrypted.tag,
createdAt: new Date()
})
}
async getMedicalRecord(patientId: string) {
const record = await this.medicalRecordRepo.findByPatient(patientId)
// Déchiffrer
const decrypted = this.encryptionService.decrypt(
record.encryptedData,
record.iv,
record.tag
)
return JSON.parse(decrypted)
}Authentification & Autorisation
1. JWT avec Refresh Tokens
typescript
// auth.service.ts
async login(email: string, password: string) {
const user = await this.userRepo.findByEmail(email)
if (!await bcrypt.compare(password, user.passwordHash)) {
throw new UnauthorizedException('Identifiants invalides')
}
// Access token (15min)
const accessToken = this.jwtService.sign(
{ userId: user.id, role: user.role },
{ expiresIn: '15m' }
)
// Refresh token (7 jours)
const refreshToken = this.jwtService.sign(
{ userId: user.id },
{ expiresIn: '7d' }
)
// Sauvegarder le refresh token (hashé)
await this.tokenRepo.create({
userId: user.id,
tokenHash: await bcrypt.hash(refreshToken, 10),
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
})
return { accessToken, refreshToken }
}2. RBAC (Role-Based Access Control)
typescript
// guards/roles.guard.ts
@Injectable()
export class RolesGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.get<string[]>('roles', context.getHandler())
const request = context.switchToHttp().getRequest()
const user = request.user
return requiredRoles.some(role => user.role === role)
}
}
// consultation.controller.ts
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('doctor')
@Post('prescriptions')
async createPrescription(@Body() dto: CreatePrescriptionDto) {
// Seuls les médecins peuvent créer des prescriptions
}Audit Logs
typescript
// Enregistrer toutes les actions sensibles
@Injectable()
export class AuditService {
async log(event: AuditEvent) {
await this.auditRepo.create({
userId: event.userId,
action: event.action,
resource: event.resource,
resourceId: event.resourceId,
ipAddress: event.ipAddress,
userAgent: event.userAgent,
timestamp: new Date(),
metadata: event.metadata
})
}
}
// Exemple d'utilisation
async viewMedicalRecord(patientId: string, doctorId: string) {
// Enregistrer l'accès
await this.auditService.log({
userId: doctorId,
action: 'VIEW_MEDICAL_RECORD',
resource: 'medical_record',
resourceId: patientId,
ipAddress: req.ip,
userAgent: req.headers['user-agent']
})
return this.medicalRecordService.get(patientId)
}Conformité RGPD
1. Droit d'accès
typescript
@Get('my-data')
async exportMyData(@User() user: User) {
// Compiler toutes les données de l'utilisateur
const data = {
profile: await this.userService.getProfile(user.id),
consultations: await this.consultationService.getUserConsultations(user.id),
medicalRecords: await this.medicalRecordService.getUserRecords(user.id),
payments: await this.paymentService.getUserPayments(user.id)
}
// Retourner en JSON
return data
}2. Droit à l'oubli
typescript
@Delete('delete-account')
async deleteAccount(@User() user: User) {
// Anonymiser les données (pas supprimer complètement pour historique médical)
await this.userService.anonymize(user.id)
// Supprimer les données non critiques
await this.notificationService.deleteUserNotifications(user.id)
// Marquer le compte comme supprimé
await this.userService.markAsDeleted(user.id)
}3. Consentement explicite
typescript
interface UserConsent {
marketing: boolean
dataSharing: boolean
analytics: boolean
consentDate: Date
}
// Enregistrer le consentement
await this.consentService.update(user.id, {
marketing: false,
dataSharing: true, // Partage avec médecins uniquement
analytics: true,
consentDate: new Date()
})Mesures de sécurité additionnelles
Résultats
Conclusion
La sécurité des données médicales ne se limite pas au chiffrement : c'est une approche globale combinant technique, processus et conformité réglementaire.
*Audit de sécurité pour votre application ? [Contactez-nous](/contact).*