centralized crypto utils. keystore with rsa.

This commit is contained in:
Kyle Spearrin 2017-06-05 21:04:19 -04:00
parent 7a56141894
commit 93176989fd
4 changed files with 211 additions and 158 deletions

View File

@ -10,7 +10,6 @@ using Java.Math;
using Android.Security.Keystore; using Android.Security.Keystore;
using Android.App; using Android.App;
using Plugin.Settings.Abstractions; using Plugin.Settings.Abstractions;
using Javax.Crypto.Spec;
using System.Collections.Generic; using System.Collections.Generic;
using Java.Util; using Java.Util;
@ -23,13 +22,12 @@ namespace Bit.Android.Services
private const string KeyAlias = "bitwardenKey"; private const string KeyAlias = "bitwardenKey";
private const string SettingsFormat = "ksSecured:{0}"; private const string SettingsFormat = "ksSecured:{0}";
private const string RsaMode = "RSA/ECB/PKCS1Padding"; private const string RsaMode = "RSA/ECB/PKCS1Padding";
private const string AesMode = "AES/GCM/NoPadding";
private const string AesKey = "ksSecured:aesKeyForService"; private const string AesKey = "ksSecured:aesKeyForService";
private readonly ISettings _settings; private readonly ISettings _settings;
private readonly KeyStore _keyStore; private readonly KeyStore _keyStore;
private readonly bool _oldAndroid = Build.VERSION.SdkInt < BuildVersionCodes.M; private readonly bool _oldAndroid = Build.VERSION.SdkInt < BuildVersionCodes.M;
private readonly KeyStoreStorageService _oldKeyStorageService; private readonly ISecureStorageService _oldKeyStorageService;
public KeyStoreBackedStorageService(ISettings settings) public KeyStoreBackedStorageService(ISettings settings)
{ {
@ -39,7 +37,7 @@ namespace Bit.Android.Services
_keyStore = KeyStore.GetInstance(AndroidKeyStore); _keyStore = KeyStore.GetInstance(AndroidKeyStore);
_keyStore.Load(null); _keyStore.Load(null);
GenerateKeys(); GenerateRsaKey();
GenerateAesKey(); GenerateAesKey();
} }
@ -62,8 +60,28 @@ namespace Bit.Android.Services
return TryGetAndMigrateFromOldKeyStore(key); return TryGetAndMigrateFromOldKeyStore(key);
} }
var cipherString = _settings.GetValueOrDefault<string>(formattedKey); var cs = _settings.GetValueOrDefault<string>(formattedKey);
return AesDecrypt(cipherString); if(string.IsNullOrWhiteSpace(cs))
{
return null;
}
var aesKey = GetAesKey();
if(aesKey == null)
{
return null;
}
try
{
return App.Utilities.Crypto.AesCbcDecrypt(new App.Models.CipherString(cs), aesKey);
}
catch
{
Console.WriteLine("Failed to decrypt from secure storage.");
_settings.Remove(formattedKey);
return null;
}
} }
public void Store(string key, byte[] dataBytes) public void Store(string key, byte[] dataBytes)
@ -76,35 +94,41 @@ namespace Bit.Android.Services
return; return;
} }
var cipherString = AesEncrypt(dataBytes); var aesKey = GetAesKey();
_settings.AddOrUpdateValue(formattedKey, cipherString); if(aesKey == null)
{
return;
}
try
{
var cipherString = App.Utilities.Crypto.AesCbcEncrypt(dataBytes, aesKey);
_settings.AddOrUpdateValue(formattedKey, cipherString.EncryptedString);
}
catch
{
Console.WriteLine("Failed to encrypt to secure storage.");
}
} }
private byte[] RandomBytes(int length) private void GenerateRsaKey()
{
var key = new byte[length];
var secureRandom = new SecureRandom();
secureRandom.NextBytes(key);
return key;
}
private void GenerateKeys()
{ {
if(_keyStore.ContainsAlias(KeyAlias)) if(_keyStore.ContainsAlias(KeyAlias))
{ {
return; return;
} }
var start = Calendar.Instance;
var end = Calendar.Instance;
end.Add(CalendarField.Year, 30);
var subject = new X500Principal($"CN={KeyAlias}");
if(_oldAndroid) if(_oldAndroid)
{ {
var start = Calendar.Instance;
var end = Calendar.Instance;
end.Add(CalendarField.Year, 30);
var gen = KeyPairGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, AndroidKeyStore); var gen = KeyPairGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, AndroidKeyStore);
var spec = new KeyPairGeneratorSpec.Builder(Application.Context) var spec = new KeyPairGeneratorSpec.Builder(Application.Context)
.SetAlias(KeyAlias) .SetAlias(KeyAlias)
.SetSubject(new X500Principal($"CN={KeyAlias}")) .SetSubject(subject)
.SetSerialNumber(BigInteger.Ten) .SetSerialNumber(BigInteger.Ten)
.SetStartDate(start.Time) .SetStartDate(start.Time)
.SetEndDate(end.Time) .SetEndDate(end.Time)
@ -115,10 +139,12 @@ namespace Bit.Android.Services
} }
else else
{ {
var gen = KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmAes, AndroidKeyStore); var gen = KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, AndroidKeyStore);
var spec = new KeyGenParameterSpec.Builder(KeyAlias, KeyStorePurpose.Decrypt | KeyStorePurpose.Encrypt) var spec = new KeyGenParameterSpec.Builder(KeyAlias, KeyStorePurpose.Decrypt | KeyStorePurpose.Encrypt)
.SetBlockModes(KeyProperties.BlockModeGcm) .SetCertificateSubject(subject)
.SetEncryptionPaddings(KeyProperties.EncryptionPaddingNone) .SetCertificateSerialNumber(BigInteger.Ten)
.SetKeyValidityStart(start.Time)
.SetKeyValidityEnd(end.Time)
.Build(); .Build();
gen.Init(spec); gen.Init(spec);
@ -126,63 +152,44 @@ namespace Bit.Android.Services
} }
} }
private void GenerateAesKey()
{
if(!_oldAndroid)
{
return;
}
if(_settings.Contains(AesKey))
{
return;
}
var key = RandomBytes(16);
var encKey = RsaEncrypt(key);
_settings.AddOrUpdateValue(AesKey, Convert.ToBase64String(encKey));
}
private IKey GetAesKey()
{
if(_oldAndroid)
{
var encKey = _settings.GetValueOrDefault<string>(AesKey);
var encKeyBytes = Convert.FromBase64String(encKey);
var key = RsaDecrypt(encKeyBytes);
return new SecretKeySpec(key, "AES");
}
else
{
return _keyStore.GetKey(KeyAlias, null);
}
}
private KeyStore.PrivateKeyEntry GetRsaKeyEntry() private KeyStore.PrivateKeyEntry GetRsaKeyEntry()
{ {
return _keyStore.GetEntry(KeyAlias, null) as KeyStore.PrivateKeyEntry; return _keyStore.GetEntry(KeyAlias, null) as KeyStore.PrivateKeyEntry;
} }
private string AesEncrypt(byte[] input) private void GenerateAesKey()
{ {
var cipher = Cipher.GetInstance(AesMode); if(_settings.Contains(AesKey))
cipher.Init(CipherMode.EncryptMode, GetAesKey()); {
var encBytes = cipher.DoFinal(input); return;
var ivBytes = cipher.GetIV(); }
return $"{Convert.ToBase64String(ivBytes)}|{Convert.ToBase64String(encBytes)}";
var key = App.Utilities.Crypto.RandomBytes(512 / 8);
var encKey = RsaEncrypt(key);
_settings.AddOrUpdateValue(AesKey, Convert.ToBase64String(encKey));
} }
private byte[] AesDecrypt(string cipherString) private App.Models.SymmetricCryptoKey GetAesKey()
{ {
var parts = cipherString.Split('|'); try
var ivBytes = Convert.FromBase64String(parts[0]); {
var encBytes = Convert.FromBase64String(parts[1]); var encKey = _settings.GetValueOrDefault<string>(AesKey);
if(encKey == null)
{
return null;
}
var cipher = Cipher.GetInstance(AesMode); var encKeyBytes = Convert.FromBase64String(encKey);
var spec = new GCMParameterSpec(128, ivBytes); var key = RsaDecrypt(encKeyBytes);
cipher.Init(CipherMode.DecryptMode, GetAesKey(), spec); return new App.Models.SymmetricCryptoKey(key);
var decBytes = cipher.DoFinal(encBytes); }
return decBytes; catch
{
Console.WriteLine("Cannot get AesKey.");
_keyStore.DeleteEntry(KeyAlias);
_settings.Remove(AesKey);
return null;
}
} }
private byte[] RsaEncrypt(byte[] input) private byte[] RsaEncrypt(byte[] input)

View File

@ -285,6 +285,7 @@
<Compile Include="Pages\Vault\VaultEditLoginPage.cs" /> <Compile Include="Pages\Vault\VaultEditLoginPage.cs" />
<Compile Include="Pages\Vault\VaultListLoginsPage.cs" /> <Compile Include="Pages\Vault\VaultListLoginsPage.cs" />
<Compile Include="Services\PasswordGenerationService.cs" /> <Compile Include="Services\PasswordGenerationService.cs" />
<Compile Include="Utilities\Crypto.cs" />
<Compile Include="Utilities\Helpers.cs" /> <Compile Include="Utilities\Helpers.cs" />
<Compile Include="Utilities\IdentityHttpClient.cs" /> <Compile Include="Utilities\IdentityHttpClient.cs" />
<Compile Include="Utilities\Extentions.cs" /> <Compile Include="Utilities\Extentions.cs" />

View File

@ -10,6 +10,7 @@ using System.Collections.Generic;
using Newtonsoft.Json; using Newtonsoft.Json;
using Plugin.Settings.Abstractions; using Plugin.Settings.Abstractions;
using Bit.App.Models.Api; using Bit.App.Models.Api;
using Bit.App.Utilities;
namespace Bit.App.Services namespace Bit.App.Services
{ {
@ -27,7 +28,6 @@ namespace Bit.App.Services
private SymmetricCryptoKey _key; private SymmetricCryptoKey _key;
private SymmetricCryptoKey _encKey; private SymmetricCryptoKey _encKey;
private SymmetricCryptoKey _legacyEtmKey; private SymmetricCryptoKey _legacyEtmKey;
private SymmetricCryptoKey _previousKey;
private IDictionary<string, SymmetricCryptoKey> _orgKeys; private IDictionary<string, SymmetricCryptoKey> _orgKeys;
private byte[] _privateKey; private byte[] _privateKey;
@ -257,14 +257,7 @@ namespace Bit.App.Services
throw new ArgumentNullException(nameof(plainBytes)); throw new ArgumentNullException(nameof(plainBytes));
} }
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7); return Crypto.AesCbcEncrypt(plainBytes, key);
var cryptoKey = provider.CreateSymmetricKey(key.EncKey);
var iv = WinRTCrypto.CryptographicBuffer.GenerateRandom(provider.BlockLength);
var encryptedBytes = WinRTCrypto.CryptographicEngine.Encrypt(cryptoKey, plainBytes, iv);
var mac = key.MacKey != null ? ComputeMacBase64(encryptedBytes, iv, key.MacKey) : null;
return new CipherString(key.EncryptionType, Convert.ToBase64String(iv),
Convert.ToBase64String(encryptedBytes), mac);
} }
public string Decrypt(CipherString encyptedValue, SymmetricCryptoKey key = null) public string Decrypt(CipherString encyptedValue, SymmetricCryptoKey key = null)
@ -298,8 +291,8 @@ namespace Bit.App.Services
throw new ArgumentNullException(nameof(encyptedValue)); throw new ArgumentNullException(nameof(encyptedValue));
} }
if(encyptedValue.EncryptionType == Enums.EncryptionType.AesCbc128_HmacSha256_B64 && if(encyptedValue.EncryptionType == EncryptionType.AesCbc128_HmacSha256_B64 &&
key.EncryptionType == Enums.EncryptionType.AesCbc256_B64) key.EncryptionType == EncryptionType.AesCbc256_B64)
{ {
// Old encrypt-then-mac scheme, swap out the key // Old encrypt-then-mac scheme, swap out the key
if(_legacyEtmKey == null) if(_legacyEtmKey == null)
@ -315,21 +308,7 @@ namespace Bit.App.Services
throw new ArgumentException("encType unavailable."); throw new ArgumentException("encType unavailable.");
} }
if(key.MacKey != null && !string.IsNullOrWhiteSpace(encyptedValue.Mac)) return Crypto.AesCbcDecrypt(encyptedValue, key);
{
var computedMacBytes = ComputeMac(encyptedValue.CipherTextBytes,
encyptedValue.InitializationVectorBytes, key.MacKey);
if(!MacsEqual(key.MacKey, computedMacBytes, encyptedValue.MacBytes))
{
throw new InvalidOperationException("MAC failed.");
}
}
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
var cryptoKey = provider.CreateSymmetricKey(key.EncKey);
var decryptedBytes = WinRTCrypto.CryptographicEngine.Decrypt(cryptoKey, encyptedValue.CipherTextBytes,
encyptedValue.InitializationVectorBytes);
return decryptedBytes;
} }
public byte[] RsaDecryptToBytes(CipherString encyptedValue, byte[] privateKey) public byte[] RsaDecryptToBytes(CipherString encyptedValue, byte[] privateKey)
@ -362,65 +341,6 @@ namespace Bit.App.Services
return decryptedBytes; return decryptedBytes;
} }
private string ComputeMacBase64(byte[] ctBytes, byte[] ivBytes, byte[] macKey)
{
var mac = ComputeMac(ctBytes, ivBytes, macKey);
return Convert.ToBase64String(mac);
}
private byte[] ComputeMac(byte[] ctBytes, byte[] ivBytes, byte[] macKey)
{
if(macKey == null)
{
throw new ArgumentNullException(nameof(macKey));
}
if(ctBytes == null)
{
throw new ArgumentNullException(nameof(ctBytes));
}
if(ivBytes == null)
{
throw new ArgumentNullException(nameof(ivBytes));
}
var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256);
var hasher = algorithm.CreateHash(macKey);
hasher.Append(ivBytes.Concat(ctBytes).ToArray());
var mac = hasher.GetValueAndReset();
return mac;
}
// Safely compare two MACs in a way that protects against timing attacks (Double HMAC Verification).
// ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
private bool MacsEqual(byte[] macKey, byte[] mac1, byte[] mac2)
{
var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256);
var hasher = algorithm.CreateHash(macKey);
hasher.Append(mac1);
mac1 = hasher.GetValueAndReset();
hasher.Append(mac2);
mac2 = hasher.GetValueAndReset();
if(mac1.Length != mac2.Length)
{
return false;
}
for(int i = 0; i < mac2.Length; i++)
{
if(mac1[i] != mac2[i])
{
return false;
}
}
return true;
}
public SymmetricCryptoKey MakeKeyFromPassword(string password, string salt) public SymmetricCryptoKey MakeKeyFromPassword(string password, string salt)
{ {
if(password == null) if(password == null)
@ -471,7 +391,7 @@ namespace Bit.App.Services
public CipherString MakeEncKey(SymmetricCryptoKey key) public CipherString MakeEncKey(SymmetricCryptoKey key)
{ {
var bytes = WinRTCrypto.CryptographicBuffer.GenerateRandom(512 / 8); var bytes = Crypto.RandomBytes(512 / 8);
return Encrypt(bytes, key); return Encrypt(bytes, key);
} }
} }

125
src/App/Utilities/Crypto.cs Normal file
View File

@ -0,0 +1,125 @@
using Bit.App.Models;
using PCLCrypto;
using System;
using System.Linq;
namespace Bit.App.Utilities
{
public static class Crypto
{
public static CipherString AesCbcEncrypt(byte[] plainBytes, SymmetricCryptoKey key)
{
if(key == null)
{
throw new ArgumentNullException(nameof(key));
}
if(plainBytes == null)
{
throw new ArgumentNullException(nameof(plainBytes));
}
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
var cryptoKey = provider.CreateSymmetricKey(key.EncKey);
var iv = RandomBytes(provider.BlockLength);
var encryptedBytes = WinRTCrypto.CryptographicEngine.Encrypt(cryptoKey, plainBytes, iv);
var mac = key.MacKey != null ? ComputeMacBase64(encryptedBytes, iv, key.MacKey) : null;
return new CipherString(key.EncryptionType, Convert.ToBase64String(iv),
Convert.ToBase64String(encryptedBytes), mac);
}
public static byte[] AesCbcDecrypt(CipherString encyptedValue, SymmetricCryptoKey key)
{
if(key == null)
{
throw new ArgumentNullException(nameof(key));
}
if(encyptedValue == null)
{
throw new ArgumentNullException(nameof(encyptedValue));
}
if(key.MacKey != null && !string.IsNullOrWhiteSpace(encyptedValue.Mac))
{
var computedMacBytes = ComputeMac(encyptedValue.CipherTextBytes,
encyptedValue.InitializationVectorBytes, key.MacKey);
if(!MacsEqual(key.MacKey, computedMacBytes, encyptedValue.MacBytes))
{
throw new InvalidOperationException("MAC failed.");
}
}
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
var cryptoKey = provider.CreateSymmetricKey(key.EncKey);
var decryptedBytes = WinRTCrypto.CryptographicEngine.Decrypt(cryptoKey, encyptedValue.CipherTextBytes,
encyptedValue.InitializationVectorBytes);
return decryptedBytes;
}
public static byte[] RandomBytes(int length)
{
return WinRTCrypto.CryptographicBuffer.GenerateRandom(length);
}
private static string ComputeMacBase64(byte[] ctBytes, byte[] ivBytes, byte[] macKey)
{
var mac = ComputeMac(ctBytes, ivBytes, macKey);
return Convert.ToBase64String(mac);
}
private static byte[] ComputeMac(byte[] ctBytes, byte[] ivBytes, byte[] macKey)
{
if(macKey == null)
{
throw new ArgumentNullException(nameof(macKey));
}
if(ctBytes == null)
{
throw new ArgumentNullException(nameof(ctBytes));
}
if(ivBytes == null)
{
throw new ArgumentNullException(nameof(ivBytes));
}
var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256);
var hasher = algorithm.CreateHash(macKey);
hasher.Append(ivBytes.Concat(ctBytes).ToArray());
var mac = hasher.GetValueAndReset();
return mac;
}
// Safely compare two MACs in a way that protects against timing attacks (Double HMAC Verification).
// ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
private static bool MacsEqual(byte[] macKey, byte[] mac1, byte[] mac2)
{
var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256);
var hasher = algorithm.CreateHash(macKey);
hasher.Append(mac1);
mac1 = hasher.GetValueAndReset();
hasher.Append(mac2);
mac2 = hasher.GetValueAndReset();
if(mac1.Length != mac2.Length)
{
return false;
}
for(int i = 0; i < mac2.Length; i++)
{
if(mac1[i] != mac2[i])
{
return false;
}
}
return true;
}
}
}