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