Encore un article sur le stockage des mots de passe 🙂

Dans mon dernier article je conseillais de regarder les mécanismes existants avant de tenter de refaire la même chose (surement en moins bien). Cette fois-ci je vais vous présenter une fonctionnalité géniale de Windows : le Credential Manager.

credential manager

Le Credential Manager permet d’enregistrer un triplé Application/UserName/Password. Il fourni également une interface graphique permettant à l’utilisateur d’ajouter ou supprimer les credentials et aussi de les importer et de les exporter. Bien évidemment Windows se débrouille pour garder les mots de passe en sécurité (comme avec Data Protection API).

demo

Pour ajouter et lire une entrée dans le Credential Manager, Windows fournit 3 méthodes CredRead et CredWrite et CredDelete et la structure CREDENTIAL :

[DllImport("Advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool CredRead(string target, CredentialType type, int reservedFlag, out IntPtr credentialPtr);
 
[DllImport("Advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool CredWrite([In] ref NativeCredential userCredential, [In] UInt32 flags);
 
[DllImport("advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool CredDelete(string target, CredentialType type, int flags);
 
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct Credential 
{ 
   public UInt32 Flags; 
   public CredentialType Type; 
   public IntPtr TargetName; 
   public IntPtr Comment; 
   public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten; 
   public UInt32 CredentialBlobSize; 
   public IntPtr CredentialBlob; 
   public UInt32 Persist; 
   public UInt32 AttributeCount; 
   public IntPtr Attributes; 
   public IntPtr TargetAlias; 
   public IntPtr UserName; 
} 
  
enum CredentialType 
{ 
   Generic = 1, 
   DomainPassword, 
   DomainCertificate, 
   DomainVisiblePassword, 
   Maximum 
} 
  
enum CredentialPersistence 
{ 
   Session = 1, 
   LocalMachine, 
   Enterprise 
}

Il “ suffit ” ensuite d’appeler ces méthodes :

public static int WriteCredential(string applicationName, string userName, string secret) 
{ 
   byte[] byteArray = Encoding.Unicode.GetBytes(secret); 
   if (byteArray.Length > 512) // il est possible d'améliorer ce test (voir code complet ci-dessous): Windows XP et Vista : 512; Windows 7 et plus : 5*512 
   throw new ArgumentOutOfRangeException(secret, "The secret message has exceeded 512 bytes."); 
   Credential credential = new Credential(); 
   credential.AttributeCount = 0; 
   credential.Attributes = IntPtr.Zero; 
   credential.Comment = IntPtr.Zero; 
   credential.TargetAlias = IntPtr.Zero; 
   credential.Type = CredentialType.Generic; 
   credential.Persist = (UInt32)CredentialPersistence.LocalMachine; 
   credential.CredentialBlobSize = (UInt32)Encoding.Unicode.GetBytes(secret).Length;
   credential.TargetName = Marshal.StringToCoTaskMemUni(applicationName); 
   credential.CredentialBlob = Marshal.StringToCoTaskMemUni(secret); 
   credential.UserName = Marshal.StringToCoTaskMemUni(userName ?? Environment.UserName); 
   bool written = CredWrite(ref credential, 0); 
   int lastError = Marshal.GetLastWin32Error(); 
   if (written) 
   return 0; 
   throw new Exception(string.Format("CredWrite failed with the error code {0}.", lastError)); 
} 
  
public static Tuple<string, string> ReadCredential(string applicationName) 
{ 
   IntPtr nCredPtr; 
   bool read = CredRead(applicationName, CredentialType.Generic, 0, out nCredPtr); 
   int lastError = Marshal.GetLastWin32Error(); 
   Trace.WriteLineIf(lastError != 0, "ReadCred: " + lastError); 
   if (read) 
   { 
   using (CriticalCredentialHandle critCred = new CriticalCredentialHandle(nCredPtr)) 
   { 
   Credential cred = critCred.GetCredential(); 
   string userName = Marshal.PtrToStringUni(cred.UserName); 
   string secret = Marshal.PtrToStringUni(cred.CredentialBlob, (int)cred.CredentialBlobSize / 2); 
   return Tuple.Create(userName, secret); 
   } 
   } 
   return null; 
} 
  
public static void DeleteCredential(string applicationName) 
{ 
   CredDelete(applicationName, CredentialType.Generic, 0); 
}

et voilĂ 

WriteCredential("Demo", "GĂ©rald", "Passw0rd"); 
Console.WriteLine(ReadCredential("Demo"));

Je n’ai prĂ©sentĂ© qu’une partie de ce que l’API offre. Vous pouvez Ă©galement Ă©numĂ©rer les entrĂ©es avec la mĂ©thode CredEnumerate(l’implĂ©mentation est dans le fichier attachĂ©) ou afficher une fenĂŞtre dans laquelle l’utilisateur peut saisir ses credentials (CredUIPromptForWindowsCredentials) :

credentials

Le code complet : CredentialManager.cs