Use 2 iterations for local password hashing (#1423)

* Add HashPurpose parameter to HashPasswordAsync

* Use 2 iterations for local password hashing

* Force logout if user has old keyHash stored

* Revert "Force logout if user has old keyHash stored"

This reverts commit 497d4928fa.

* Add backwards compatability with existing keyHash
This commit is contained in:
Thomas Rittson 2021-06-14 14:39:34 -07:00 committed by GitHub
parent 0aed13a2cf
commit 79589b07fc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 76 additions and 48 deletions

View file

@ -241,32 +241,31 @@ namespace Bit.App.Pages
else else
{ {
var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdf, kdfIterations); var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdf, kdfIterations);
var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key); var storedKeyHash = await _cryptoService.GetKeyHashAsync();
var passwordValid = false; var passwordValid = false;
if (keyHash != null)
if (storedKeyHash != null)
{ {
var storedKeyHash = await _cryptoService.GetKeyHashAsync(); passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, key);
if (storedKeyHash != null) }
else
{
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization);
var request = new PasswordVerificationRequest();
request.MasterPasswordHash = keyHash;
try
{ {
passwordValid = storedKeyHash == keyHash; await _apiService.PostAccountVerifyPasswordAsync(request);
passwordValid = true;
var localKeyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization);
await _cryptoService.SetKeyHashAsync(localKeyHash);
} }
else catch (Exception e)
{ {
await _deviceActionService.ShowLoadingAsync(AppResources.Loading); System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", e.GetType(), e.StackTrace);
var request = new PasswordVerificationRequest();
request.MasterPasswordHash = keyHash;
try
{
await _apiService.PostAccountVerifyPasswordAsync(request);
passwordValid = true;
await _cryptoService.SetKeyHashAsync(keyHash);
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", e.GetType(), e.StackTrace);
}
await _deviceActionService.HideLoadingAsync();
} }
await _deviceActionService.HideLoadingAsync();
} }
if (passwordValid) if (passwordValid)
{ {

View file

@ -138,7 +138,8 @@ namespace Bit.App.Pages
var kdfIterations = 100000; var kdfIterations = 100000;
var email = await _userService.GetEmailAsync(); var email = await _userService.GetEmailAsync();
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, kdfIterations); var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, kdfIterations);
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key); var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization);
var localMasterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization);
Tuple<SymmetricCryptoKey, EncString> encKey; Tuple<SymmetricCryptoKey, EncString> encKey;
var existingEncKey = await _cryptoService.GetEncKeyAsync(); var existingEncKey = await _cryptoService.GetEncKeyAsync();
@ -174,7 +175,7 @@ namespace Bit.App.Pages
await _userService.SetInformationAsync(await _userService.GetUserIdAsync(), await _userService.SetInformationAsync(await _userService.GetUserIdAsync(),
await _userService.GetEmailAsync(), kdf, kdfIterations); await _userService.GetEmailAsync(), kdf, kdfIterations);
await _cryptoService.SetKeyAsync(key); await _cryptoService.SetKeyAsync(key);
await _cryptoService.SetKeyHashAsync(masterPasswordHash); await _cryptoService.SetKeyHashAsync(localMasterPasswordHash);
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString); await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString);
await _cryptoService.SetEncPrivateKeyAsync(keys.Item2.EncryptedString); await _cryptoService.SetEncPrivateKeyAsync(keys.Item2.EncryptedString);
await _deviceActionService.HideLoadingAsync(); await _deviceActionService.HideLoadingAsync();

View file

@ -103,11 +103,10 @@ namespace Bit.App.Pages
return; return;
} }
var keyHash = await _cryptoService.HashPasswordAsync(_masterPassword, null);
MasterPassword = string.Empty; MasterPassword = string.Empty;
var storedKeyHash = await _cryptoService.GetKeyHashAsync(); var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(_masterPassword, null);
if (storedKeyHash == null || keyHash == null || storedKeyHash != keyHash) if (passwordValid)
{ {
await _platformUtilsService.ShowDialogAsync(_i18nService.T("InvalidMasterPassword")); await _platformUtilsService.ShowDialogAsync(_i18nService.T("InvalidMasterPassword"));
return; return;

View file

@ -29,15 +29,7 @@ namespace Bit.App.Services
return false; return false;
}; };
var keyHash = await _cryptoService.HashPasswordAsync(password, null); return await _cryptoService.CompareAndUpdateKeyHashAsync(password, null);
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
if (storedKeyHash == null || keyHash == null || storedKeyHash != keyHash)
{
return false;
}
return true;
}; };
return await _platformUtilsService.ShowPasswordDialogAsync(AppResources.PasswordConfirmation, AppResources.PasswordConfirmationDesc, validator); return await _platformUtilsService.ShowPasswordDialogAsync(AppResources.PasswordConfirmation, AppResources.PasswordConfirmationDesc, validator);

View file

@ -30,8 +30,9 @@ namespace Bit.Core.Abstractions
Task<Dictionary<string, SymmetricCryptoKey>> GetOrgKeysAsync(); Task<Dictionary<string, SymmetricCryptoKey>> GetOrgKeysAsync();
Task<byte[]> GetPrivateKeyAsync(); Task<byte[]> GetPrivateKeyAsync();
Task<byte[]> GetPublicKeyAsync(); Task<byte[]> GetPublicKeyAsync();
Task<bool> CompareAndUpdateKeyHashAsync(string masterPassword, SymmetricCryptoKey key);
Task<bool> HasEncKeyAsync(); Task<bool> HasEncKeyAsync();
Task<string> HashPasswordAsync(string password, SymmetricCryptoKey key); Task<string> HashPasswordAsync(string password, SymmetricCryptoKey key, HashPurpose hashPurpose = HashPurpose.ServerAuthorization);
Task<bool> HasKeyAsync(); Task<bool> HasKeyAsync();
Task<Tuple<SymmetricCryptoKey, EncString>> MakeEncKeyAsync(SymmetricCryptoKey key); Task<Tuple<SymmetricCryptoKey, EncString>> MakeEncKeyAsync(SymmetricCryptoKey key);
Task<SymmetricCryptoKey> MakeKeyAsync(string password, string salt, KdfType? kdf, int? kdfIterations); Task<SymmetricCryptoKey> MakeKeyAsync(string password, string salt, KdfType? kdf, int? kdfIterations);

View file

@ -0,0 +1,8 @@
namespace Bit.Core.Enums
{
public enum HashPurpose : byte
{
ServerAuthorization = 1,
LocalAuthorization = 2,
}
}

View file

@ -93,6 +93,7 @@ namespace Bit.Core.Services
public string Email { get; set; } public string Email { get; set; }
public string MasterPasswordHash { get; set; } public string MasterPasswordHash { get; set; }
public string LocalMasterPasswordHash { get; set; }
public string Code { get; set; } public string Code { get; set; }
public string CodeVerifier { get; set; } public string CodeVerifier { get; set; }
public string SsoRedirectUrl { get; set; } public string SsoRedirectUrl { get; set; }
@ -123,7 +124,8 @@ namespace Bit.Core.Services
SelectedTwoFactorProviderType = null; SelectedTwoFactorProviderType = null;
var key = await MakePreloginKeyAsync(masterPassword, email); var key = await MakePreloginKeyAsync(masterPassword, email);
var hashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key); var hashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key);
return await LogInHelperAsync(email, hashedPassword, null, null, null, key, null, null, null); var localHashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key, HashPurpose.LocalAuthorization);
return await LogInHelperAsync(email, hashedPassword, localHashedPassword, null, null, null, key, null, null, null);
} }
public async Task<AuthResult> LogInSsoAsync(string code, string codeVerifier, string redirectUrl) public async Task<AuthResult> LogInSsoAsync(string code, string codeVerifier, string redirectUrl)
@ -135,7 +137,7 @@ namespace Bit.Core.Services
public Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken, public Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken,
bool? remember = null) bool? remember = null)
{ {
return LogInHelperAsync(Email, MasterPasswordHash, Code, CodeVerifier, SsoRedirectUrl, _key, return LogInHelperAsync(Email, MasterPasswordHash, LocalMasterPasswordHash, Code, CodeVerifier, SsoRedirectUrl, _key,
twoFactorProvider, twoFactorToken, remember); twoFactorProvider, twoFactorToken, remember);
} }
@ -145,7 +147,8 @@ namespace Bit.Core.Services
SelectedTwoFactorProviderType = null; SelectedTwoFactorProviderType = null;
var key = await MakePreloginKeyAsync(masterPassword, email); var key = await MakePreloginKeyAsync(masterPassword, email);
var hashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key); var hashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key);
return await LogInHelperAsync(email, hashedPassword, null, null, null, key, twoFactorProvider, var localHashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key, HashPurpose.LocalAuthorization);
return await LogInHelperAsync(email, hashedPassword, localHashedPassword, null, null, null, key, twoFactorProvider,
twoFactorToken, remember); twoFactorToken, remember);
} }
@ -153,7 +156,7 @@ namespace Bit.Core.Services
TwoFactorProviderType twoFactorProvider, string twoFactorToken, bool? remember = null) TwoFactorProviderType twoFactorProvider, string twoFactorToken, bool? remember = null)
{ {
SelectedTwoFactorProviderType = null; SelectedTwoFactorProviderType = null;
return await LogInHelperAsync(null, null, code, codeVerifier, redirectUrl, null, twoFactorProvider, return await LogInHelperAsync(null, null, null, code, codeVerifier, redirectUrl, null, twoFactorProvider,
twoFactorToken, remember); twoFactorToken, remember);
} }
@ -266,8 +269,8 @@ namespace Bit.Core.Services
return await _cryptoService.MakeKeyAsync(masterPassword, email, kdf, kdfIterations); return await _cryptoService.MakeKeyAsync(masterPassword, email, kdf, kdfIterations);
} }
private async Task<AuthResult> LogInHelperAsync(string email, string hashedPassword, string code, private async Task<AuthResult> LogInHelperAsync(string email, string hashedPassword, string localHashedPassword,
string codeVerifier, string redirectUrl, SymmetricCryptoKey key, string code, string codeVerifier, string redirectUrl, SymmetricCryptoKey key,
TwoFactorProviderType? twoFactorProvider = null, string twoFactorToken = null, bool? remember = null) TwoFactorProviderType? twoFactorProvider = null, string twoFactorToken = null, bool? remember = null)
{ {
var storedTwoFactorToken = await _tokenService.GetTwoFactorTokenAsync(email); var storedTwoFactorToken = await _tokenService.GetTwoFactorTokenAsync(email);
@ -318,6 +321,7 @@ namespace Bit.Core.Services
var twoFactorResponse = response.Item2; var twoFactorResponse = response.Item2;
Email = email; Email = email;
MasterPasswordHash = hashedPassword; MasterPasswordHash = hashedPassword;
LocalMasterPasswordHash = localHashedPassword;
Code = code; Code = code;
CodeVerifier = codeVerifier; CodeVerifier = codeVerifier;
SsoRedirectUrl = redirectUrl; SsoRedirectUrl = redirectUrl;
@ -339,7 +343,7 @@ namespace Bit.Core.Services
if (_setCryptoKeys) if (_setCryptoKeys)
{ {
await _cryptoService.SetKeyAsync(key); await _cryptoService.SetKeyAsync(key);
await _cryptoService.SetKeyHashAsync(hashedPassword); await _cryptoService.SetKeyHashAsync(localHashedPassword);
await _cryptoService.SetEncKeyAsync(tokenResponse.Key); await _cryptoService.SetEncKeyAsync(tokenResponse.Key);
// User doesn't have a key pair yet (old account), let's generate one for them. // User doesn't have a key pair yet (old account), let's generate one for them.

View file

@ -281,6 +281,28 @@ namespace Bit.Core.Services
return orgKeys[orgId]; return orgKeys[orgId];
} }
public async Task<bool> CompareAndUpdateKeyHashAsync(string masterPassword, SymmetricCryptoKey key)
{
var storedKeyHash = await GetKeyHashAsync();
if (masterPassword != null && storedKeyHash != null)
{
var localKeyHash = await HashPasswordAsync(masterPassword, key, HashPurpose.LocalAuthorization);
if (localKeyHash != null && storedKeyHash == localKeyHash)
{
return true;
}
var serverKeyHash = await HashPasswordAsync(masterPassword, key, HashPurpose.ServerAuthorization);
if (serverKeyHash != null & storedKeyHash == serverKeyHash)
{
await SetKeyHashAsync(localKeyHash);
return true;
}
}
return false;
}
public async Task<bool> HasKeyAsync() public async Task<bool> HasKeyAsync()
{ {
var key = await GetKeyAsync(); var key = await GetKeyAsync();
@ -433,7 +455,7 @@ namespace Bit.Core.Services
return new SymmetricCryptoKey(sendKey); return new SymmetricCryptoKey(sendKey);
} }
public async Task<string> HashPasswordAsync(string password, SymmetricCryptoKey key) public async Task<string> HashPasswordAsync(string password, SymmetricCryptoKey key, HashPurpose hashPurpose = HashPurpose.ServerAuthorization)
{ {
if (key == null) if (key == null)
{ {
@ -443,7 +465,8 @@ namespace Bit.Core.Services
{ {
throw new Exception("Invalid parameters."); throw new Exception("Invalid parameters.");
} }
var hash = await _cryptoFunctionService.Pbkdf2Async(key.Key, password, CryptoHashAlgorithm.Sha256, 1); var iterations = hashPurpose == HashPurpose.LocalAuthorization ? 2 : 1;
var hash = await _cryptoFunctionService.Pbkdf2Async(key.Key, password, CryptoHashAlgorithm.Sha256, iterations);
return Convert.ToBase64String(hash); return Convert.ToBase64String(hash);
} }

View file

@ -190,19 +190,20 @@ namespace Bit.iOS.Core.Controllers
else else
{ {
var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdf, kdfIterations); var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdf, kdfIterations);
var keyHash = await _cryptoService.HashPasswordAsync(inputtedValue, key2);
var storedKeyHash = await _cryptoService.GetKeyHashAsync(); var storedKeyHash = await _cryptoService.GetKeyHashAsync();
if (storedKeyHash == null) if (storedKeyHash == null)
{ {
var oldKey = await _secureStorageService.GetAsync<string>("oldKey"); var oldKey = await _secureStorageService.GetAsync<string>("oldKey");
if (key2.KeyB64 == oldKey) if (key2.KeyB64 == oldKey)
{ {
var localKeyHash = await _cryptoService.HashPasswordAsync(inputtedValue, key2, HashPurpose.LocalAuthorization);
await _secureStorageService.RemoveAsync("oldKey"); await _secureStorageService.RemoveAsync("oldKey");
await _cryptoService.SetKeyHashAsync(keyHash); await _cryptoService.SetKeyHashAsync(localKeyHash);
storedKeyHash = keyHash;
} }
} }
if (storedKeyHash != null && keyHash != null && storedKeyHash == keyHash) var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, key2);
if (passwordValid)
{ {
if (_pinSet.Item1) if (_pinSet.Item1)
{ {