1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-17 16:53:26 +00:00

[PM-2713] more updates to crypto service

This commit is contained in:
Jacob Fink
2023-07-18 09:37:56 -04:00
parent 69d38d4d75
commit bf28d373e9
2 changed files with 418 additions and 328 deletions

View File

@@ -18,6 +18,9 @@ namespace Bit.Core.Abstractions
Task<MasterKey> GetMasterKeyAsync(string userId = null);
Task<MasterKey> MakeMasterKeyAsync(string password, string email, KdfConfig kdfConfig);
Task ClearMasterKeyAsync(string userId = null);
Task<Tuple<UserKey, EncString>> EncryptUserKeyWithMasterKeyAsync(MasterKey masterKey, UserKey userKey = null);
Task<UserKey> DecryptUserKeyWithMasterKeyAsync(MasterKey masterKey, EncString encUserKey = null, string userId = null);
Task<string> HashPasswordAsync(string password, SymmetricCryptoKey key, HashPurpose hashPurpose = HashPurpose.ServerAuthorization);
Task SetPasswordHashAsync(string keyHash);
Task<string> GetPasswordHashAsync();
Task ClearPasswordHashAsync(string userId = null);
@@ -41,21 +44,28 @@ namespace Bit.Core.Abstractions
Task<byte[]> RsaDecryptAsync(string encValue, byte[] privateKey = null);
Task<int> RandomNumberAsync(int min, int max);
Task<string> RandomStringAsync(int length);
Task ClearEncKeyAsync(bool memoryOnly = false, string userId = null);
Task ClearKeyAsync(string userId = null);
Task ClearPinProtectedKeyAsync(string userId = null);
void ClearCache();
Task<byte[]> DecryptFromBytesAsync(byte[] encBytes, SymmetricCryptoKey key);
Task<byte[]> DecryptToBytesAsync(EncString encString, SymmetricCryptoKey key = null);
Task<string> DecryptToUtf8Async(EncString encString, SymmetricCryptoKey key = null);
Task<EncString> EncryptAsync(byte[] plainValue, SymmetricCryptoKey key = null);
Task<EncString> EncryptAsync(string plainValue, SymmetricCryptoKey key = null);
Task<EncByteArray> EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null);
Task ClearEncKeyAsync(bool memoryOnly = false, string userId = null);
Task ClearKeyAsync(string userId = null);
Task ClearPinProtectedKeyAsync(string userId = null);
void ClearCache();
Task<SymmetricCryptoKey> GetEncKeyAsync(SymmetricCryptoKey key = null);
Task<SymmetricCryptoKey> GetKeyAsync(string userId = null);
Task<bool> HasEncKeyAsync();
Task<string> HashPasswordAsync(string password, SymmetricCryptoKey key, HashPurpose hashPurpose = HashPurpose.ServerAuthorization);
Task<bool> HasKeyAsync(string userId = null);
Task<Tuple<SymmetricCryptoKey, EncString>> MakeEncKeyAsync(SymmetricCryptoKey key);
Task<SymmetricCryptoKey> MakeKeyAsync(string password, string salt, KdfConfig config);

View File

@@ -89,6 +89,52 @@ namespace Bit.Core.Services
await _stateService.SetMasterKeyAsync(null, userId);
}
public async Task<Tuple<UserKey, EncString>> EncryptUserKeyWithMasterKeyAsync(MasterKey masterKey, UserKey userKey = null)
{
userKey ??= await GetUserKeyAsync();
return await BuildProtectedSymmetricKey<UserKey>(masterKey, userKey.Key);
}
public async Task<UserKey> DecryptUserKeyWithMasterKeyAsync(MasterKey masterKey, EncString encUserKey = null, string userId = null)
{
masterKey ??= await GetMasterKeyAsync(userId);
if (masterKey == null)
{
throw new Exception("No master key found.");
}
if (encUserKey == null)
{
var userKeyMasterKey = await _stateService.GetUserKeyMasterKeyAsync(userId);
if (userKeyMasterKey == null)
{
throw new Exception("No encrypted user key found");
}
encUserKey = new EncString(userKeyMasterKey);
}
byte[] decUserKey = null;
if (encUserKey.EncryptionType == EncryptionType.AesCbc256_B64)
{
decUserKey = await DecryptToBytesAsync(encUserKey, masterKey);
}
else if (encUserKey.EncryptionType == EncryptionType.AesCbc256_HmacSha256_B64)
{
var newKey = await StretchKeyAsync(masterKey);
decUserKey = await DecryptToBytesAsync(encUserKey, newKey);
}
else
{
throw new Exception("Unsupported encKey type.");
}
if (decUserKey == null)
{
return null;
}
return new UserKey(decUserKey);
}
public async Task<Tuple<SymmetricCryptoKey, EncString>> MakeDataEncKey(UserKey key)
{
if (key == null)
@@ -111,6 +157,22 @@ namespace Bit.Core.Services
return await BuildProtectedSymmetricKey<SymmetricCryptoKey>(key, newSymKey);
}
// TODO(Jake): Uses Master Key
public async Task<string> HashPasswordAsync(string password, SymmetricCryptoKey key, HashPurpose hashPurpose = HashPurpose.ServerAuthorization)
{
if (key == null)
{
key = await GetKeyAsync();
}
if (password == null || key == null)
{
throw new Exception("Invalid parameters.");
}
var iterations = hashPurpose == HashPurpose.LocalAuthorization ? 2 : 1;
var hash = await _cryptoFunctionService.Pbkdf2Async(key.Key, password, CryptoHashAlgorithm.Sha256, iterations);
return Convert.ToBase64String(hash);
}
public async Task SetPasswordHashAsync(string keyHash)
{
_passwordHash = keyHash;
@@ -137,6 +199,7 @@ namespace Bit.Core.Services
await _stateService.SetKeyHashAsync(null, userId);
}
// TODO(Jake): Uses Master Key
public async Task<bool> CompareAndUpdatePasswordHashAsync(string masterPassword, SymmetricCryptoKey key)
{
var storedPasswordHash = await GetPasswordHashAsync();
@@ -443,6 +506,345 @@ namespace Bit.Core.Services
return sb.ToString();
}
// TODO: The following operations should be moved to a new encrypt service
public async Task<byte[]> DecryptFromBytesAsync(byte[] encBytes, SymmetricCryptoKey key)
{
if (encBytes == null)
{
throw new Exception("no encBytes.");
}
var encType = (EncryptionType)encBytes[0];
byte[] ctBytes = null;
byte[] ivBytes = null;
byte[] macBytes = null;
switch (encType)
{
case EncryptionType.AesCbc128_HmacSha256_B64:
case EncryptionType.AesCbc256_HmacSha256_B64:
if (encBytes.Length < 49) // 1 + 16 + 32 + ctLength
{
return null;
}
ivBytes = new ArraySegment<byte>(encBytes, 1, 16).ToArray();
macBytes = new ArraySegment<byte>(encBytes, 17, 32).ToArray();
ctBytes = new ArraySegment<byte>(encBytes, 49, encBytes.Length - 49).ToArray();
break;
case EncryptionType.AesCbc256_B64:
if (encBytes.Length < 17) // 1 + 16 + ctLength
{
return null;
}
ivBytes = new ArraySegment<byte>(encBytes, 1, 16).ToArray();
ctBytes = new ArraySegment<byte>(encBytes, 17, encBytes.Length - 17).ToArray();
break;
default:
return null;
}
return await AesDecryptToBytesAsync(encType, ctBytes, ivBytes, macBytes, key);
}
public async Task<byte[]> DecryptToBytesAsync(EncString encString, SymmetricCryptoKey key = null)
{
var iv = Convert.FromBase64String(encString.Iv);
var data = Convert.FromBase64String(encString.Data);
var mac = !string.IsNullOrWhiteSpace(encString.Mac) ? Convert.FromBase64String(encString.Mac) : null;
return await AesDecryptToBytesAsync(encString.EncryptionType, data, iv, mac, key);
}
public async Task<string> DecryptToUtf8Async(EncString encString, SymmetricCryptoKey key = null)
{
return await AesDecryptToUtf8Async(encString.EncryptionType, encString.Data,
encString.Iv, encString.Mac, key);
}
public async Task<EncString> EncryptAsync(string plainValue, SymmetricCryptoKey key = null)
{
if (plainValue == null)
{
return null;
}
return await EncryptAsync(Encoding.UTF8.GetBytes(plainValue), key);
}
public async Task<EncString> EncryptAsync(byte[] plainValue, SymmetricCryptoKey key = null)
{
if (plainValue == null)
{
return null;
}
var encObj = await AesEncryptAsync(plainValue, key);
var iv = Convert.ToBase64String(encObj.Iv);
var data = Convert.ToBase64String(encObj.Data);
var mac = encObj.Mac != null ? Convert.ToBase64String(encObj.Mac) : null;
return new EncString(encObj.Key.EncType, data, iv, mac);
}
public async Task<EncByteArray> EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null)
{
var encValue = await AesEncryptAsync(plainValue, key);
var macLen = 0;
if (encValue.Mac != null)
{
macLen = encValue.Mac.Length;
}
var encBytes = new byte[1 + encValue.Iv.Length + macLen + encValue.Data.Length];
Buffer.BlockCopy(new byte[] { (byte)encValue.Key.EncType }, 0, encBytes, 0, 1);
Buffer.BlockCopy(encValue.Iv, 0, encBytes, 1, encValue.Iv.Length);
if (encValue.Mac != null)
{
Buffer.BlockCopy(encValue.Mac, 0, encBytes, 1 + encValue.Iv.Length, encValue.Mac.Length);
}
Buffer.BlockCopy(encValue.Data, 0, encBytes, 1 + encValue.Iv.Length + macLen, encValue.Data.Length);
return new EncByteArray(encBytes);
}
// Helpers
private async Task<EncryptedObject> AesEncryptAsync(byte[] data, SymmetricCryptoKey key)
{
var obj = new EncryptedObject
{
Key = await GetKeyForEncryptionAsync(key),
Iv = await _cryptoFunctionService.RandomBytesAsync(16)
};
obj.Data = await _cryptoFunctionService.AesEncryptAsync(data, obj.Iv, obj.Key.EncKey);
if (obj.Key.MacKey != null)
{
var macData = new byte[obj.Iv.Length + obj.Data.Length];
Buffer.BlockCopy(obj.Iv, 0, macData, 0, obj.Iv.Length);
Buffer.BlockCopy(obj.Data, 0, macData, obj.Iv.Length, obj.Data.Length);
obj.Mac = await _cryptoFunctionService.HmacAsync(macData, obj.Key.MacKey, CryptoHashAlgorithm.Sha256);
}
return obj;
}
private async Task<string> AesDecryptToUtf8Async(EncryptionType encType, string data, string iv, string mac,
SymmetricCryptoKey key)
{
var keyForEnc = await GetKeyForEncryptionAsync(key);
var theKey = ResolveLegacyKey(encType, keyForEnc);
if (theKey.MacKey != null && mac == null)
{
// Mac required.
return null;
}
if (theKey.EncType != encType)
{
// encType unavailable.
return null;
}
// "Fast params" conversion
var encKey = theKey.EncKey;
var dataBytes = Convert.FromBase64String(data);
var ivBytes = Convert.FromBase64String(iv);
var macDataBytes = new byte[ivBytes.Length + dataBytes.Length];
Buffer.BlockCopy(ivBytes, 0, macDataBytes, 0, ivBytes.Length);
Buffer.BlockCopy(dataBytes, 0, macDataBytes, ivBytes.Length, dataBytes.Length);
byte[] macKey = null;
if (theKey.MacKey != null)
{
macKey = theKey.MacKey;
}
byte[] macBytes = null;
if (mac != null)
{
macBytes = Convert.FromBase64String(mac);
}
// Compute mac
if (macKey != null && macBytes != null)
{
var computedMac = await _cryptoFunctionService.HmacAsync(macDataBytes, macKey,
CryptoHashAlgorithm.Sha256);
var macsEqual = await _cryptoFunctionService.CompareAsync(macBytes, computedMac);
if (!macsEqual)
{
// Mac failed
return null;
}
}
var decBytes = await _cryptoFunctionService.AesDecryptAsync(dataBytes, ivBytes, encKey);
return Encoding.UTF8.GetString(decBytes);
}
private async Task<byte[]> AesDecryptToBytesAsync(EncryptionType encType, byte[] data, byte[] iv, byte[] mac,
SymmetricCryptoKey key)
{
var keyForEnc = await GetKeyForEncryptionAsync(key);
var theKey = ResolveLegacyKey(encType, keyForEnc);
if (theKey.MacKey != null && mac == null)
{
// Mac required.
return null;
}
if (theKey.EncType != encType)
{
// encType unavailable.
return null;
}
// Compute mac
if (theKey.MacKey != null && mac != null)
{
var macData = new byte[iv.Length + data.Length];
Buffer.BlockCopy(iv, 0, macData, 0, iv.Length);
Buffer.BlockCopy(data, 0, macData, iv.Length, data.Length);
var computedMac = await _cryptoFunctionService.HmacAsync(macData, theKey.MacKey,
CryptoHashAlgorithm.Sha256);
if (computedMac == null)
{
return null;
}
var macsMatch = await _cryptoFunctionService.CompareAsync(mac, computedMac);
if (!macsMatch)
{
// Mac failed
return null;
}
}
return await _cryptoFunctionService.AesDecryptAsync(data, iv, theKey.EncKey);
}
private async Task<UserKey> GetUserKeyWithLegacySupport(string userId = null)
{
var userKey = await GetUserKeyAsync();
if (userKey != null)
{
return userKey;
}
// Legacy support: encryption used to be done with the master key (derived from master password).
// Users who have not migrated will have a null user key and must use the master key instead.
return (SymmetricCryptoKey)await GetMasterKeyAsync() as UserKey;
}
private async Task<SymmetricCryptoKey> GetKeyForEncryptionAsync(SymmetricCryptoKey key = null)
{
if (key != null)
{
return key;
}
var encKey = await GetEncKeyAsync();
if (encKey != null)
{
return encKey;
}
return await GetKeyAsync();
}
private SymmetricCryptoKey ResolveLegacyKey(EncryptionType encKey, SymmetricCryptoKey key)
{
if (encKey == EncryptionType.AesCbc128_HmacSha256_B64 && key.EncType == EncryptionType.AesCbc256_B64)
{
// Old encrypt-then-mac scheme, make a new key
if (_legacyEtmKey == null)
{
_legacyEtmKey = new SymmetricCryptoKey(key.Key, EncryptionType.AesCbc128_HmacSha256_B64);
}
return _legacyEtmKey;
}
return key;
}
private async Task<SymmetricCryptoKey> StretchKeyAsync(SymmetricCryptoKey key)
{
var newKey = new byte[64];
var enc = await _cryptoFunctionService.HkdfExpandAsync(key.Key, Encoding.UTF8.GetBytes("enc"), 32, HkdfAlgorithm.Sha256);
Buffer.BlockCopy(enc, 0, newKey, 0, 32);
var mac = await _cryptoFunctionService.HkdfExpandAsync(key.Key, Encoding.UTF8.GetBytes("mac"), 32, HkdfAlgorithm.Sha256);
Buffer.BlockCopy(mac, 0, newKey, 32, 32);
return new SymmetricCryptoKey(newKey);
}
private List<string> HashPhrase(byte[] hash, int minimumEntropy = 64)
{
var wordLength = EEFLongWordList.Instance.List.Count;
var entropyPerWord = Math.Log(wordLength) / Math.Log(2);
var numWords = (int)Math.Ceiling(minimumEntropy / entropyPerWord);
var entropyAvailable = hash.Length * 4;
if (numWords * entropyPerWord > entropyAvailable)
{
throw new Exception("Output entropy of hash function is too small");
}
var phrase = new List<string>();
var hashHex = string.Concat("0", BitConverter.ToString(hash).Replace("-", ""));
var hashNumber = BigInteger.Parse(hashHex, System.Globalization.NumberStyles.HexNumber);
while (numWords-- > 0)
{
var remainder = (int)(hashNumber % wordLength);
hashNumber = hashNumber / wordLength;
phrase.Add(EEFLongWordList.Instance.List[remainder]);
}
return phrase;
}
private async Task<Tuple<T, EncString>> BuildProtectedSymmetricKey<T>(SymmetricCryptoKey key,
byte[] encKey) where T : SymmetricCryptoKey
{
EncString encKeyEnc = null;
if (key.Key.Length == 32)
{
var newKey = await StretchKeyAsync(key);
encKeyEnc = await EncryptAsync(encKey, newKey);
}
else if (key.Key.Length == 64)
{
encKeyEnc = await EncryptAsync(encKey, key);
}
else
{
throw new Exception("Invalid key size.");
}
return new Tuple<T, EncString>(new SymmetricCryptoKey(encKey) as T, encKeyEnc);
}
private class EncryptedObject
{
public byte[] Iv { get; set; }
public byte[] Data { get; set; }
public byte[] Mac { get; set; }
public SymmetricCryptoKey Key { get; set; }
}
@@ -694,20 +1096,6 @@ namespace Bit.Core.Services
public async Task<string> HashPasswordAsync(string password, SymmetricCryptoKey key, HashPurpose hashPurpose = HashPurpose.ServerAuthorization)
{
if (key == null)
{
key = await GetKeyAsync();
}
if (password == null || key == null)
{
throw new Exception("Invalid parameters.");
}
var iterations = hashPurpose == HashPurpose.LocalAuthorization ? 2 : 1;
var hash = await _cryptoFunctionService.Pbkdf2Async(key.Key, password, CryptoHashAlgorithm.Sha256, iterations);
return Convert.ToBase64String(hash);
}
public async Task<Tuple<SymmetricCryptoKey, EncString>> MakeEncKeyAsync(SymmetricCryptoKey key)
{
@@ -722,318 +1110,10 @@ namespace Bit.Core.Services
return await BuildProtectedSymmetricKey<SymmetricCryptoKey>(key, encKey.Key);
}
public async Task<EncString> EncryptAsync(string plainValue, SymmetricCryptoKey key = null)
{
if (plainValue == null)
{
return null;
}
return await EncryptAsync(Encoding.UTF8.GetBytes(plainValue), key);
}
public async Task<EncString> EncryptAsync(byte[] plainValue, SymmetricCryptoKey key = null)
{
if (plainValue == null)
{
return null;
}
var encObj = await AesEncryptAsync(plainValue, key);
var iv = Convert.ToBase64String(encObj.Iv);
var data = Convert.ToBase64String(encObj.Data);
var mac = encObj.Mac != null ? Convert.ToBase64String(encObj.Mac) : null;
return new EncString(encObj.Key.EncType, data, iv, mac);
}
public async Task<EncByteArray> EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null)
{
var encValue = await AesEncryptAsync(plainValue, key);
var macLen = 0;
if (encValue.Mac != null)
{
macLen = encValue.Mac.Length;
}
var encBytes = new byte[1 + encValue.Iv.Length + macLen + encValue.Data.Length];
Buffer.BlockCopy(new byte[] { (byte)encValue.Key.EncType }, 0, encBytes, 0, 1);
Buffer.BlockCopy(encValue.Iv, 0, encBytes, 1, encValue.Iv.Length);
if (encValue.Mac != null)
{
Buffer.BlockCopy(encValue.Mac, 0, encBytes, 1 + encValue.Iv.Length, encValue.Mac.Length);
}
Buffer.BlockCopy(encValue.Data, 0, encBytes, 1 + encValue.Iv.Length + macLen, encValue.Data.Length);
return new EncByteArray(encBytes);
}
public async Task<byte[]> DecryptToBytesAsync(EncString encString, SymmetricCryptoKey key = null)
{
var iv = Convert.FromBase64String(encString.Iv);
var data = Convert.FromBase64String(encString.Data);
var mac = !string.IsNullOrWhiteSpace(encString.Mac) ? Convert.FromBase64String(encString.Mac) : null;
return await AesDecryptToBytesAsync(encString.EncryptionType, data, iv, mac, key);
}
public async Task<string> DecryptToUtf8Async(EncString encString, SymmetricCryptoKey key = null)
{
return await AesDecryptToUtf8Async(encString.EncryptionType, encString.Data,
encString.Iv, encString.Mac, key);
}
public async Task<byte[]> DecryptFromBytesAsync(byte[] encBytes, SymmetricCryptoKey key)
{
if (encBytes == null)
{
throw new Exception("no encBytes.");
}
var encType = (EncryptionType)encBytes[0];
byte[] ctBytes = null;
byte[] ivBytes = null;
byte[] macBytes = null;
switch (encType)
{
case EncryptionType.AesCbc128_HmacSha256_B64:
case EncryptionType.AesCbc256_HmacSha256_B64:
if (encBytes.Length < 49) // 1 + 16 + 32 + ctLength
{
return null;
}
ivBytes = new ArraySegment<byte>(encBytes, 1, 16).ToArray();
macBytes = new ArraySegment<byte>(encBytes, 17, 32).ToArray();
ctBytes = new ArraySegment<byte>(encBytes, 49, encBytes.Length - 49).ToArray();
break;
case EncryptionType.AesCbc256_B64:
if (encBytes.Length < 17) // 1 + 16 + ctLength
{
return null;
}
ivBytes = new ArraySegment<byte>(encBytes, 1, 16).ToArray();
ctBytes = new ArraySegment<byte>(encBytes, 17, encBytes.Length - 17).ToArray();
break;
default:
return null;
}
return await AesDecryptToBytesAsync(encType, ctBytes, ivBytes, macBytes, key);
}
// Helpers
private async Task<EncryptedObject> AesEncryptAsync(byte[] data, SymmetricCryptoKey key)
{
var obj = new EncryptedObject
{
Key = await GetKeyForEncryptionAsync(key),
Iv = await _cryptoFunctionService.RandomBytesAsync(16)
};
obj.Data = await _cryptoFunctionService.AesEncryptAsync(data, obj.Iv, obj.Key.EncKey);
if (obj.Key.MacKey != null)
{
var macData = new byte[obj.Iv.Length + obj.Data.Length];
Buffer.BlockCopy(obj.Iv, 0, macData, 0, obj.Iv.Length);
Buffer.BlockCopy(obj.Data, 0, macData, obj.Iv.Length, obj.Data.Length);
obj.Mac = await _cryptoFunctionService.HmacAsync(macData, obj.Key.MacKey, CryptoHashAlgorithm.Sha256);
}
return obj;
}
private async Task<string> AesDecryptToUtf8Async(EncryptionType encType, string data, string iv, string mac,
SymmetricCryptoKey key)
{
var keyForEnc = await GetKeyForEncryptionAsync(key);
var theKey = ResolveLegacyKey(encType, keyForEnc);
if (theKey.MacKey != null && mac == null)
{
// Mac required.
return null;
}
if (theKey.EncType != encType)
{
// encType unavailable.
return null;
}
// "Fast params" conversion
var encKey = theKey.EncKey;
var dataBytes = Convert.FromBase64String(data);
var ivBytes = Convert.FromBase64String(iv);
var macDataBytes = new byte[ivBytes.Length + dataBytes.Length];
Buffer.BlockCopy(ivBytes, 0, macDataBytes, 0, ivBytes.Length);
Buffer.BlockCopy(dataBytes, 0, macDataBytes, ivBytes.Length, dataBytes.Length);
byte[] macKey = null;
if (theKey.MacKey != null)
{
macKey = theKey.MacKey;
}
byte[] macBytes = null;
if (mac != null)
{
macBytes = Convert.FromBase64String(mac);
}
// Compute mac
if (macKey != null && macBytes != null)
{
var computedMac = await _cryptoFunctionService.HmacAsync(macDataBytes, macKey,
CryptoHashAlgorithm.Sha256);
var macsEqual = await _cryptoFunctionService.CompareAsync(macBytes, computedMac);
if (!macsEqual)
{
// Mac failed
return null;
}
}
var decBytes = await _cryptoFunctionService.AesDecryptAsync(dataBytes, ivBytes, encKey);
return Encoding.UTF8.GetString(decBytes);
}
private async Task<byte[]> AesDecryptToBytesAsync(EncryptionType encType, byte[] data, byte[] iv, byte[] mac,
SymmetricCryptoKey key)
{
var keyForEnc = await GetKeyForEncryptionAsync(key);
var theKey = ResolveLegacyKey(encType, keyForEnc);
if (theKey.MacKey != null && mac == null)
{
// Mac required.
return null;
}
if (theKey.EncType != encType)
{
// encType unavailable.
return null;
}
// Compute mac
if (theKey.MacKey != null && mac != null)
{
var macData = new byte[iv.Length + data.Length];
Buffer.BlockCopy(iv, 0, macData, 0, iv.Length);
Buffer.BlockCopy(data, 0, macData, iv.Length, data.Length);
var computedMac = await _cryptoFunctionService.HmacAsync(macData, theKey.MacKey,
CryptoHashAlgorithm.Sha256);
if (computedMac == null)
{
return null;
}
var macsMatch = await _cryptoFunctionService.CompareAsync(mac, computedMac);
if (!macsMatch)
{
// Mac failed
return null;
}
}
return await _cryptoFunctionService.AesDecryptAsync(data, iv, theKey.EncKey);
}
private async Task<UserKey> GetUserKeyWithLegacySupport(string userId = null)
{
var userKey = await GetUserKeyAsync();
if (userKey != null)
{
return userKey;
}
// Legacy support: encryption used to be done with the master key (derived from master password).
// Users who have not migrated will have a null user key and must use the master key instead.
return (SymmetricCryptoKey)await GetMasterKeyAsync() as UserKey;
}
private async Task<SymmetricCryptoKey> GetKeyForEncryptionAsync(SymmetricCryptoKey key = null)
{
if (key != null)
{
return key;
}
var encKey = await GetEncKeyAsync();
if (encKey != null)
{
return encKey;
}
return await GetKeyAsync();
}
private SymmetricCryptoKey ResolveLegacyKey(EncryptionType encKey, SymmetricCryptoKey key)
{
if (encKey == EncryptionType.AesCbc128_HmacSha256_B64 && key.EncType == EncryptionType.AesCbc256_B64)
{
// Old encrypt-then-mac scheme, make a new key
if (_legacyEtmKey == null)
{
_legacyEtmKey = new SymmetricCryptoKey(key.Key, EncryptionType.AesCbc128_HmacSha256_B64);
}
return _legacyEtmKey;
}
return key;
}
private async Task<SymmetricCryptoKey> StretchKeyAsync(SymmetricCryptoKey key)
{
var newKey = new byte[64];
var enc = await _cryptoFunctionService.HkdfExpandAsync(key.Key, Encoding.UTF8.GetBytes("enc"), 32, HkdfAlgorithm.Sha256);
Buffer.BlockCopy(enc, 0, newKey, 0, 32);
var mac = await _cryptoFunctionService.HkdfExpandAsync(key.Key, Encoding.UTF8.GetBytes("mac"), 32, HkdfAlgorithm.Sha256);
Buffer.BlockCopy(mac, 0, newKey, 32, 32);
return new SymmetricCryptoKey(newKey);
}
private List<string> HashPhrase(byte[] hash, int minimumEntropy = 64)
{
var wordLength = EEFLongWordList.Instance.List.Count;
var entropyPerWord = Math.Log(wordLength) / Math.Log(2);
var numWords = (int)Math.Ceiling(minimumEntropy / entropyPerWord);
var entropyAvailable = hash.Length * 4;
if (numWords * entropyPerWord > entropyAvailable)
{
throw new Exception("Output entropy of hash function is too small");
}
var phrase = new List<string>();
var hashHex = string.Concat("0", BitConverter.ToString(hash).Replace("-", ""));
var hashNumber = BigInteger.Parse(hashHex, System.Globalization.NumberStyles.HexNumber);
while (numWords-- > 0)
{
var remainder = (int)(hashNumber % wordLength);
hashNumber = hashNumber / wordLength;
phrase.Add(EEFLongWordList.Instance.List[remainder]);
}
return phrase;
}
private async Task<Tuple<T, EncString>> BuildProtectedSymmetricKey<T>(SymmetricCryptoKey key,
byte[] encKey) where T : SymmetricCryptoKey
{
EncString encKeyEnc = null;
if (key.Key.Length == 32)
{
var newKey = await StretchKeyAsync(key);
encKeyEnc = await EncryptAsync(encKey, newKey);
}
else if (key.Key.Length == 64)
{
encKeyEnc = await EncryptAsync(encKey, key);
}
else
{
throw new Exception("Invalid key size.");
}
return new Tuple<T, EncString>(new SymmetricCryptoKey(encKey) as T, encKeyEnc);
}
private class EncryptedObject
{
public byte[] Iv { get; set; }
public byte[] Data { get; set; }
public byte[] Mac { get; set; }
public SymmetricCryptoKey Key { get; set; }
}
}
}