Dernière mise à jour :2008-07-24

informatique

Pour les développeur ASP.NET, l’un des aspects critique au niveau de la sécurité est de stocker de façon sécuritaire tout ce qui doit être gardé secret, comme par exemple, les chaînes de connexion aux bases de données qui contiennent la plupart du temps, des informations d’identification. En effet, mal protégé, ces données pourraient être accédées par des pirates informatiques (hackers) et ceux-ci pourraient utiliser ces informations pour lire ou manipuler les données de votre application.

Il est donc recommandé de crypter ces données secrètes. Pour réaliser cela, il est possible d’utiliser une série de classes fournies par le .Net Framework mais il est aussi possible et plus efficace d’utiliser l’API Data Protection (DPAPI) fournie sous Windows 2000/XP/2003. Cet article vous propose de vous montrer comment vous servir de DPAPI dans vos application ASP.NET.

Un peu plus à propos de DPAPI

À partir de la version «Windows 2000» de son système d’exploitation, Microsoft a commencé à y inclure une API permettant de crypter et décrypter des données au niveau application, nommée Win32® Data Protection API (DPAPI). Cette API fait partie de l’API Cryptographie, implémentée dans Crypt32.dll.

L’une des principales caractéristiques qui rend cette API différente d’autres API de cryptographie, comme par exemple, celles fournies dans le .Net Framework (System.Security.Cryptography) c’est qu’elle fournie une gestion automatique de la clé. DPAPI utilise en fait le mot de passe du compte de l’utilisateur ainsi que le code qui a fait l’appel des fonctions de l’API pour en dériver la clé de cryptage.

Approche machine ou approche utilisateur?

DPAPI peut être utilisé au niveau de la machine (machine store) ou encore, limité au niveau d’un utilisateur ciblé (user store), ce qui requiert le chargement du profil de l’utilisateur en question. Par défaut, c’est l’approche utilisateur qui est retenue par l’API sans doute parce que celle-ci apporte un niveau de sécurité supplémentaire en limitant l’accès à un seul utilisateur. Il est cependant possible de changer pour l’option «machine store» en passant aux fonctions de l’API, le paramètre CRYPTPROTECT_LOCAL_MACHINE.

Au niveau des applications web, l’approche machine est plus facile à développer puisque l’approche utilisateur implique qu’il faut ajouter des étapes de développement pour gérer le chargement et le déchargement du profil d’un utilisateur car le compte ASPNET n’a pour sa part, pas de profil.

Web.Config

Le fichier web.config fait partie de toute application ASP.NET et fourni une façon simple de stocker des informations utilisées fréquemment dans des pages ASP.NET, comme par exemple, une chaîne de connexion à une base de données.

Voici un exemple de code de chaîne de connexion qui pourrait être ajouté dans un fichier web.config. Veuillez noter que le code en question est placé dans la balise «configuration» du fichier.

<appSettings>
<add key="ConnectionString" value="data source=mondatasource;userid=monuser;password=monpassword;" />
</appSettings>

Pour ensuite aller chercher cette valeur dans une page ASP.NET, le code ressemblerait à ceci :

strConnection = ConfigurationSettings.AppSettings("ConnectionString");

Note : Il faut inclure le namespace «System.Configuration» dans votre page.

Mise en pratique

Création d’une librairie DPAPI

Le code suivant montre comment créer une librairie pour encapsuler les appels aux méthodes de la DPAPI pour crypter et décrypter des données. Cette librairie pourra ensuite être utilisé dans des applications ASP.NET.

Note : Le code de cette API est tiré d’un code publié sur la MSDN de Microsoft.

Listing 1.0 Création d'une librairie DPAPI
1.namespace Crypt
2.{
3.  using System;
4.  using System.Text;
5.  using System.Runtime.InteropServices;
6.
7.  public class DataProtector
8.  {
9.
10.   [DllImport("Crypt32.dll", SetLastError=true,
11.     CharSet=System.Runtime.InteropServices.CharSet.Auto)]
12.   private static extern bool CryptProtectData(
13.     ref DATA_BLOB pDataIn,
14.     String szDataDescr,
15.     ref DATA_BLOB pOptionalEntropy,
16.     IntPtr pvReserved,
17.     ref CRYPTPROTECT_PROMPTSTRUCT
18.     pPromptStruct,
19.     int dwFlags,
20.     ref DATA_BLOB pDataOut);
21.   [DllImport("Crypt32.dll", SetLastError=true,
22.     CharSet=System.Runtime.InteropServices.CharSet.Auto)]
23.   private static extern bool CryptUnprotectData(
24.     ref DATA_BLOB pDataIn,
25.     String szDataDescr,
26.     ref DATA_BLOB pOptionalEntropy,
27.     IntPtr pvReserved,
28.     ref CRYPTPROTECT_PROMPTSTRUCT
29.     pPromptStruct,
30.     int dwFlags,
31.     ref DATA_BLOB pDataOut);
32.   [DllImport("kernel32.dll",
33.     CharSet=System.Runtime.InteropServices.CharSet.Auto)]
34.   private unsafe static extern int FormatMessage(int dwFlags,
35.     ref IntPtr lpSource,
36.     int dwMessageId,
37.     int dwLanguageId,
38.     ref String lpBuffer,
39.     int nSize,
40.     IntPtr *Arguments);
41.
42.   [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
43.   internal struct DATA_BLOB
44.   {
45.    public int cbData;
46.    public IntPtr pbData;
47.   }
48.
49.   [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
50.   internal struct CRYPTPROTECT_PROMPTSTRUCT
51.   {
52.    public int cbSize;
53.    public int dwPromptFlags;
54.    public IntPtr hwndApp;
55.    public String szPrompt;
56.   }
57.   static private IntPtr NullPtr = ((IntPtr)((int)(0)));
58.   private const int CRYPTPROTECT_UI_FORBIDDEN = 0x1;
59.   private const int CRYPTPROTECT_LOCAL_MACHINE = 0x4;
60.
61.   public enum Store {USE_MACHINE_STORE = 1, USE_USER_STORE};
62.   private Store store;
63.
64.   public DataProtector(Store tempStore)
65.   {
66.    store = tempStore;
67.   }
68.
69.   public byte[] Encrypt(byte[] plainText, byte[] optionalEntropy)
70.   {
71.    bool retVal = false;
72.
73.    DATA_BLOB plainTextBlob = new DATA_BLOB();
74.    DATA_BLOB cipherTextBlob = new DATA_BLOB();
75.    DATA_BLOB entropyBlob = new DATA_BLOB();
76.
77.    CRYPTPROTECT_PROMPTSTRUCT prompt = new
78.    CRYPTPROTECT_PROMPTSTRUCT();
79.    InitPromptstruct(ref prompt);
80.
81.    int dwFlags;
82.    try
83.    {
84.     try
85.     {
86.      int bytesSize = plainText.Length;
87.      plainTextBlob.pbData = Marshal.AllocHGlobal(bytesSize);
88.      if(IntPtr.Zero == plainTextBlob.pbData)
89.      {
90.       throw new Exception("Unable to allocate plaintext buffer.");
91.      }
92.      plainTextBlob.cbData = bytesSize;
93.      Marshal.Copy(plainText, 0, plainTextBlob.pbData, bytesSize);
94.     }
95.     catch(Exception ex)
96.     {
97.      throw new Exception("Exception marshalling data. " + ex.Message);
98.     }
99.     if(Store.USE_MACHINE_STORE == store)
100.     {
101.      dwFlags = CRYPTPROTECT_LOCAL_MACHINE|CRYPTPROTECT_UI_FORBIDDEN;
102.      if(null == optionalEntropy)
103.       {
104.        optionalEntropy = new byte[0];
105.       }
106.       try
107.       {
108.        int bytesSize = optionalEntropy.Length;
109.        entropyBlob.pbData = Marshal.AllocHGlobal(optionalEntropy.Length);
110.        if(IntPtr.Zero == entropyBlob.pbData)
111.        {
112.         throw new Exception("Unable to allocate entropy data buffer.");
113.        }
114.        Marshal.Copy(optionalEntropy, 0, entropyBlob.pbData, bytesSize);
115.        entropyBlob.cbData = bytesSize;
116.       }
117.       catch(Exception ex)
118.       {
119.        throw new Exception("Exception entropy marshalling data. " + ex.Message);
120.       }
121.      }
122.      else
123.      {
124.       dwFlags = CRYPTPROTECT_UI_FORBIDDEN;
125.      }
126.      retVal = CryptProtectData(ref plainTextBlob, "", ref entropyBlob, IntPtr.Zero, ref prompt, dwFlags, ref cipherTextBlob);
127.      if(false == retVal)
128.      {
129.       throw new Exception("Encryption failed. " + GetErrorMessage(Marshal.GetLastWin32Error()));
130.      }
131.     
132.     
133.      
134.     
135.     
136.     
137.      
138.     
139.     }
140.     catch(Exception ex)
141.     {
142.     throw new Exception("Exception encrypting. " + ex.Message);
143.     }
144.     byte[] cipherText = new byte[cipherTextBlob.cbData];
145.     Marshal.Copy(cipherTextBlob.pbData, cipherText, 0,
146.     cipherTextBlob.cbData);
147.     return cipherText;
148.   }
149.
150.   public byte[] Decrypt(byte[] cipherText, byte[] optionalEntropy)
151.   {
152.    bool retVal = false;
153.    DATA_BLOB plainTextBlob = new DATA_BLOB();
154.    DATA_BLOB cipherBlob = new DATA_BLOB();
155.    CRYPTPROTECT_PROMPTSTRUCT prompt = new
156.    CRYPTPROTECT_PROMPTSTRUCT();
157.    InitPromptstruct(ref prompt);
158.    try
159.    {
160.     try
161.     {
162.      int cipherTextSize = cipherText.Length;
163.      cipherBlob.pbData = Marshal.AllocHGlobal(cipherTextSize);
164.      if(IntPtr.Zero == cipherBlob.pbData)
165.      {
166.       throw new Exception("Unable to allocate cipherText buffer.");
167.      }
168.      cipherBlob.cbData = cipherTextSize;
169.      Marshal.Copy(cipherText, 0, cipherBlob.pbData,
170.      cipherBlob.cbData);
171.     }
172.     catch(Exception ex)
173.     {
174.      throw new Exception("Exception marshalling data. " + ex.Message);
175.     }
176.     DATA_BLOB entropyBlob = new DATA_BLOB();
177.     int dwFlags;
178.     if(Store.USE_MACHINE_STORE == store)
179.     {
180.      dwFlags = CRYPTPROTECT_LOCAL_MACHINE|CRYPTPROTECT_UI_FORBIDDEN;
181.      if(null == optionalEntropy)
182.      {
183.       optionalEntropy = new byte[0];
184.      }
185.      try
186.      {
187.       int bytesSize = optionalEntropy.Length;
188.       entropyBlob.pbData = Marshal.AllocHGlobal(bytesSize);
189.       if(IntPtr.Zero == entropyBlob.pbData)
190.       {
191.        throw new Exception("Unable to allocate entropy buffer.");
192.       }
193.       entropyBlob.cbData = bytesSize;
194.       Marshal.Copy(optionalEntropy, 0, entropyBlob.pbData, bytesSize);
195.      }
196.      catch(Exception ex)
197.      {
198.       throw new Exception("Exception entropy marshalling data. " + ex.Message);
199.      }
200.     }
201.     else
202.     {
203.      dwFlags = CRYPTPROTECT_UI_FORBIDDEN;
204.     }
205.     retVal = CryptUnprotectData(ref cipherBlob, null, ref entropyBlob,
206.     IntPtr.Zero, ref prompt, dwFlags,
207.     ref plainTextBlob);
208.     if(false == retVal)
209.     {
210.      throw new Exception("Decryption failed. " + GetErrorMessage(Marshal.GetLastWin32Error()));
211.     }
212.     if(IntPtr.Zero != cipherBlob.pbData)
213.     {
214.      Marshal.FreeHGlobal(cipherBlob.pbData);
215.     }
216.     if(IntPtr.Zero != entropyBlob.pbData)
217.     {
218.      Marshal.FreeHGlobal(entropyBlob.pbData);
219.     }
220.    }
221.    catch(Exception ex)
222.    {
223.     throw new Exception("Exception decrypting. " + ex.Message);
224.    }
225.    byte[] plainText = new byte[plainTextBlob.cbData];
226.    Marshal.Copy(plainTextBlob.pbData, plainText, 0, plainTextBlob.cbData);
227.    return plainText;
228.  }
229.
230.   private void InitPromptstruct(ref CRYPTPROTECT_PROMPTSTRUCT ps)
231.   {
232.    ps.cbSize = Marshal.SizeOf(typeof(CRYPTPROTECT_PROMPTSTRUCT));
233.    ps.dwPromptFlags = 0;
234.    ps.hwndApp = NullPtr;
235.    ps.szPrompt = null;
236.   }
237.
238.   private unsafe static String GetErrorMessage(int errorCode)
239.   {
240.    int FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100;
241.    int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
242.    int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
243.    int messageSize = 255;
244.    String lpMsgBuf = "";
245.    int dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
246.    IntPtr ptrlpSource = new IntPtr();
247.    IntPtr prtArguments = new IntPtr();
248.    int retVal = FormatMessage(dwFlags, ref ptrlpSource, errorCode, 0,
249.    ref lpMsgBuf, messageSize, &prtArguments);
250.    if(0 == retVal)
251.    {
252.     throw new Exception("Failed to format message for error code " + errorCode + ". ");
253.    }
254.    return lpMsgBuf;
255.   }
256.
257. }
258.}

Pour compiler ce code, vous pouvez utiliser Visual Studio.Net ou encore, en DOS, la bonne vieille méthode de la ligne de commande, en écrivant :

csc /out:bin\Crypt.dll /t:library Crypt.cs /r:System.dll /unsafe

Note : Dans la ligne ci-dessus, Crypt.dll sera placé dans le dossier bin à la racine de votre application web. Sur la plupart des site web, cette racine se trouve dans c:\InetPub\wwwRoot.

Création du code de cryptage

La librairie étant maintenant à notre disposition, il faut maintenant créer une page qui servira à crypter notre chaîne de connexion.

Listing 2.0 Création du code de cryptage
1. <%@ Page language="c#" %>
2. 
3. <%@ Import namespace="System.Text"%>
4. <%@ Import namespace="data.security.encryption"%>
5. 
6. <script language="c#" runat="server">
7. void Page_Load(Object Src, EventArgs E)
8. {
9.  if (Page.IsPostBack)
10.  {
11.   DataProtector dp = new DataProtector(DataProtector.Store.USE_MACHINE_STORE);
12.   try
13.   {
14.    byte[] dataToEncrypt = Encoding.ASCII.GetBytes(txt1.Text);
15.    Response.Write ("<textarea rows=\"5\" cols=\"60\">" + Convert.ToBase64String(dp.Encrypt(dataToEncrypt,null)) + "</textarea>");
16.   }
17.   catch(Exception ex)
18.   {
19.    Response.Write (ex.Message);
20.   }
21.  }
22. }
23. </script>
24. <html>
25. <head>
26. <title>Exemple de page ASP.NET</title>
27. </head>
28. <body>
29. <form id="form1" runat="SERVER">
30. <asp:TextBox id="txt1" size="100" runat="SERVER"/>
31. <br />
32. <asp:Button id="btnSubmit" text="Soumettre" runat="SERVER" />
33. </form>
34. </body>
35. </html>

Mise à jour du fichier web.config

Lorsque la page aspx sera créé, rendez-vous sur cette page et entrez dans la zone de texte votre chaîne de connexion par exemple : data source=mondatasource;userid=monuser;password=monpassword;

Soumettez ensuite le formulaire. Dans une zone de texte apparaîtra le résultat.

éditez ensuite votre fichier web.config, créez une zone appSettings dans la balise configuration :

<appSettings>
<add key="ConnectionString" value="" />
</appSettings>

Copier le résultat dans la page aspx et collez celui-ci dans entre les guillemets de l’attribut «value».

Création d’une page de décryptage

Listing 3.0 Création d’une page de décryptage
1. <%@ Page language="c#" %>
2. 
3. <%@ Import namespace="System.Text"%>
4. <%@ Import namespace="System.Configuration"%>
5. <%@ Import namespace="Crypt"%>
6. 
7. <script language="c#" runat="server">
8.   void Page_Load(Object Src, EventArgs E)
9 .   {
10.    DataProtector dp = new DataProtector(DataProtector.Store.USE_MACHINE_STORE);
11.    try
12.    {
13.      string strAppSettings = ConfigurationSettings.AppSettings["ConnectionString"];
14.      byte[] dataToDecrypt = Convert.FromBase64String(strAppSettings);
15.      string strCon = Encoding.ASCII.GetString(dp.Decrypt(dataToDecrypt,null));
16.      lbl1.Text = strCon;
17.      Response.Write (strAppSettings);
18.     }
19.     catch(Exception ex)
20.     {
21.      Response.Write (ex.Message);
22.    }
23.   }
24. </script>
25. <html>
26. <head>
27. <title>Exemple de page ASP.NET</title>
28. </head>
29. <body>
30. <form id="form1" runat="SERVER">
31. <asp:Label id="lbl1" runat="SERVER"/>
32. </form>
33. </body>
34. </html>

Auteur : Sylvain Bilodeau

Date de mise en ligne : 2003-07-02

Aucun commentaire pour l'instant.