Encrypting credentials in a WPF application

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

Leave a Comment

tech