Chiffrement des informations d’identification dans une application WPF

Dans une application WPF, j’aimerais fournir l’option typique “Mémoriser mes informations” pour mémoriser les informations d’identification et les utiliser automatiquement au prochain lancement de l’application.

L’utilisation d’un hachage unidirectionnel n’est clairement pas une option. Bien que je puisse stocker les informations d’identification dans un stockage isolé ou dans le registre , il existe un problème à résoudre lors du cryptage des informations d’identification.

Si j’utilise un algorithme de chiffrement à clé symésortingque, je devrai stocker la clé quelque part. Et si la clé est, par exemple, codée en dur dans la mémoire, alors j’imagine qu’il serait facile de désassembler les assemblys .NET et de la trouver.

Quel est le meilleur moyen de chiffrer les informations d’identification dans .NET et de les maintenir en sécurité, tout en maintenant la clé de chiffrement hors de scope?

Vous pouvez utiliser l’API Data Protection et son implémentation .NET ( ProtectedData ) pour chiffrer le mot de passe. Voici un exemple:

public static ssortingng Protect(ssortingng str) { byte[] entropy = Encoding.ASCII.GetBytes(Assembly.GetExecutingAssembly().FullName); byte[] data = Encoding.ASCII.GetBytes(str); ssortingng protectedData = Convert.ToBase64Ssortingng(ProtectedData.Protect(data, entropy, DataProtectionScope.CurrentUser)); return protectedData; } public static ssortingng Unprotect(ssortingng str) { byte[] protectedData = Convert.FromBase64Ssortingng(str); byte[] entropy = Encoding.ASCII.GetBytes(Assembly.GetExecutingAssembly().FullName); ssortingng data = Encoding.ASCII.GetSsortingng(ProtectedData.Unprotect(protectedData, entropy, DataProtectionScope.CurrentUser)); return data; } 

Ou vous pouvez utiliser Windows Credential Manager (C’est ce que je préfère car il permet aux utilisateurs de sauvegarder / restaurer / modifier leurs informations d’identité même si votre application n’a pas cette fonctionnalité). J’ai créé un package NuGet Meziantou.Framework.Win32.CredentialManager . Comment l’utiliser:

 CredentialManager.WriteCredential("ApplicationName", "username", "Pa$$w0rd", CredentialPersistence.Session); var cred = CredentialManager.ReadCredential("ApplicationName"); Assert.AreEqual("username", cred.UserName); Assert.AreEqual("Pa$$w0rd", cred.Password); CredentialManager.DeleteCredential("ApplicationName"); 

Réponse originale avec le wrapper de l’API native:

 using System; using System.Collections.Generic; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; using System.Text; using System.ComponentModel; public static class CredentialManager { public static Credential ReadCredential(ssortingng applicationName) { IntPtr nCredPtr; bool read = CredRead(applicationName, CredentialType.Generic, 0, out nCredPtr); if (read) { using (CriticalCredentialHandle critCred = new CriticalCredentialHandle(nCredPtr)) { CREDENTIAL cred = critCred.GetCredential(); return ReadCredential(cred); } } return null; } private static Credential ReadCredential(CREDENTIAL credential) { ssortingng applicationName = Marshal.PtrToSsortingngUni(credential.TargetName); ssortingng userName = Marshal.PtrToSsortingngUni(credential.UserName); ssortingng secret = null; if (credential.CredentialBlob != IntPtr.Zero) { secret = Marshal.PtrToSsortingngUni(credential.CredentialBlob, (int)credential.CredentialBlobSize / 2); } return new Credential(credential.Type, applicationName, userName, secret); } public static int WriteCredential(ssortingng applicationName, ssortingng userName, ssortingng secret) { byte[] byteArray = Encoding.Unicode.GetBytes(secret); if (byteArray.Length > 512) throw new ArgumentOutOfRangeException("secret", "The secret message has exceeded 512 bytes."); CREDENTIAL credential = new CREDENTIAL(); credential.AtsortingbuteCount = 0; credential.Atsortingbutes = IntPtr.Zero; credential.Comment = IntPtr.Zero; credential.TargetAlias = IntPtr.Zero; credential.Type = CredentialType.Generic; credential.Persist = (UInt32)CredentialPersistence.Session; credential.CredentialBlobSize = (UInt32)Encoding.Unicode.GetBytes(secret).Length; credential.TargetName = Marshal.SsortingngToCoTaskMemUni(applicationName); credential.CredentialBlob = Marshal.SsortingngToCoTaskMemUni(secret); credential.UserName = Marshal.SsortingngToCoTaskMemUni(userName ?? Environment.UserName); bool written = CredWrite(ref credential, 0); int lastError = Marshal.GetLastWin32Error(); Marshal.FreeCoTaskMem(credential.TargetName); Marshal.FreeCoTaskMem(credential.CredentialBlob); Marshal.FreeCoTaskMem(credential.UserName); if (written) return 0; throw new Exception(ssortingng.Format("CredWrite failed with the error code {0}.", lastError)); } public static IReadOnlyList EnumerateCrendentials() { List result = new List(); int count; IntPtr pCredentials; bool ret = CredEnumerate(null, 0, out count, out pCredentials); if (ret) { for (int n = 0; n < count; n++) { IntPtr credential = Marshal.ReadIntPtr(pCredentials, n * Marshal.SizeOf(typeof(IntPtr))); result.Add(ReadCredential((CREDENTIAL)Marshal.PtrToStructure(credential, typeof(CREDENTIAL)))); } } else { int lastError = Marshal.GetLastWin32Error(); throw new Win32Exception(lastError); } return result; } [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 CREDENTIAL userCredential, [In] UInt32 flags); [DllImport("advapi32", SetLastError = true, CharSet = CharSet.Unicode)] static extern bool CredEnumerate(string filter, int flag, out int count, out IntPtr pCredentials); [DllImport("Advapi32.dll", EntryPoint = "CredFree", SetLastError = true)] static extern bool CredFree([In] IntPtr cred); private enum CredentialPersistence : uint { Session = 1, LocalMachine, Enterprise } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private 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; } sealed class CriticalCredentialHandle : CriticalHandleZeroOrMinusOneIsInvalid { public CriticalCredentialHandle(IntPtr preexistingHandle) { SetHandle(preexistingHandle); } public CREDENTIAL GetCredential() { if (!IsInvalid) { CREDENTIAL credential = (CREDENTIAL)Marshal.PtrToStructure(handle, typeof(CREDENTIAL)); return credential; } throw new InvalidOperationException("Invalid CriticalHandle!"); } protected override bool ReleaseHandle() { if (!IsInvalid) { CredFree(handle); SetHandleAsInvalid(); return true; } return false; } } } public enum CredentialType { Generic = 1, DomainPassword, DomainCertificate, DomainVisiblePassword, GenericCertificate, DomainExtended, Maximum, MaximumEx = Maximum + 1000, } public class Credential { private readonly string _applicationName; private readonly string _userName; private readonly string _password; private readonly CredentialType _credentialType; public CredentialType CredentialType { get { return _credentialType; } } public string ApplicationName { get { return _applicationName; } } public string UserName { get { return _userName; } } public string Password { get { return _password; } } public Credential(CredentialType credentialType, string applicationName, string userName, string password) { _applicationName = applicationName; _userName = userName; _password = password; _credentialType = credentialType; } public override string ToString() { return string.Format("CredentialType: {0}, ApplicationName: {1}, UserName: {2}, Password: {3}", CredentialType, ApplicationName, UserName, Password); } } 

Usage:

 WriteCredential("ApplicationName", "Meziantou", "Passw0rd"); Console.WriteLine(ReadCredential("Demo"));