Cifrado de credenciales en una aplicación WPF

En una aplicación WPF, me gustaría proporcionar la típica opción “Recordarme” para recordar las credenciales y usarlas automáticamente la próxima vez que se inicie la aplicación.

El uso de un hash unidireccional claramente no es una opción, y si bien puedo almacenar credenciales en un almacenamiento aislado o en el registro , hay un problema al respecto al encriptar las credenciales.

Si utilizo un algoritmo de cifrado de clave simétrica, tendré que guardar la clave en algún lugar. Y si la clave está, por ejemplo, codificada en la memoria, entonces me imagino que sería fácil desensamblar los ensamblajes .NET y encontrarlos.

¿Cuál es la mejor manera de cifrar las credenciales en .NET y mantenerlas seguras, manteniendo la clave de cifrado completamente fuera de scope?

Puede usar la API de protección de datos y su implementación .NET ( ProtectedData ) para cifrar la contraseña. Aquí hay un ejemplo:

public static string Protect(string str) { byte[] entropy = Encoding.ASCII.GetBytes(Assembly.GetExecutingAssembly().FullName); byte[] data = Encoding.ASCII.GetBytes(str); string protectedData = Convert.ToBase64String(ProtectedData.Protect(data, entropy, DataProtectionScope.CurrentUser)); return protectedData; } public static string Unprotect(string str) { byte[] protectedData = Convert.FromBase64String(str); byte[] entropy = Encoding.ASCII.GetBytes(Assembly.GetExecutingAssembly().FullName); string data = Encoding.ASCII.GetString(ProtectedData.Unprotect(protectedData, entropy, DataProtectionScope.CurrentUser)); return data; } 

O puede usar el Administrador de credenciales de Windows (esta es la forma que prefiero porque permite a los usuarios realizar copias de seguridad / restaurar / editar sus credenciales, incluso si su aplicación no tiene dicha funcionalidad). Creé un paquete NuGet Meziantou.Framework.Win32.CredentialManager . Cómo usarlo:

 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"); 

Respuesta original con el contenedor API nativo:

 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(string 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) { string applicationName = Marshal.PtrToStringUni(credential.TargetName); string userName = Marshal.PtrToStringUni(credential.UserName); string secret = null; if (credential.CredentialBlob != IntPtr.Zero) { secret = Marshal.PtrToStringUni(credential.CredentialBlob, (int)credential.CredentialBlobSize / 2); } return new Credential(credential.Type, applicationName, userName, secret); } public static int WriteCredential(string applicationName, string userName, string 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.AttributeCount = 0; credential.Attributes = 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.StringToCoTaskMemUni(applicationName); credential.CredentialBlob = Marshal.StringToCoTaskMemUni(secret); credential.UserName = Marshal.StringToCoTaskMemUni(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(string.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); } } 

Uso:

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