mirror of
https://github.com/bitwarden/android.git
synced 2024-12-25 18:38:27 +03:00
centralized crypto utils. keystore with rsa.
This commit is contained in:
parent
7a56141894
commit
93176989fd
4 changed files with 211 additions and 158 deletions
|
@ -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)
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] RandomBytes(int length)
|
|
||||||
{
|
{
|
||||||
var key = new byte[length];
|
return;
|
||||||
var secureRandom = new SecureRandom();
|
|
||||||
secureRandom.NextBytes(key);
|
|
||||||
return key;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GenerateKeys()
|
try
|
||||||
|
{
|
||||||
|
var cipherString = App.Utilities.Crypto.AesCbcEncrypt(dataBytes, aesKey);
|
||||||
|
_settings.AddOrUpdateValue(formattedKey, cipherString.EncryptedString);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Console.WriteLine("Failed to encrypt to secure storage.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GenerateRsaKey()
|
||||||
{
|
{
|
||||||
if(_keyStore.ContainsAlias(KeyAlias))
|
if(_keyStore.ContainsAlias(KeyAlias))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_oldAndroid)
|
|
||||||
{
|
|
||||||
var start = Calendar.Instance;
|
var start = Calendar.Instance;
|
||||||
var end = Calendar.Instance;
|
var end = Calendar.Instance;
|
||||||
end.Add(CalendarField.Year, 30);
|
end.Add(CalendarField.Year, 30);
|
||||||
|
var subject = new X500Principal($"CN={KeyAlias}");
|
||||||
|
|
||||||
|
if(_oldAndroid)
|
||||||
|
{
|
||||||
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)}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] AesDecrypt(string cipherString)
|
var key = App.Utilities.Crypto.RandomBytes(512 / 8);
|
||||||
{
|
var encKey = RsaEncrypt(key);
|
||||||
var parts = cipherString.Split('|');
|
_settings.AddOrUpdateValue(AesKey, Convert.ToBase64String(encKey));
|
||||||
var ivBytes = Convert.FromBase64String(parts[0]);
|
}
|
||||||
var encBytes = Convert.FromBase64String(parts[1]);
|
|
||||||
|
|
||||||
var cipher = Cipher.GetInstance(AesMode);
|
private App.Models.SymmetricCryptoKey GetAesKey()
|
||||||
var spec = new GCMParameterSpec(128, ivBytes);
|
{
|
||||||
cipher.Init(CipherMode.DecryptMode, GetAesKey(), spec);
|
try
|
||||||
var decBytes = cipher.DoFinal(encBytes);
|
{
|
||||||
return decBytes;
|
var encKey = _settings.GetValueOrDefault<string>(AesKey);
|
||||||
|
if(encKey == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var encKeyBytes = Convert.FromBase64String(encKey);
|
||||||
|
var key = RsaDecrypt(encKeyBytes);
|
||||||
|
return new App.Models.SymmetricCryptoKey(key);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Console.WriteLine("Cannot get AesKey.");
|
||||||
|
_keyStore.DeleteEntry(KeyAlias);
|
||||||
|
_settings.Remove(AesKey);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] RsaEncrypt(byte[] input)
|
private byte[] RsaEncrypt(byte[] input)
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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
125
src/App/Utilities/Crypto.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue