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
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:
key = sha256(signature);