3

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

Florian Moser
  • 2,583
  • 1
  • 30
  • 40
  • Could you please explain what do you mean "I want a symmetric way to store and retrieve my password. This obviously does not have to be the same password as the one used to login, but it has to be same every time it is retrieved."? AFAIK, Windows Hello is more like a two-factor authentication, it not used to store and retrieve password. – Scavenger May 25 '17 at 08:57
  • I've removed the strangely formulated phrase, in short; I want to store data symmetrically. "Windows Hello is more like a two-factor authentication": this was my impression too unfortunately, therefore my question if there is a way I can use it as a secure "symmetric" storage. – Florian Moser May 25 '17 at 11:41

2 Answers2

4

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);
Florian Moser
  • 2,583
  • 1
  • 30
  • 40
  • 1
    This answer works well for me. Just to expand a little bit, this will sign the challenge using an 2048 bit RSA keypair and will produce exactly 256 bytes of data (= length of RSA key). So if for instance the data is used as a symmetric encryption key for AES or similar, probably not all of it will be used. – Alex Suzuki Aug 09 '21 at 07:47
  • That raises a very good point @AlexSuzuki! I have edited the answer to include more context. – Florian Moser Aug 10 '21 at 08:29
0

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

fs_dm
  • 391
  • 3
  • 13
  • 1
    I am not sure this solves the problem I've had: I wanted to store a secret securely which could be retrieved again by using either a password (you answer solves this problem) or windows hello (which your answer can't deal with) – Florian Moser Jan 18 '19 at 12:10
  • If I remember correctly, I've solved this by using a specific signature (similar to the example 2 from the question): After the Windows Hello login, I would sign a specific message (which never changed). I then used this signature as an encryption key and hence could then symmetrically encrypt. – Florian Moser Jan 18 '19 at 12:15
  • @FlorianMoser, I have just updated my answer, please check – fs_dm Jan 18 '19 at 12:32
  • 1
    The posted example can not provide the same level of security than the approach I have described above (encrypt/decrypt the password using the signature over a fixed message): With the solution from your answer, if one removes the Windows Hello call (for example in a buggy update), the SignIn would complete successfully! This is not possible with the signature approach as the decryption is impossible without the Windows Hello signature. I'll formulate how I have solved the issue exactly as an answer as soon as I find the time. Your answer is still correct though; and I could done it that way. – Florian Moser Jan 20 '19 at 00:03
  • @FlorianMoser Did you use 'KeyCredentialManager' in you approach? – fs_dm Jan 21 '19 at 09:54