mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
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:
parent
0aed13a2cf
commit
79589b07fc
9 changed files with 76 additions and 48 deletions
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
8
src/Core/Enums/HashPurpose.cs
Normal file
8
src/Core/Enums/HashPurpose.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Bit.Core.Enums
|
||||||
|
{
|
||||||
|
public enum HashPurpose : byte
|
||||||
|
{
|
||||||
|
ServerAuthorization = 1,
|
||||||
|
LocalAuthorization = 2,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue