Encrypting credentials in a WPF application

前端 未结 1 1871
北荒
北荒 2020-11-28 04:33

In a WPF application, I would like to provide the typical \"Remember Me\" option to remember credentials and use them automatically next time the application is launched.

相关标签:
1条回答
  • 2020-11-28 05:11

    Here's a summary of my blog post: How to store a password on Windows?

    You can use the Data Protection API and its .NET implementation (ProtectedData) to encrypt the password. Here's an example:

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

    Or you can use the Windows Credential Manager (This is the way I prefer because it allows users to backup/restore/edit their credentials even if your application has no such functionality). I've created a NuGet package Meziantou.Framework.Win32.CredentialManager. How to use it:

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

    Original answer with the native API wrapper (A more recent version of this is available on GitHub):

    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<Credential> EnumerateCrendentials()
        {
            List<Credential> result = new List<Credential>();
    
            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"));
    
    0 讨论(0)
提交回复
热议问题