store password with Windows Hello

1.6k Views Asked by At

I want to store and retrieve a password with Windows Hello. The user can choose at login time if he wants to input his password manually, or if he wants to use Windows Hello to unlock (which then retrieves the last used password, and fills it in for the user).

If Windows Hello is setup correctly there are two use cases in the doc.
One to just unlock:

UserConsentVerificationResult consentResult = await UserConsentVerifier.RequestVerificationAsync("userMessage");
if (consentResult.Equals(UserConsentVerificationResult.Verified))
{
   // continue
}

and one to sign a message from the server:

var openKeyResult = await KeyCredentialManager.OpenAsync(AccountId);
if (openKeyResult.Status == KeyCredentialStatus.Success)
{
    var userKey = openKeyResult.Credential;
    var publicKey = userKey.RetrievePublicKey();
    //the message is the challenge from the server
    var signResult = await userKey.RequestSignAsync(message);

    if (signResult.Status == KeyCredentialStatus.Success)
    {
        //the with the private key of the user signed message        
        return signResult.Result;
    }
}

Both is not very useful for my use-case: I want a symmetric way to store and retrieve the password.

My question in short:
Is there a way to symmetrically store data with Windows Hello?

relevant docs:
https://learn.microsoft.com/en-us/windows/uwp/security/microsoft-passport

2

There are 2 best solutions below

2
On BEST ANSWER

I have solved this problem by encrypting / decrypting the secret I wanted to store using a password generated with Windows Hello. The password was a signature over a fixed message.

A complete code example (untested) to illustrate my point:

const accountId = "A ID for this key";
const challenge = "sign this challenge using Windows Hello to get a secure string";

public async Task<byte[]> GetSecureEncryptionKey() {
    // if first time; we first need to create a key
    if (firstTime) {
        if (!await this.CreateKeyAsync()) {
            return null;
        }
    }

    // get the key using Windows Hellp
    return await this.GetKeyAsync();
}

private async Task<bool> CreateKeyAsync() {
    if (!await KeyCredentialManager.IsSupportedAsync()) {
        return false;
    }

    // if app opened for the first time, we need to create an account first
    var keyCreationResult = await KeyCredentialManager.RequestCreateAsync(AccountId, KeyCredentialCreationOption.ReplaceExisting);
    return keyCreationResult.Status == KeyCredentialStatus.Success);
}

private async Task<byte[]> GetKeyAsync() {
    var openKeyResult = await KeyCredentialManager.OpenAsync(AccountId);

    if (openKeyResult.Status == KeyCredentialStatus.Success)
    {
        // convert our string challenge to be able to sign it 
        var buffer = CryptographicBuffer.ConvertStringToBinary(
            challenge, BinaryStringEncoding.Utf8
        );
        
        // request a sign from the user
        var signResult = await openKeyResult.Credential.RequestSignAsync(buffer);

        // if successful, we can use that signature as a key
        if (signResult.Status == KeyCredentialStatus.Success)
        {
            return signResult.Result.ToArray();
        }
    }

    return null;
}

The full source is on github, and shows how I have integrated these concepts into the application.

However, this abuses cryptographic primitives intended for different purposes, which is very dangerous. Consider looking for a more sound approach before resorting to this workaround.

Concrete caveats:

  • This assumes the signature is not randomized. This is the case currently with Windows Hello, but might change in the future.
  • When using the signature as key, ensure the whole signature is used. If the encryption/decryption key needs 256 bits, you could use the SHA-256 algorithm to map the signature to the key, like: key = sha256(signature);
5
On

You could use PasswordVault as follows, for setting password:

    private void SetPassword(string password, string userName)
    {
        PasswordVault myVault = new PasswordVault();
        myVault.Add(new PasswordCredential("Your App ID", userName, password));
    }

for remove password:

    private void RemovePassword(string userName)
    {
        PasswordVault myVault = new PasswordVault();

        var password = myVault.Retrieve("Your App ID", userName);
        if (password != null)
            myVault.Remove(password);
    }

if you want use it with Windows Hello:

    public async Task<string> SignInAsync(string userName)
    {
        var result = await UserConsentVerifier.RequestVerificationAsync(requestMessage);
        if (result != UserConsentVerificationResult.Verified)
            return null;

        var vault = new PasswordVault();
        var credentials = vault.Retrieve("Your App ID", userName);

        return credentials?.Password;
    }

this gonna check Windows Hello before accessing the password value