Шифрование учетных данных в приложении WPF

В приложении WPF я хотел бы предоставить типичную опцию «Запомнить меня», чтобы помнить учетные данные и использовать их автоматически при следующем запуске приложения.

Использование одностороннего hashа явно не является опцией, и, хотя я могу хранить учетные данные в изолированном хранилище или в реестре , есть одна проблема, с которой приходится иметь дело при шифровании учетных данных.

Если я использую алгоритм шифрования с симметричным ключом, мне нужно будет где-то хранить ключ. И если ключ, например, жестко закодирован в памяти, то я предполагаю, что было бы легко разобрать сборки .NET и найти его.

Каков наилучший способ шифрования учетных данных в .NET и обеспечения их безопасности, сохраняя ключ шифрования полностью недоступным?

Вы можете использовать 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 (так я предпочитаю, потому что он позволяет пользователям делать резервные копии / восстанавливать / редактировать свои учетные данные, даже если ваше приложение не имеет таких функций). Я создал пакет 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")); 
  • Лучший способ создать ключи AES, чем посев SecureRandom
  • Java - шифрование / дешифрование имени пользователя и пароля из файла конфигурации
  • Шифрование файлов cookie в ASP.NET
  • Почему безопасность через неясность - плохая идея?
  • Какая оптимальная длина для паролей пользователей?
  • Давайте будем гением компьютера.