Le chiffrement avec SQL Server

Vous avez des données confidentielles dans vos bases de données (données financières, médicales, etc...) et besoin de les sécuriser : le chiffrement de SQL Server est à votre disposition. Mais quelles sont les limites de la mise en place du chiffrement pour mes données existantes ? Qu'en est-il de mon indexation et des performances ? Puis-je chiffrer et indexer des colonnes ?

La problématique de chiffrement est triple :

  • Chiffrement des données stockées en bases de données : Empêcher l’accès aux données sensibles par des personnes ou des applications non autorisées, empêcher le vol de fichiers de données ou de sauvegardes.
  • Chiffrement des fichiers d’import ou d’export : Empêcher le vol de fichiers et la lecture de données par des personnes non autorisées
  • Chiffrement du trafic réseau : Empêcher la capture sur le réseau LAN des données transitant entre le client et le serveur SQL.

Il existe une solution distincte pour chacun de ces aspects :

  • Chiffrement des fichiers : pour les chiffrements des backups et fichiers de données SQL Server, il existe le TDE (Transparent Data Encryption) depuis SQL Server 2008. Pour les fichiers texte d'import/export, les fonctionnalités de chiffrement du système d'exploitation sont là (EFS).
  • Chiffrement du trafic réseau : fonctionnalité existante dans les bibliothèques réseau de SQL Server (SSL), ou fonctionnalités de chiffrement réseau du système d’exploitation (IPSEC)
  • Chiffrement des données dans les tables de SQL Server : une des fonctionnalités du moteur de bases de données depuis SQL Server 2005, c'est ce que nous allons détailler ici.

L'architecture de chiffrement des données dans SQL Server

SQL Server offre des fonctions TRANSACT-SQL de chiffrement/déchiffrement de données qui s’appuient sur une infrastructure de gestion de clés gérées par le moteur de bases de données. L’infrastructure de gestion de clés est organisée en hiérarchie : chaque couche chiffre la couche qui se trouve en dessous d'elle à l'aide d'une combinaison de certificats, de clés asymétriques et de clés symétriques.

Les clés et certificats sont en général stockés dans SQL Server. Depuis SQL Server 2008R2, une API est fournie pour permettre de les stocker si nécessaire hors de SQL Server dans un module de gestion de clés extensible (EKM, Extensible Key Management).

Les objets disponibles sur lesquels s’appuient le chiffrement sont :

• La clé principale de service (chiffrée par windows) • La clé principale de base de données (chiffrée par la clé précédente et un mot de passe) • Des clés symétriques • Des clés asymétriques • Des certificats

En termes de performances, un chiffrement par clé symétrique est le plus efficace. La clé symétrique est elle-même chiffrée par une autre clé ou par un certificat.

Chiffrement et déchiffrement des données

Une fois l’architecture de clés configurée (et sécurisée et documentée, si les clés sont perdues la perte de données est irréversible !), le chiffrement de données sera effectué dès l’insertion ou la mise à jour (INSERT, UPDATE) en utilisant les fonctions TRANSACT-SQL :

• EncryptByCert(), EncryptByAsymKey(), EncryptByKey() ou EncryptByPassphrase()

Les données devont donc être déchiffrées lors de la lecture (SELECT) avec les fonctions :

• DecryptByCert(), DecryptByAsymKey(), DecryptByKey() ou DecryptByPassphrase()

Exemple de mise en place

-- Mise en place (simplifiée) de mon architecture de chiffrement
USE CRYPTEST
GO
CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'P@ssw0rd';
GO
CREATE CERTIFICATE Cert1_Test
WITH SUBJECT = 'Certificat Test',
START_DATE = '2011-04-21',
EXPIRY_DATE = '2021-04-21';
GO
CREATE SYMMETRIC KEY SymKey1_Test
WITH ALGORITHM = AES_256
ENCRYPTION BY CERTIFICATE Cert1_Test;
GO

Ma table à chiffrer :

[id_adh] [char](19) NULL,
[chardata] [varchar](50) NULL

Structure de la nouvelle table chiffrée : adh_encrypted

CREATE TABLE [dbo].[adh_encrypted](
[id_adh_Ch] [binary](68) NULL,
[chardata] [varchar](50) NULL)

Ecriture des données chiffrées

-- Ouverture de la clé de chiffrement
-- cette commande doit être lancée pour la session
OPEN SYMMETRIC KEY SymKey1_Test DECRYPTION BY CERTIFICATE Cert1_Test
GO
-- Insertion des données chiffrées
INSERT  adh_encrypted ( id_adh_Ch, chardata )
        SELECT  ENCRYPTBYKEY(KEY_GUID('SymKey1_Test'), id_adh), chardata
        FROM    adh

Lecture des données chiffrées

-- Ouverture de la clé de chiffrement,
-- cette commande doit être lancée pour la session
OPEN SYMMETRIC KEY SymKey1_Test DECRYPTION BY CERTIFICATE Cert1_Test
GO
-- Lecture des données
SELECT  convert(char(19), decryptbykey([id_adh_Ch])) as id_adh
    ,       [chardata]
    FROM    [adh_encrypted]

Pour limiter en partie l’impact des modifications de structure sur le code SQL, différentes techniques sont à notre disposition :

  • Vues sur les tables chiffrées, portant le nom de l’ancienne table non chiffrée et intégrant la fonction de chiffrement : elles peuvent être utilisées pour les SELECT
  • Triggers INSTEAD OF sur les vues : elles permettent d’effectuer les INSERT, UPDATE, DELETE dans les tables chiffrées au travers des vues

Chiffrement et indexation

Que se passe-t-il si je dois indexer une colonne chiffrée ? Dans le cas précédent, je souhaitais chiffrer la colonne id_adh, qui contient un numéro de carte bleue confidentiel. Or cette colonne est aussi utilisée comme clé de recherche (WHERE), voire comme clé de jointure (JOIN) !

Il n’est pas possible d’utiliser un index sur une colonne chiffrée. Dans l’exemple précédent, la condition :

WHERE   convert(char(19), decryptbykey([id_adh_Ch]))=' 1234567890'

oblige SQL Server à parcourir (et déchiffrer !) toutes les données de la table pour localiser l’enregistrement (table scan).

Si l’on modifie la condition de cette façon :

WHERE   id_adh_Ch = 
ENCRYPTBYKEY(KEY_GUID('SymKey1_Test'), '1234567890')

l’index est bien utilisable, mais la ligne ne peut pas être trouvée : la fonction de chiffrement n’est pas déterministe (pour une même valeur en clair n’obtient jamais la même valeur chiffrée…)

Il faut donc imaginer des solutions alternatives pour permettre une recherche efficace sur une clé chiffrée et permettre des jointures.

Utilisation du hashage

La solution imaginée pour résoudre ce problème serait d’utiliser les fonctions de hachage.

SQL Server 2008 offre la fonction HashBytes(), qui retourne le hachage d’une valeur, avec différents algorithmes cryptographiques au choix. La valeur hachée ne peut être déchiffrée, mais elle peut servir à la recherche. Le problème des valeurs hachées reste la possibilité de collisions (même valeur hachée pour des valeurs en clair différentes). Ce cas de figure, bien qu’extrêmement rare (avec l’algorighme SHA1, évalué à 1 sur 10^48) est à considérer. En test sur des tables de plusieurs millions de lignes, aucune collision n’a pu être constatée.

Une solution pour éviter définitivement tout risque de collision serait d’utiliser deux colonnes de hachage avec des algorithmes différents. (Une autre solution serait de lire et déchiffrer la zone chiffrée pour vérification, mais cette solution exclut des jointures efficaces.)

Solution testée :

  • Modification de la table chiffrée avec ajout de deux colonnes pour les valeurs hachées, l’une en SHA1, l’autre en MD5 : pour une recherche effectuée sur les deux colonnes, la possibilité de collision est virtuellement nulle.
  • Les deux colonnes sont indexées (et clé primaire dans cet exemple)

CREATE TABLE [dbo].[adh_encrypted](
        [id_adh_H1] [binary](20) NOT NULL,
        [id_adh_H2] [binary](16) NOT NULL,
        [id_adh_Ch] [binary](68) NULL,
        [chardata] [varchar](50) NULL,
 CONSTRAINT [PK_adh_encrypted] PRIMARY KEY NONCLUSTERED 
(
        [id_adh_H1] ASC,
        [id_adh_H2] ASC
)

  La mise à jour des colonnes hachées s’effectue avec la fonction Hashbytes(), par exemple une insertion :

insert  adh_encrypted
        ( id_adh_H1, id_adh_H2, id_adh_Ch  )
VALUES  (
          HASHBYTES('SHA1', @sIdAdh)
        , HASHBYTES('MD5', @sIdAdh)
        , ENCRYPTBYKEY(key_guid('SymKey1_Test'), @sIdAdh)
        )


Pour une recherche, la condition doit donc être modifiée de la manière suivante :

WHERE (id_adh_H1=HASHBYTES('SHA1', '1234567890'
  and id_adh_H2 = HASHBYTES('MD5', '1234567890')

A noter cependant que cette technique est totalement inefficace pour les recherches non basées sur une égalité (BETWEEN, LIKE, >, <), dans ce cas SQL Server effectuera forcément un parcours de table.

Ce procédé fonctionne aussi avec les jointures, qui doivent aussi par conséquent être modifiées pour utiliser les deux colonnes hachées.

A noter aussi que l'’utilisation du hachage réduit sans aucun doute le niveau de sécurité du chiffrement. Il est aussi envisageable de complexifier les hachages avec le développement de fonctions de hachage spécifiques en CLR.

Commentaires

1. Le vendredi 27 janvier 2012, 14:52 par SQLpro

Bonjour,

une autre solution est de ne crypter qu'une partie du code. Par exemple pour les cartes bleu, les 12 premiers chiffres et laisser en clair les 4 derniers (ce que fait amazaon.com)
Pour un n° de sécurité social, crypter la partie date de naissance et sexe et laisser le code commune + rang + clef

A +

2. Le mardi 19 mars 2013, 17:54 par roger

Les hash sans clés secrète sont cassable via une recherche exhaustive, a fortiori lorsqu'ils ont une structure déterministe (numéro de CB, NIR, ...). Calculer tous les MD5 de tous les numéros de CB possible (10^15 possibilités (16 chiffres moins un chiffre qui sert de clé de contrôle)) prends dans les 3 jours, ce qui semble relativement faisable. Je déconseillerai cette méthode, à moins de calculer les hash avec une clé secrète qui devra bien entendu être stockée à l'extérieur de la base de données.