加密WPF应用程序中的凭据

在WPF应用程序中,我想提供典型的“记住我”选项来记住凭据,并在下次启动应用程序时自动使用它们。

使用单向散列显然不是一种选择,虽然我可以将凭据存储在独立存储或注册表中 ,但在加密凭据时有一个问题需要处理。

如果我使用对称密钥加密算法,我将需要将密钥存储在某处。 例如,如果密钥在内存中是硬编码的,那么我想可以很容易地反汇编.NET程序集并找到它。

在.NET中加密凭据并保持其安全性的最佳方法是什么,使加密密钥完全无法访问?

您可以使用Data Protection API及其.NET实现( ProtectedData )来加密密码。 这是一个例子:

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; } 

或者您可以使用Windows Credential Manager(这是我喜欢的方式,因为它允许用户备份/恢复/编辑其凭据,即使您的应用程序没有此类function)。 我创建了一个NuGet包Meziantou.Framework.Win32.CredentialManager 。 如何使用它:

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

原生API包装器的原始答案:

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

用法:

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