[PS-2358] Add kdf configuration options (#2328)

* Implement kdf configuration

* Remove unused import

* Move kdf parameters to kdfConfiguration struct

* Remove unused state migration service keys

* Revert newline changes in PCLCryptoFunctionService

* Update KdfConfiguration.cs

* Add checks for argon2, clean statemigration service

* Update constants

* Clean up code

* Further cleanup

* Change KdfType to non-nullable in SetKeyConnectorKeyRequest

---------

Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
This commit is contained in:
Bernd Schoolmann 2023-01-30 17:34:50 +01:00 committed by GitHub
parent 8b08f906bd
commit c3ad5f0580
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 135 additions and 101 deletions

View file

@ -228,8 +228,7 @@ namespace Bit.App.Pages
} }
ShowPassword = false; ShowPassword = false;
var kdf = await _stateService.GetKdfTypeAsync(); var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
var kdfIterations = await _stateService.GetKdfIterationsAsync();
if (PinLock) if (PinLock)
{ {
@ -239,7 +238,7 @@ namespace Bit.App.Pages
if (_isPinProtected) if (_isPinProtected)
{ {
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email, var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000), kdfConfig,
await _stateService.GetPinProtectedKeyAsync()); await _stateService.GetPinProtectedKeyAsync());
var encKey = await _cryptoService.GetEncKeyAsync(key); var encKey = await _cryptoService.GetEncKeyAsync(key);
var protectedPin = await _stateService.GetProtectedPinAsync(); var protectedPin = await _stateService.GetProtectedPinAsync();
@ -254,8 +253,7 @@ namespace Bit.App.Pages
} }
else else
{ {
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email, var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email, kdfConfig);
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
failed = false; failed = false;
Pin = string.Empty; Pin = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
@ -280,7 +278,7 @@ namespace Bit.App.Pages
} }
else else
{ {
var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdf, kdfIterations); var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdfConfig);
var storedKeyHash = await _cryptoService.GetKeyHashAsync(); var storedKeyHash = await _cryptoService.GetKeyHashAsync();
var passwordValid = false; var passwordValid = false;
@ -314,8 +312,7 @@ namespace Bit.App.Pages
var protectedPin = await _stateService.GetProtectedPinAsync(); var protectedPin = await _stateService.GetProtectedPinAsync();
var encKey = await _cryptoService.GetEncKeyAsync(key); var encKey = await _cryptoService.GetEncKeyAsync(key);
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey); var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, _email, var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, _email, kdfConfig);
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key.Key, pinKey)); await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key.Key, pinKey));
} }
MasterPassword = string.Empty; MasterPassword = string.Empty;

View file

@ -175,8 +175,8 @@ namespace Bit.App.Pages
Name = string.IsNullOrWhiteSpace(Name) ? null : Name; Name = string.IsNullOrWhiteSpace(Name) ? null : Name;
Email = Email.Trim().ToLower(); Email = Email.Trim().ToLower();
var kdf = KdfType.PBKDF2_SHA256; var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null);
var key = await _cryptoService.MakeKeyAsync(MasterPassword, Email, kdf, Constants.KdfIterations); var key = await _cryptoService.MakeKeyAsync(MasterPassword, Email, kdfConfig);
var encKey = await _cryptoService.MakeEncKeyAsync(key); var encKey = await _cryptoService.MakeEncKeyAsync(key);
var hashedPassword = await _cryptoService.HashPasswordAsync(MasterPassword, key); var hashedPassword = await _cryptoService.HashPasswordAsync(MasterPassword, key);
var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1); var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1);
@ -187,8 +187,10 @@ namespace Bit.App.Pages
MasterPasswordHash = hashedPassword, MasterPasswordHash = hashedPassword,
MasterPasswordHint = Hint, MasterPasswordHint = Hint,
Key = encKey.Item2.EncryptedString, Key = encKey.Item2.EncryptedString,
Kdf = kdf, Kdf = kdfConfig.Type,
KdfIterations = Constants.KdfIterations, KdfIterations = kdfConfig.Iterations,
KdfMemory = kdfConfig.Memory,
KdfParallelism = kdfConfig.Parallelism,
Keys = new KeysRequest Keys = new KeysRequest
{ {
PublicKey = keys.Item1, PublicKey = keys.Item1,

View file

@ -163,9 +163,9 @@ namespace Bit.App.Pages
return; return;
} }
var kdf = KdfType.PBKDF2_SHA256; var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null);
var email = await _stateService.GetEmailAsync(); var email = await _stateService.GetEmailAsync();
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, Constants.KdfIterations); var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdfConfig);
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization); var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization);
var localMasterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization); var localMasterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization);
@ -186,8 +186,10 @@ namespace Bit.App.Pages
MasterPasswordHash = masterPasswordHash, MasterPasswordHash = masterPasswordHash,
Key = encKey.Item2.EncryptedString, Key = encKey.Item2.EncryptedString,
MasterPasswordHint = Hint, MasterPasswordHint = Hint,
Kdf = kdf, Kdf = kdfConfig.Type.GetValueOrDefault(KdfType.PBKDF2_SHA256),
KdfIterations = Constants.KdfIterations, KdfIterations = kdfConfig.Iterations.GetValueOrDefault(Constants.Pbkdf2Iterations),
KdfMemory = kdfConfig.Memory,
KdfParallelism = kdfConfig.Parallelism,
OrgIdentifier = OrgIdentifier, OrgIdentifier = OrgIdentifier,
Keys = new KeysRequest Keys = new KeysRequest
{ {
@ -201,8 +203,7 @@ namespace Bit.App.Pages
await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount); await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount);
// Set Password and relevant information // Set Password and relevant information
await _apiService.SetPasswordAsync(request); await _apiService.SetPasswordAsync(request);
await _stateService.SetKdfTypeAsync(kdf); await _stateService.SetKdfConfigurationAsync(kdfConfig);
await _stateService.SetKdfIterationsAsync(Constants.KdfIterations);
await _cryptoService.SetKeyAsync(key); await _cryptoService.SetKeyAsync(key);
await _cryptoService.SetKeyHashAsync(localMasterPasswordHash); await _cryptoService.SetKeyHashAsync(localMasterPasswordHash);
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString); await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString);

View file

@ -43,12 +43,11 @@ namespace Bit.App.Pages
} }
// Retrieve details for key generation // Retrieve details for key generation
var kdf = await _stateService.GetKdfTypeAsync(); var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
var kdfIterations = await _stateService.GetKdfIterationsAsync();
var email = await _stateService.GetEmailAsync(); var email = await _stateService.GetEmailAsync();
// Create new key and hash new password // Create new key and hash new password
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, kdfIterations); var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdfConfig);
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key); var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key);
// Create new encKey for the User // Create new encKey for the User

View file

@ -422,12 +422,9 @@ namespace Bit.App.Pages
AppResources.Yes, AppResources.No); AppResources.Yes, AppResources.No);
} }
var kdf = await _stateService.GetKdfTypeAsync(); var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
var kdfIterations = await _stateService.GetKdfIterationsAsync();
var email = await _stateService.GetEmailAsync(); var email = await _stateService.GetEmailAsync();
var pinKey = await _cryptoService.MakePinKeyAysnc(pin, email, var pinKey = await _cryptoService.MakePinKeyAysnc(pin, email, kdfConfig);
kdf.GetValueOrDefault(Core.Enums.KdfType.PBKDF2_SHA256),
kdfIterations.GetValueOrDefault(5000));
var key = await _cryptoService.GetKeyAsync(); var key = await _cryptoService.GetKeyAsync();
var pinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey); var pinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey);

View file

@ -36,11 +36,10 @@ namespace Bit.Core.Abstractions
Task<string> HashPasswordAsync(string password, SymmetricCryptoKey key, HashPurpose hashPurpose = HashPurpose.ServerAuthorization); Task<string> HashPasswordAsync(string password, SymmetricCryptoKey key, HashPurpose hashPurpose = HashPurpose.ServerAuthorization);
Task<bool> HasKeyAsync(string userId = null); Task<bool> HasKeyAsync(string userId = null);
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, KdfConfig config);
Task<SymmetricCryptoKey> MakeKeyFromPinAsync(string pin, string salt, KdfType kdf, int kdfIterations, Task<SymmetricCryptoKey> MakeKeyFromPinAsync(string pin, string salt, KdfConfig config, EncString protectedKeyEs = null);
EncString protectedKeyEs = null);
Task<Tuple<string, EncString>> MakeKeyPairAsync(SymmetricCryptoKey key = null); Task<Tuple<string, EncString>> MakeKeyPairAsync(SymmetricCryptoKey key = null);
Task<SymmetricCryptoKey> MakePinKeyAysnc(string pin, string salt, KdfType kdf, int kdfIterations); Task<SymmetricCryptoKey> MakePinKeyAysnc(string pin, string salt, KdfConfig config);
Task<Tuple<EncString, SymmetricCryptoKey>> MakeShareKeyAsync(); Task<Tuple<EncString, SymmetricCryptoKey>> MakeShareKeyAsync();
Task<SymmetricCryptoKey> MakeSendKeyAsync(byte[] keyMaterial); Task<SymmetricCryptoKey> MakeSendKeyAsync(byte[] keyMaterial);
Task<int> RandomNumberAsync(int min, int max); Task<int> RandomNumberAsync(int min, int max);

View file

@ -37,10 +37,7 @@ namespace Bit.Core.Abstractions
Task SetPinProtectedAsync(string value, string userId = null); Task SetPinProtectedAsync(string value, string userId = null);
Task<EncString> GetPinProtectedKeyAsync(string userId = null); Task<EncString> GetPinProtectedKeyAsync(string userId = null);
Task SetPinProtectedKeyAsync(EncString value, string userId = null); Task SetPinProtectedKeyAsync(EncString value, string userId = null);
Task<KdfType?> GetKdfTypeAsync(string userId = null); Task SetKdfConfigurationAsync(KdfConfig config, string userId = null);
Task SetKdfTypeAsync(KdfType? value, string userId = null);
Task<int?> GetKdfIterationsAsync(string userId = null);
Task SetKdfIterationsAsync(int? value, string userId = null);
Task<string> GetKeyEncryptedAsync(string userId = null); Task<string> GetKeyEncryptedAsync(string userId = null);
Task SetKeyEncryptedAsync(string value, string userId = null); Task SetKeyEncryptedAsync(string value, string userId = null);
Task<SymmetricCryptoKey> GetKeyDecryptedAsync(string userId = null); Task<SymmetricCryptoKey> GetKeyDecryptedAsync(string userId = null);

View file

@ -46,7 +46,10 @@
public const int SaveFileRequestCode = 44; public const int SaveFileRequestCode = 44;
public const int TotpDefaultTimer = 30; public const int TotpDefaultTimer = 30;
public const int PasswordlessNotificationTimeoutInMinutes = 15; public const int PasswordlessNotificationTimeoutInMinutes = 15;
public const int KdfIterations = 600000; public const int Pbkdf2Iterations = 600000;
public const int Argon2Iterations = 3;
public const int Argon2MemoryInMB = 64;
public const int Argon2Parallelism = 4;
public const int MasterPasswordMinimumChars = 8; public const int MasterPasswordMinimumChars = 8;
public static readonly string[] AndroidAllClearCipherCacheKeys = public static readonly string[] AndroidAllClearCipherCacheKeys =

View file

@ -46,6 +46,8 @@ namespace Bit.Core.Models.Domain
OrgIdentifier = copy.OrgIdentifier; OrgIdentifier = copy.OrgIdentifier;
KdfType = copy.KdfType; KdfType = copy.KdfType;
KdfIterations = copy.KdfIterations; KdfIterations = copy.KdfIterations;
KdfMemory = copy.KdfMemory;
KdfParallelism = copy.KdfParallelism;
EmailVerified = copy.EmailVerified; EmailVerified = copy.EmailVerified;
HasPremiumPersonally = copy.HasPremiumPersonally; HasPremiumPersonally = copy.HasPremiumPersonally;
AvatarColor = copy.AvatarColor; AvatarColor = copy.AvatarColor;
@ -59,6 +61,8 @@ namespace Bit.Core.Models.Domain
public string AvatarColor; public string AvatarColor;
public KdfType? KdfType; public KdfType? KdfType;
public int? KdfIterations; public int? KdfIterations;
public int? KdfMemory;
public int? KdfParallelism;
public bool? EmailVerified; public bool? EmailVerified;
public bool? HasPremiumPersonally; public bool? HasPremiumPersonally;
} }

View file

@ -0,0 +1,27 @@
using Bit.Core;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
public struct KdfConfig
{
public static KdfConfig Default = new KdfConfig(KdfType.PBKDF2_SHA256, 5000, null, null);
public KdfConfig(KdfType? type, int? iterations, int? memory, int? parallelism)
{
Type = type;
Iterations = iterations;
Memory = memory;
Parallelism = parallelism;
}
public KdfConfig(Account.AccountProfile profile)
{
Type = profile.KdfType;
Iterations = profile.KdfIterations;
Memory = profile.KdfMemory;
Parallelism = profile.KdfParallelism;
}
public KdfType? Type { get; set; }
public int? Iterations { get; set; }
public int? Memory { get; set; }
public int? Parallelism { get; set; }
}

View file

@ -15,6 +15,8 @@ namespace Bit.Core.Models.Request
public Guid? OrganizationUserId { get; set; } public Guid? OrganizationUserId { get; set; }
public KdfType? Kdf { get; set; } public KdfType? Kdf { get; set; }
public int? KdfIterations { get; set; } public int? KdfIterations { get; set; }
public int? KdfMemory { get; set; }
public int? KdfParallelism { get; set; }
public string CaptchaResponse { get; set; } public string CaptchaResponse { get; set; }
} }
} }

View file

@ -9,15 +9,18 @@ namespace Bit.Core.Models.Request
public KeysRequest Keys { get; set; } public KeysRequest Keys { get; set; }
public KdfType Kdf { get; set; } public KdfType Kdf { get; set; }
public int? KdfIterations { get; set; } public int? KdfIterations { get; set; }
public int? KdfMemory { get; set; }
public int? KdfParallelism { get; set; }
public string OrgIdentifier { get; set; } public string OrgIdentifier { get; set; }
public SetKeyConnectorKeyRequest(string key, KeysRequest keys, public SetKeyConnectorKeyRequest(string key, KeysRequest keys, KdfConfig kdfConfig, string orgIdentifier)
KdfType kdf, int? kdfIterations, string orgIdentifier)
{ {
this.Key = key; this.Key = key;
this.Keys = keys; this.Keys = keys;
this.Kdf = kdf; this.Kdf = kdfConfig.Type.GetValueOrDefault(KdfType.PBKDF2_SHA256);
this.KdfIterations = kdfIterations; this.KdfIterations = kdfConfig.Iterations;
this.KdfMemory = kdfConfig.Memory;
this.KdfParallelism = kdfConfig.Parallelism;
this.OrgIdentifier = orgIdentifier; this.OrgIdentifier = orgIdentifier;
} }
} }

View file

@ -10,6 +10,8 @@ namespace Bit.Core.Models.Request
public KeysRequest Keys { get; set; } public KeysRequest Keys { get; set; }
public KdfType Kdf { get; set; } public KdfType Kdf { get; set; }
public int KdfIterations { get; set; } public int KdfIterations { get; set; }
public int? KdfMemory { get; set; }
public int? KdfParallelism { get; set; }
public string OrgIdentifier { get; set; } public string OrgIdentifier { get; set; }
} }
} }

View file

@ -20,7 +20,11 @@ namespace Bit.Core.Models.Response
public string TwoFactorToken { get; set; } public string TwoFactorToken { get; set; }
public KdfType Kdf { get; set; } public KdfType Kdf { get; set; }
public int? KdfIterations { get; set; } public int? KdfIterations { get; set; }
public int? KdfMemory { get; set; }
public int? KdfParallelism { get; set; }
public bool ForcePasswordReset { get; set; } public bool ForcePasswordReset { get; set; }
public string KeyConnectorUrl { get; set; } public string KeyConnectorUrl { get; set; }
[JsonIgnore]
public KdfConfig KdfConfig => new KdfConfig(Kdf, KdfIterations, KdfMemory, KdfParallelism);
} }
} }

View file

@ -1,4 +1,5 @@
using Bit.Core.Enums; using Bit.Core.Enums;
using Newtonsoft.Json;
namespace Bit.Core.Models.Response namespace Bit.Core.Models.Response
{ {
@ -6,5 +7,9 @@ namespace Bit.Core.Models.Response
{ {
public KdfType Kdf { get; set; } public KdfType Kdf { get; set; }
public int KdfIterations { get; set; } public int KdfIterations { get; set; }
public int? KdfMemory { get; set; }
public int? KdfParallelism { get; set; }
[JsonIgnore]
public KdfConfig KdfConfig => new KdfConfig(Kdf, KdfIterations, KdfMemory, KdfParallelism);
} }
} }

View file

@ -276,15 +276,13 @@ namespace Bit.Core.Services
private async Task<SymmetricCryptoKey> MakePreloginKeyAsync(string masterPassword, string email) private async Task<SymmetricCryptoKey> MakePreloginKeyAsync(string masterPassword, string email)
{ {
email = email.Trim().ToLower(); email = email.Trim().ToLower();
KdfType? kdf = null; KdfConfig kdfConfig = KdfConfig.Default;
int? kdfIterations = null;
try try
{ {
var preloginResponse = await _apiService.PostPreloginAsync(new PreloginRequest { Email = email }); var preloginResponse = await _apiService.PostPreloginAsync(new PreloginRequest { Email = email });
if (preloginResponse != null) if (preloginResponse != null)
{ {
kdf = preloginResponse.Kdf; kdfConfig = preloginResponse.KdfConfig;
kdfIterations = preloginResponse.KdfIterations;
} }
} }
catch (ApiException e) catch (ApiException e)
@ -294,7 +292,7 @@ namespace Bit.Core.Services
throw; throw;
} }
} }
return await _cryptoService.MakeKeyAsync(masterPassword, email, kdf, kdfIterations); return await _cryptoService.MakeKeyAsync(masterPassword, email, kdfConfig);
} }
private async Task<AuthResult> LogInHelperAsync(string email, string hashedPassword, string localHashedPassword, private async Task<AuthResult> LogInHelperAsync(string email, string hashedPassword, string localHashedPassword,
@ -442,7 +440,7 @@ namespace Bit.Core.Services
{ {
// SSO Key Connector Onboarding // SSO Key Connector Onboarding
var password = await _cryptoFunctionService.RandomBytesAsync(64); var password = await _cryptoFunctionService.RandomBytesAsync(64);
var k = await _cryptoService.MakeKeyAsync(Convert.ToBase64String(password), _tokenService.GetEmail(), tokenResponse.Kdf, tokenResponse.KdfIterations); var k = await _cryptoService.MakeKeyAsync(Convert.ToBase64String(password), _tokenService.GetEmail(), tokenResponse.KdfConfig);
var keyConnectorRequest = new KeyConnectorUserKeyRequest(k.EncKeyB64); var keyConnectorRequest = new KeyConnectorUserKeyRequest(k.EncKeyB64);
await _cryptoService.SetKeyAsync(k); await _cryptoService.SetKeyAsync(k);
@ -465,7 +463,7 @@ namespace Bit.Core.Services
EncryptedPrivateKey = keyPair.Item2.EncryptedString EncryptedPrivateKey = keyPair.Item2.EncryptedString
}; };
var setPasswordRequest = new SetKeyConnectorKeyRequest( var setPasswordRequest = new SetKeyConnectorKeyRequest(
encKey.Item2.EncryptedString, keys, tokenResponse.Kdf, tokenResponse.KdfIterations, orgId encKey.Item2.EncryptedString, keys, tokenResponse.KdfConfig, orgId
); );
await _apiService.PostSetKeyConnectorKey(setPasswordRequest); await _apiService.PostSetKeyConnectorKey(setPasswordRequest);
} }

View file

@ -389,30 +389,46 @@ namespace Bit.Core.Services
await SetKeyAsync(key); await SetKeyAsync(key);
} }
public async Task<SymmetricCryptoKey> MakeKeyAsync(string password, string salt, public async Task<SymmetricCryptoKey> MakeKeyAsync(string password, string salt, KdfConfig kdfConfig)
KdfType? kdf, int? kdfIterations)
{ {
byte[] key = null; byte[] key = null;
if (kdf == null || kdf == KdfType.PBKDF2_SHA256) if (kdfConfig.Type == null || kdfConfig.Type == KdfType.PBKDF2_SHA256)
{ {
if (kdfIterations == null) var iterations = kdfConfig.Iterations.GetValueOrDefault(5000);
{ if (iterations < 5000)
kdfIterations = 5000;
}
if (kdfIterations < 5000)
{ {
throw new Exception("PBKDF2 iteration minimum is 5000."); throw new Exception("PBKDF2 iteration minimum is 5000.");
} }
key = await _cryptoFunctionService.Pbkdf2Async(password, salt, key = await _cryptoFunctionService.Pbkdf2Async(password, salt,
CryptoHashAlgorithm.Sha256, kdfIterations.Value); CryptoHashAlgorithm.Sha256, iterations);
} }
else if (kdf == KdfType.Argon2id) else if (kdfConfig.Type == KdfType.Argon2id)
{ {
var iterations = kdfIterations.Value; var iterations = kdfConfig.Iterations.GetValueOrDefault(Constants.Argon2Iterations);
const int parallelism = 1; var memory = kdfConfig.Memory.GetValueOrDefault(Constants.Argon2MemoryInMB) * 1024;
const int memory = 1024 * 16; // 16 MiB var parallelism = kdfConfig.Parallelism.GetValueOrDefault(Constants.Argon2Parallelism);
key = await _cryptoFunctionService.Argon2Async(password, salt, iterations, memory, parallelism); if (kdfConfig.Iterations < 2)
{
throw new Exception("Argon2 iterations minimum is 2");
}
if (kdfConfig.Memory < 16)
{
throw new Exception("Argon2 memory minimum is 16 MB");
}
else if (kdfConfig.Memory > 1024)
{
throw new Exception("Argon2 memory maximum is 1024 MB");
}
if (kdfConfig.Parallelism < 1)
{
throw new Exception("Argon2 parallelism minimum is 1");
}
var saltHash = await _cryptoFunctionService.HashAsync(salt, CryptoHashAlgorithm.Sha256);
key = await _cryptoFunctionService.Argon2Async(password, saltHash, iterations, memory, parallelism);
} }
else else
{ {
@ -422,7 +438,7 @@ namespace Bit.Core.Services
} }
public async Task<SymmetricCryptoKey> MakeKeyFromPinAsync(string pin, string salt, public async Task<SymmetricCryptoKey> MakeKeyFromPinAsync(string pin, string salt,
KdfType kdf, int kdfIterations, EncString protectedKeyCs = null) KdfConfig config, EncString protectedKeyCs = null)
{ {
if (protectedKeyCs == null) if (protectedKeyCs == null)
{ {
@ -433,7 +449,7 @@ namespace Bit.Core.Services
} }
protectedKeyCs = new EncString(pinProtectedKey); protectedKeyCs = new EncString(pinProtectedKey);
} }
var pinKey = await MakePinKeyAysnc(pin, salt, kdf, kdfIterations); var pinKey = await MakePinKeyAysnc(pin, salt, config);
var decKey = await DecryptToBytesAsync(protectedKeyCs, pinKey); var decKey = await DecryptToBytesAsync(protectedKeyCs, pinKey);
return new SymmetricCryptoKey(decKey); return new SymmetricCryptoKey(decKey);
} }
@ -454,9 +470,9 @@ namespace Bit.Core.Services
return new Tuple<string, EncString>(publicB64, privateEnc); return new Tuple<string, EncString>(publicB64, privateEnc);
} }
public async Task<SymmetricCryptoKey> MakePinKeyAysnc(string pin, string salt, KdfType kdf, int kdfIterations) public async Task<SymmetricCryptoKey> MakePinKeyAysnc(string pin, string salt, KdfConfig config)
{ {
var pinKey = await MakeKeyAsync(pin, salt, kdf, kdfIterations); var pinKey = await MakeKeyAsync(pin, salt, config);
return await StretchKeyAsync(pinKey); return await StretchKeyAsync(pinKey);
} }

View file

@ -339,35 +339,15 @@ namespace Bit.Core.Services
await SaveAccountAsync(account, reconciledOptions); await SaveAccountAsync(account, reconciledOptions);
} }
public async Task<KdfType?> GetKdfTypeAsync(string userId = null) public async Task SetKdfConfigurationAsync(KdfConfig config, string userId = null)
{
return (await GetAccountAsync(
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync())
))?.Profile?.KdfType;
}
public async Task SetKdfTypeAsync(KdfType? value, string userId = null)
{ {
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultStorageOptionsAsync()); await GetDefaultStorageOptionsAsync());
var account = await GetAccountAsync(reconciledOptions); var account = await GetAccountAsync(reconciledOptions);
account.Profile.KdfType = value; account.Profile.KdfType = config.Type;
await SaveAccountAsync(account, reconciledOptions); account.Profile.KdfIterations = config.Iterations;
} account.Profile.KdfMemory = config.Memory;
account.Profile.KdfParallelism = config.Parallelism;
public async Task<int?> GetKdfIterationsAsync(string userId = null)
{
return (await GetAccountAsync(
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync())
))?.Profile?.KdfIterations;
}
public async Task SetKdfIterationsAsync(int? value, string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultStorageOptionsAsync());
var account = await GetAccountAsync(reconciledOptions);
account.Profile.KdfIterations = value;
await SaveAccountAsync(account, reconciledOptions); await SaveAccountAsync(account, reconciledOptions);
} }

View file

@ -221,8 +221,7 @@ namespace Bit.iOS.Core.Controllers
} }
var email = await _stateService.GetEmailAsync(); var email = await _stateService.GetEmailAsync();
var kdf = await _stateService.GetKdfTypeAsync(); var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
var kdfIterations = await _stateService.GetKdfIterationsAsync();
var inputtedValue = MasterPasswordCell.TextField.Text; var inputtedValue = MasterPasswordCell.TextField.Text;
if (_pinLock) if (_pinLock)
@ -233,7 +232,7 @@ namespace Bit.iOS.Core.Controllers
if (_isPinProtected) if (_isPinProtected)
{ {
var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000), kdfConfig,
await _stateService.GetPinProtectedKeyAsync()); await _stateService.GetPinProtectedKeyAsync());
var encKey = await _cryptoService.GetEncKeyAsync(key); var encKey = await _cryptoService.GetEncKeyAsync(key);
var protectedPin = await _stateService.GetProtectedPinAsync(); var protectedPin = await _stateService.GetProtectedPinAsync();
@ -248,7 +247,7 @@ namespace Bit.iOS.Core.Controllers
else else
{ {
var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000)); kdfConfig);
failed = false; failed = false;
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key2); await SetKeyAndContinueAsync(key2);
@ -265,7 +264,7 @@ namespace Bit.iOS.Core.Controllers
} }
else else
{ {
var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdf, kdfIterations); var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdfConfig);
var storedKeyHash = await _cryptoService.GetKeyHashAsync(); var storedKeyHash = await _cryptoService.GetKeyHashAsync();
if (storedKeyHash == null) if (storedKeyHash == null)
@ -287,7 +286,7 @@ namespace Bit.iOS.Core.Controllers
var encKey = await _cryptoService.GetEncKeyAsync(key2); var encKey = await _cryptoService.GetEncKeyAsync(key2);
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey); var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email, var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000)); kdfConfig);
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key2.Key, pinKey)); await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key2.Key, pinKey));
} }
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();

View file

@ -210,8 +210,7 @@ namespace Bit.iOS.Core.Controllers
} }
var email = await _stateService.GetEmailAsync(); var email = await _stateService.GetEmailAsync();
var kdf = await _stateService.GetKdfTypeAsync(); var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
var kdfIterations = await _stateService.GetKdfIterationsAsync();
var inputtedValue = MasterPasswordCell.TextField.Text; var inputtedValue = MasterPasswordCell.TextField.Text;
if (_pinLock) if (_pinLock)
@ -222,7 +221,7 @@ namespace Bit.iOS.Core.Controllers
if (_isPinProtected) if (_isPinProtected)
{ {
var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000), kdfConfig,
await _stateService.GetPinProtectedKeyAsync()); await _stateService.GetPinProtectedKeyAsync());
var encKey = await _cryptoService.GetEncKeyAsync(key); var encKey = await _cryptoService.GetEncKeyAsync(key);
var protectedPin = await _stateService.GetProtectedPinAsync(); var protectedPin = await _stateService.GetProtectedPinAsync();
@ -237,7 +236,7 @@ namespace Bit.iOS.Core.Controllers
else else
{ {
var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000)); kdfConfig);
failed = false; failed = false;
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key2); await SetKeyAndContinueAsync(key2);
@ -260,7 +259,7 @@ namespace Bit.iOS.Core.Controllers
} }
else else
{ {
var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdf, kdfIterations); var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdfConfig);
var storedKeyHash = await _cryptoService.GetKeyHashAsync(); var storedKeyHash = await _cryptoService.GetKeyHashAsync();
if (storedKeyHash == null) if (storedKeyHash == null)
@ -282,7 +281,7 @@ namespace Bit.iOS.Core.Controllers
var encKey = await _cryptoService.GetEncKeyAsync(key2); var encKey = await _cryptoService.GetEncKeyAsync(key2);
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey); var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email, var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000)); kdfConfig);
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key2.Key, pinKey)); await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key2.Key, pinKey));
} }
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();