Procédure : Créer une bibliothèque DPAPI
Sur cette page
Objectifs
S'applique à
Résumé
Points à connaître
Créer une bibliothèque de classes Visual C#
Nom fort de l'assembly (facultatif)
Ressources supplémentaires
Objectifs
Ce module vous permet d'effectuer l'opération suivante :
-
créer une bibliothèque gérée qui utilise l'interface DPAPI pour crypter et décrypter les données.
S'applique à
Ce module s'applique aux produits et technologies suivants :
-
Microsoft® Windows® XP ou Windows 2000 Server (avec le Service Pack 3) et systèmes d'exploitation ultérieurs
-
Microsoft DPAPI (Data Protection Application Programming Interface)
-
Microsoft .NET Framework version 1.0 (avec le Service Pack 2) et versions ultérieures
-
Microsoft Visual C#® .NET
Résumé
Les applications Web doivent souvent stocker dans les fichiers de configuration d'application des données de sécurité sensibles, telles que les chaînes de connexion de base de données et des informations d'identification de compte de service. Pour des raisons de sécurité, ces éléments ne doivent jamais être stockés sous forme de texte brut. Ces informations doivent toujours être cryptées avant leur stockage.
Ce module explique comment créer une bibliothèque de classes gérées qui encapsule les appels vers DPAPI (Data Protection API) pour le cryptage et le décryptage de données à l'aide des magasins de clés d'ordinateurs ou d'utilisateurs. Cette bibliothèque peut ensuite être utilisée à partir d'autres applications gérées telles que les applications Web ASP.NET, les services Web et les applications Microsoft Enterprise Services.
Points à connaître
Avant d'utiliser ce module, vous devez tenir compte des points suivants :
-
Le système d'exploitation Windows 2000 et les systèmes d'exploitation ultérieurs fournissent l'API Win32® Data Protection (DPAPI) pour le cryptage et le décryptage des données.
-
L'interface DPAPI fait partie de l'interface Crypto API (Cryptography API) et est implémentée dans crypt32.dll. Elle est constituée de deux méthodes, CryptProtectData et CryptUnprotectData.
-
DPAPI est d'autant plus utile qu'elle permet de gommer le problème de gestion des clés inhérent aux applications qui font appel à la cryptographie. Bien que le cryptage garantisse la sécurité des données, vous devez effectuer des étapes supplémentaires pour garantir la sécurité de la clé. DPAPI utilise le mot de passe du compte d'utilisateur associé au code chargé d'appeler les fonctions DPAPI pour extraire la clé de cryptage. Par conséquent, c'est le système d'exploitation (et non l'application) qui gère la clé.
-
DPAPI peut fonctionner avec le magasin de l'ordinateur ou le magasin d'utilisateurs (qui nécessite un profil d'utilisateur chargé). DPAPI fait par défaut appel au magasin d'utilisateurs, même s'il est possible d'indiquer que le magasin de l'ordinateur peut être adopté en transmettant l'indicateur CRYPTPROTECT_LOCAL_MACHINE aux fonctions DPAPI.
-
La méthode du profil d'utilisateur permet de faire appel à une couche de sécurité supplémentaire, car elle limite à certaines personnes l'accès au secret. Seul l'utilisateur qui crypte les données peut les décrypter. Cependant, l'emploi du profil d'utilisateur nécessite un effort de développement supplémentaire lorsque l'interface DPAPI est exploitée à partir d'une application Web ASP.NET. Vous devrez en effet réaliser des opérations explicites pour charger et décharger les profils d'utilisateurs (ASP.NET ne charge pas automatiquement les profils d'utilisateurs).
-
La méthode du magasin de l'ordinateur est plus simple à développer, car elle ne nécessite pas de gestion des profils d'utilisateurs. Toutefois, à moins qu'un paramètre d'entropie supplémentaire ne soit défini, cette méthode est moins sûre car tous les utilisateurs de l'ordinateur peuvent décrypter les données (l'entropie est une valeur aléatoire conçue pour compliquer le déchiffrage du secret). L'utilisation d'un paramètre d'entropie supplémentaire présente toutefois un inconvénient. Ce paramètre doit en effet être stocké de manière sécurisée par l'application, ce qui complique la gestion de clés.
Remarque : si vous utilisez DPAPI avec le magasin de l'ordinateur, la chaîne cryptée est associée à un ordinateur particulier et vous devez par conséquent générer les données cryptées sur chaque ordinateur. Ne copiez pas les données cryptées sur plusieurs ordinateurs organisés en ferme ou en cluster.
Si vous utilisez DPAPI avec le magasin d'utilisateurs, vous pouvez décrypter les données sur tous les ordinateurs associés à un profil d'utilisateur itinérant.
Créer une bibliothèque de classes Visual C#
Cette procédure permet de créer une bibliothèque de classes Visual C# qui met à disposition des méthodes de cryptage et de décryptage. Les appels sont encapsulés vers les fonctions Win32 DPAPI.
-
Pour créer une bibliothèque de classes Visual C#
-
Démarrez Visual Studio .NET et créez un projet de bibliothèque de classes Visual C# nommé DataProtection.
-
Utilisez l'Explorateur de solutions pour renommer class1.cs en DataProtection.cs.
-
Dans DataProtection.cs, renommez class1 en DataProtector et renommez le constructeur par défaut de manière appropriée.
-
Dans l'Explorateur de solutions, cliquez avec le bouton droit sur DataProtection et cliquez sur Propriétés.
-
Cliquez sur le dossier Propriétés de configuration et attribuez la valeur True à Autoriser les blocs de code unsafe.
-
Cliquez sur OK pour fermer la boîte de dialogue Propriétés.
-
Ajoutez les instructions using suivantes en haut du fichier DataProtection.cs, sous l'instruction using existante.
using System.Text; using System.Runtime.InteropServices;
-
Ajoutez les instructions DllImport suivantes au-dessus de la classe DataProtector pour permettre aux fonctions Win32 DPAPI et à la fonction d'utilitaire FormatMessage d'être appelées au moyen de P/Invoke.
[DllImport("Crypt32.dll", SetLastError=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] private static extern bool CryptProtectData( ref DATA_BLOB pDataIn, String szDataDescr, ref DATA_BLOB pOptionalEntropy, IntPtr pvReserved, ref CRYPTPROTECT_PROMPTSTRUCT pPromptStruct, int dwFlags, ref DATA_BLOB pDataOut); [DllImport("Crypt32.dll", SetLastError=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] private static extern bool CryptUnprotectData( ref DATA_BLOB pDataIn, String szDataDescr, ref DATA_BLOB pOptionalEntropy, IntPtr pvReserved, ref CRYPTPROTECT_PROMPTSTRUCT pPromptStruct, int dwFlags, ref DATA_BLOB pDataOut); [DllImport("kernel32.dll", CharSet=System.Runtime.InteropServices.CharSet.Auto)] private unsafe static extern int FormatMessage(int dwFlags, ref IntPtr lpSource, int dwMessageId, int dwLanguageId, ref String lpBuffer, int nSize, IntPtr *Arguments); -
Ajoutez les définitions et constantes de structure suivantes utilisées par les fonctions DPAPI.
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] internal struct DATA_BLOB { public int cbData; public IntPtr pbData; } [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] internal struct CRYPTPROTECT_PROMPTSTRUCT { public int cbSize; public int dwPromptFlags; public IntPtr hwndApp; public String szPrompt; } static private IntPtr NullPtr = ((IntPtr)((int)(0))); private const int CRYPTPROTECT_UI_FORBIDDEN = 0x1; private const int CRYPTPROTECT_LOCAL_MACHINE = 0x4; -
Ajoutez à la classe un type énuméré public nommé Store. Cet élément sert à indiquer si DPAPI doit être utilisée avec les magasins d'ordinateurs ou d'utilisateurs.
public enum Store {USE_MACHINE_STORE = 1, USE_USER_STORE}; -
Ajoutez à la classe une variable de membre privé de type Store.
private Store store;
-
Remplacez le constructeur par défaut de la classe par le constructeur suivant, qui accepte un paramètre Store et place la valeur fournie dans la variable de membre privé store.
public DataProtector(Store tempStore) { store = tempStore; } -
Ajoutez à la classe la méthode publique Encrypt.
public byte[] Encrypt(byte[] plainText, byte[] optionalEntropy) { bool retVal = false; DATA_BLOB plainTextBlob = new DATA_BLOB(); DATA_BLOB cipherTextBlob = new DATA_BLOB(); DATA_BLOB entropyBlob = new DATA_BLOB(); CRYPTPROTECT_PROMPTSTRUCT prompt = new CRYPTPROTECT_PROMPTSTRUCT(); InitPromptstruct(ref prompt); int dwFlags; try { try { int bytesSize = plainText.Length; plainTextBlob.pbData = Marshal.AllocHGlobal(bytesSize); if(IntPtr.Zero == plainTextBlob.pbData) { throw new Exception("Impossible d'allouer le tampon plaintext."); } plainTextBlob.cbData = bytesSize; Marshal.Copy(plainText, 0, plainTextBlob.pbData, bytesSize); } catch(Exception ex) { throw new Exception("Exception de mise en ordre des données. " + ex.Message); } if(Store.USE_MACHINE_STORE == store) {//Utilisation du magasin de l'ordinateur. Devrait fournir l'entropie. dwFlags = CRYPTPROTECT_LOCAL_MACHINE|CRYPTPROTECT_UI_FORBIDDEN; //Vérifier si l'entropie est nulle. if(null == optionalEntropy) {//Allouer quelque chose. optionalEntropy = new byte[0]; } try { int bytesSize = optionalEntropy.Length; entropyBlob.pbData = Marshal.AllocHGlobal(optionalEntropy .Length);; if(IntPtr.Zero == entropyBlob.pbData) { throw new Exception("Impossible d'allouer un tampon de données d'entropie."); } Marshal.Copy(optionalEntropy, 0, entropyBlob.pbData, bytesSize); entropyBlob.cbData = bytesSize; } catch(Exception ex) { throw new Exception("Exception liée à l'entropie et à la mise en ordre des données. " + ex.Message); } } else {//Utilisation du magasin des utilisateurs dwFlags = CRYPTPROTECT_UI_FORBIDDEN; } retVal = CryptProtectData(ref plainTextBlob, "", ref entropyBlob, IntPtr.Zero, ref prompt, dwFlags, ref cipherTextBlob); if(false == retVal) { throw new Exception("Échec du cryptage. " + GetErrorMessage(Marshal.GetLastWin32Error())); } } catch(Exception ex) { throw new Exception("Exception liée au cryptage. " + ex.Message); } byte[] cipherText = new byte[cipherTextBlob.cbData]; Marshal.Copy(cipherTextBlob.pbData, cipherText, 0, cipherTextBlob .cbData); return cipherText; } -
Ajoutez à la classe la méthode publique Decrypt.
public byte[] Decrypt(byte[] cipherText, byte[] optionalEntropy) { bool retVal = false; DATA_BLOB plainTextBlob = new DATA_BLOB(); DATA_BLOB cipherBlob = new DATA_BLOB(); CRYPTPROTECT_PROMPTSTRUCT prompt = new CRYPTPROTECT_PROMPTSTRUCT(); InitPromptstruct(ref prompt); try { try { int cipherTextSize = cipherText.Length; cipherBlob.pbData = Marshal.AllocHGlobal(cipherTextSize); if(IntPtr.Zero == cipherBlob.pbData) { throw new Exception("Impossible d'allouer le tampon cipherText."); } cipherBlob.cbData = cipherTextSize; Marshal.Copy(cipherText, 0, cipherBlob.pbData, cipherBlob.cbData); } catch(Exception ex) { throw new Exception("Exception de mise en ordre des données. " + ex.Message); } DATA_BLOB entropyBlob = new DATA_BLOB(); int dwFlags; if(Store.USE_MACHINE_STORE == store) {//Utilisation du magasin de l'ordinateur. Devrait fournir l'entropie. dwFlags = CRYPTPROTECT_LOCAL_MACHINE|CRYPTPROTECT_UI_FORBIDDEN; //Vérifier si l'entropie est nulle. if(null == optionalEntropy) {//Allouer quelque chose. optionalEntropy = new byte[0]; } try { int bytesSize = optionalEntropy.Length; entropyBlob.pbData = Marshal.AllocHGlobal(bytesSize); if(IntPtr.Zero == entropyBlob.pbData) { throw new Exception("Impossible d'allouer le tampon entropy."); } entropyBlob.cbData = bytesSize; Marshal.Copy(optionalEntropy, 0, entropyBlob.pbData, bytesSize); } catch(Exception ex) { throw new Exception("Exception liée à l'entropie et à la mise en ordre des données. " + ex.Message); } } else {//Utilisation du magasin des utilisateurs dwFlags = CRYPTPROTECT_UI_FORBIDDEN; } retVal = CryptUnprotectData(ref cipherBlob, null, ref entropyBlob, IntPtr.Zero, ref prompt, dwFlags, ref plainTextBlob); if(false == retVal) { throw new Exception("Échec du décryptage. " + GetErrorMessage(Marshal.GetLastWin32Error())); } //Libérer le journal blob et l'entropie. if(IntPtr.Zero != cipherBlob.pbData) { Marshal.FreeHGlobal(cipherBlob.pbData); } if(IntPtr.Zero != entropyBlob.pbData) { Marshal.FreeHGlobal(entropyBlob.pbData); } } catch(Exception ex) { throw new Exception("Exception liée au décryptage. " + ex.Message); } byte[] plainText = new byte[plainTextBlob.cbData]; Marshal.Copy(plainTextBlob.pbData, plainText, 0, plainTextBlob.cbData); return plainText; } -
Ajoutez les méthodes utiles privées suivantes à la classe.
private void InitPromptstruct(ref CRYPTPROTECT_PROMPTSTRUCT ps) { ps.cbSize = Marshal.SizeOf(typeof(CRYPTPROTECT_PROMPTSTRUCT)); ps.dwPromptFlags = 0; ps.hwndApp = NullPtr; ps.szPrompt = null; } private unsafe static String GetErrorMessage(int errorCode) { int FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100; int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; int messageSize = 255; String lpMsgBuf = ""; int dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; IntPtr ptrlpSource = new IntPtr(); IntPtr prtArguments = new IntPtr(); int retVal = FormatMessage(dwFlags, ref ptrlpSource, errorCode, 0, ref lpMsgBuf, messageSize, & prtArguments); if(0 == retVal) { throw new Exception("Échec de la définition du format du message pour le code d'erreur. " + errorCode + ". "); } return lpMsgBuf; } -
Dans le menu Générer, choisissez Générer la solution.
-
Nom fort de l'assembly (facultatif)
Si la bibliothèque de classes DPAPI gérée doit être nommée par une application Microsoft Enterprise Services (avec un nom fort), alors la bibliothèque de classes DPAPI doit également posséder un nom fort. Cette procédure permet de créer un nom fort pour la bibliothèque de classes.
Si la bibliothèque de classes DPAPI gérée doit être directement nommée à partir d'une application Web ASP.NET (avec un nom fort), vous n'avez pas besoin d'effectuer cette procédure.
-
Pour attribuer un nom fort à l'assembly
-
Ouvrez une fenêtre de commande et modifiez le répertoire pour indiquer le dossier du projet DataProtection.
-
Exécutez l'utilitaire sn.exe pour générer une paire de clés permettant de signer l'assembly.
sn -k dataprotection.snk
-
Revenez dans Visual Studio .NET et ouvrez le fichier Assemblyinfo.cs.
-
Recherchez l'attribut AssemblyKeyFile et ajoutez un chemin au fichier de clé dans le dossier du projet.
[assembly: AssemblyKeyFile(@"..\..\dataprotection.snk")]
-
Dans le menu Générer, choisissez Générer la solution.
-
Ressources supplémentaires
Pour plus d'informations sur l'utilisation de la bibliothèque créée dans ce module, consultez les modules suivants :
-
« Procédure : Utiliser DPAPI (magasin de l'ordinateur) à partir de ASP.NET » dans ce guide.
-
« Procédure : Utiliser DPAPI (magasin d'utilisateurs) à partir de ASP.NET avec les services Microsoft Enterprise Services » dans ce guide.