diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj
index 47e3863a4..2f20f1023 100644
--- a/src/Android/Android.csproj
+++ b/src/Android/Android.csproj
@@ -110,6 +110,7 @@
+
diff --git a/src/Android/Migration/AndroidKeyStoreStorageService.cs b/src/Android/Migration/AndroidKeyStoreStorageService.cs
new file mode 100644
index 000000000..453b0f12a
--- /dev/null
+++ b/src/Android/Migration/AndroidKeyStoreStorageService.cs
@@ -0,0 +1,371 @@
+using Java.Security;
+using Javax.Crypto;
+using Android.OS;
+using Bit.App.Abstractions;
+using System;
+using Android.Security;
+using Javax.Security.Auth.X500;
+using Java.Math;
+using Android.Security.Keystore;
+using Android.App;
+using Java.Util;
+using Javax.Crypto.Spec;
+using Android.Preferences;
+using Bit.App.Migration;
+
+namespace Bit.Droid.Migration
+{
+ public class AndroidKeyStoreStorageService
+ {
+ private const string AndroidKeyStore = "AndroidKeyStore";
+ private const string AesMode = "AES/GCM/NoPadding";
+
+ private const string KeyAlias = "bitwardenKey2";
+ private const string KeyAliasV1 = "bitwardenKey";
+
+ private const string SettingsFormat = "ksSecured2:{0}";
+ private const string SettingsFormatV1 = "ksSecured:{0}";
+
+ private const string AesKey = "ksSecured2:aesKeyForService";
+ private const string AesKeyV1 = "ksSecured:aesKeyForService";
+
+ private readonly string _rsaMode;
+ private readonly bool _oldAndroid;
+ private readonly SettingsShim _settings;
+ private readonly KeyStore _keyStore;
+
+ public AndroidKeyStoreStorageService()
+ {
+ _oldAndroid = Build.VERSION.SdkInt < BuildVersionCodes.M;
+ _rsaMode = _oldAndroid ? "RSA/ECB/PKCS1Padding" : "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
+
+ _settings = new SettingsShim();
+
+ _keyStore = KeyStore.GetInstance(AndroidKeyStore);
+ _keyStore.Load(null);
+
+ /*
+ try
+ {
+ GenerateStoreKey(true);
+ }
+ catch
+ {
+ GenerateStoreKey(false);
+ }
+
+ GenerateAesKey();
+ */
+ }
+
+ public bool Contains(string key)
+ {
+ return _settings.Contains(string.Format(SettingsFormat, key)) ||
+ _settings.Contains(string.Format(SettingsFormatV1, key));
+ }
+
+ public void Delete(string key)
+ {
+ CleanupOld(key);
+
+ var formattedKey = string.Format(SettingsFormat, key);
+ if(_settings.Contains(formattedKey))
+ {
+ _settings.Remove(formattedKey);
+ }
+ }
+
+ public byte[] Retrieve(string key)
+ {
+ var formattedKey = string.Format(SettingsFormat, key);
+ if(!_settings.Contains(formattedKey))
+ {
+ return TryGetAndMigrate(key);
+ }
+
+ var cs = _settings.GetValueOrDefault(formattedKey, null);
+ if(string.IsNullOrWhiteSpace(cs))
+ {
+ return null;
+ }
+
+ var aesKey = GetAesKey();
+ if(aesKey == null)
+ {
+ return null;
+ }
+
+ try
+ {
+ return App.Migration.Crypto.AesCbcDecrypt(new App.Migration.Models.CipherString(cs), aesKey);
+ }
+ catch
+ {
+ Console.WriteLine("Failed to decrypt from secure storage.");
+ _settings.Remove(formattedKey);
+ //Utilities.SendCrashEmail(e);
+ //Utilities.SaveCrashFile(e);
+ return null;
+ }
+ }
+
+ public void Store(string key, byte[] dataBytes)
+ {
+ var formattedKey = string.Format(SettingsFormat, key);
+ CleanupOld(key);
+ if(dataBytes == null)
+ {
+ _settings.Remove(formattedKey);
+ return;
+ }
+
+ var aesKey = GetAesKey();
+ if(aesKey == null)
+ {
+ return;
+ }
+
+ try
+ {
+ var cipherString = App.Migration.Crypto.AesCbcEncrypt(dataBytes, aesKey);
+ _settings.AddOrUpdateValue(formattedKey, cipherString.EncryptedString);
+ }
+ catch
+ {
+ Console.WriteLine("Failed to encrypt to secure storage.");
+ //Utilities.SendCrashEmail(e);
+ //Utilities.SaveCrashFile(e);
+ }
+ }
+
+ private void GenerateStoreKey(bool withDate)
+ {
+ if(_keyStore.ContainsAlias(KeyAlias))
+ {
+ return;
+ }
+
+ ClearSettings();
+
+ var end = Calendar.Instance;
+ end.Add(CalendarField.Year, 99);
+
+ if(_oldAndroid)
+ {
+ var subject = new X500Principal($"CN={KeyAlias}");
+
+ var builder = new KeyPairGeneratorSpec.Builder(Application.Context)
+ .SetAlias(KeyAlias)
+ .SetSubject(subject)
+ .SetSerialNumber(BigInteger.Ten);
+
+ if(withDate)
+ {
+ builder.SetStartDate(new Date(0)).SetEndDate(end.Time);
+ }
+
+ var spec = builder.Build();
+ var gen = KeyPairGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, AndroidKeyStore);
+ gen.Initialize(spec);
+ gen.GenerateKeyPair();
+ }
+ else
+ {
+ var builder = new KeyGenParameterSpec.Builder(KeyAlias, KeyStorePurpose.Decrypt | KeyStorePurpose.Encrypt)
+ .SetBlockModes(KeyProperties.BlockModeGcm)
+ .SetEncryptionPaddings(KeyProperties.EncryptionPaddingNone);
+
+ if(withDate)
+ {
+ builder.SetKeyValidityStart(new Date(0)).SetKeyValidityEnd(end.Time);
+ }
+
+ var spec = builder.Build();
+ var gen = KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmAes, AndroidKeyStore);
+ gen.Init(spec);
+ gen.GenerateKey();
+ }
+ }
+
+ private KeyStore.PrivateKeyEntry GetRsaKeyEntry(string alias)
+ {
+ return _keyStore.GetEntry(alias, null) as KeyStore.PrivateKeyEntry;
+ }
+
+ private void GenerateAesKey()
+ {
+ if(_settings.Contains(AesKey))
+ {
+ return;
+ }
+
+ var key = App.Migration.Crypto.RandomBytes(512 / 8);
+ var encKey = _oldAndroid ? RsaEncrypt(key) : AesEncrypt(key);
+ _settings.AddOrUpdateValue(AesKey, encKey);
+ }
+
+ private App.Migration.Models.SymmetricCryptoKey GetAesKey(bool v1 = false)
+ {
+ try
+ {
+ var aesKey = v1 ? AesKeyV1 : AesKey;
+ if(!_settings.Contains(aesKey))
+ {
+ return null;
+ }
+
+ var encKey = _settings.GetValueOrDefault(aesKey, null);
+ if(string.IsNullOrWhiteSpace(encKey))
+ {
+ return null;
+ }
+
+ if(_oldAndroid || v1)
+ {
+ var encKeyBytes = Convert.FromBase64String(encKey);
+ var key = RsaDecrypt(encKeyBytes, v1);
+ return new App.Migration.Models.SymmetricCryptoKey(key);
+ }
+ else
+ {
+ var parts = encKey.Split('|');
+ if(parts.Length < 2)
+ {
+ return null;
+ }
+
+ var ivBytes = Convert.FromBase64String(parts[0]);
+ var encKeyBytes = Convert.FromBase64String(parts[1]);
+ var key = AesDecrypt(ivBytes, encKeyBytes);
+ return new App.Migration.Models.SymmetricCryptoKey(key);
+ }
+ }
+ catch
+ {
+ Console.WriteLine("Cannot get AesKey.");
+ _keyStore.DeleteEntry(KeyAlias);
+ _settings.Remove(AesKey);
+ if(!v1)
+ {
+ //Utilities.SendCrashEmail(e);
+ //Utilities.SaveCrashFile(e);
+ }
+ return null;
+ }
+ }
+
+ private string AesEncrypt(byte[] input)
+ {
+ using(var entry = _keyStore.GetKey(KeyAlias, null))
+ using(var cipher = Cipher.GetInstance(AesMode))
+ {
+ cipher.Init(CipherMode.EncryptMode, entry);
+ var encBytes = cipher.DoFinal(input);
+ var ivBytes = cipher.GetIV();
+ return $"{Convert.ToBase64String(ivBytes)}|{Convert.ToBase64String(encBytes)}";
+ }
+ }
+
+ private byte[] AesDecrypt(byte[] iv, byte[] encData)
+ {
+ using(var entry = _keyStore.GetKey(KeyAlias, null))
+ using(var cipher = Cipher.GetInstance(AesMode))
+ {
+ var spec = new GCMParameterSpec(128, iv);
+ cipher.Init(CipherMode.DecryptMode, entry, spec);
+ var decBytes = cipher.DoFinal(encData);
+ return decBytes;
+ }
+ }
+
+ private string RsaEncrypt(byte[] data)
+ {
+ using(var entry = GetRsaKeyEntry(KeyAlias))
+ using(var cipher = Cipher.GetInstance(_rsaMode))
+ {
+ cipher.Init(CipherMode.EncryptMode, entry.Certificate.PublicKey);
+ var cipherText = cipher.DoFinal(data);
+ return Convert.ToBase64String(cipherText);
+ }
+ }
+
+ private byte[] RsaDecrypt(byte[] encData, bool v1)
+ {
+ using(var entry = GetRsaKeyEntry(v1 ? KeyAliasV1 : KeyAlias))
+ using(var cipher = Cipher.GetInstance(_rsaMode))
+ {
+ if(_oldAndroid)
+ {
+ cipher.Init(CipherMode.DecryptMode, entry.PrivateKey);
+ }
+ else
+ {
+ cipher.Init(CipherMode.DecryptMode, entry.PrivateKey, OAEPParameterSpec.Default);
+ }
+
+ var plainText = cipher.DoFinal(encData);
+ return plainText;
+ }
+ }
+
+ private byte[] TryGetAndMigrate(string key)
+ {
+ var formattedKeyV1 = string.Format(SettingsFormatV1, key);
+ if(_settings.Contains(formattedKeyV1))
+ {
+ var aesKeyV1 = GetAesKey(true);
+ if(aesKeyV1 != null)
+ {
+ try
+ {
+ var cs = _settings.GetValueOrDefault(formattedKeyV1, null);
+ var value = App.Migration.Crypto.AesCbcDecrypt(new App.Migration.Models.CipherString(cs), aesKeyV1);
+ Store(key, value);
+ return value;
+ }
+ catch
+ {
+ Console.WriteLine("Failed to decrypt v1 from secure storage.");
+ }
+ }
+
+ _settings.Remove(formattedKeyV1);
+ }
+
+ return null;
+ }
+
+ private void CleanupOld(string key)
+ {
+ var formattedKeyV1 = string.Format(SettingsFormatV1, key);
+ if(_settings.Contains(formattedKeyV1))
+ {
+ _settings.Remove(formattedKeyV1);
+ }
+ }
+
+ private void ClearSettings(string format = SettingsFormat)
+ {
+ var prefix = string.Format(format, string.Empty);
+
+ using(var sharedPreferences = PreferenceManager.GetDefaultSharedPreferences(Application.Context))
+ using(var sharedPreferencesEditor = sharedPreferences.Edit())
+ {
+ var removed = false;
+ foreach(var pref in sharedPreferences.All)
+ {
+ if(pref.Key.StartsWith(prefix))
+ {
+ removed = true;
+ sharedPreferencesEditor.Remove(pref.Key);
+ }
+ }
+
+ if(removed)
+ {
+ sharedPreferencesEditor.Commit();
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/App/Migration/Crypto.cs b/src/App/Migration/Crypto.cs
new file mode 100644
index 000000000..d17ca639a
--- /dev/null
+++ b/src/App/Migration/Crypto.cs
@@ -0,0 +1,199 @@
+using Bit.App.Migration.Models;
+using Bit.Core.Enums;
+using PCLCrypto;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Bit.App.Migration
+{
+ public static class Crypto
+ {
+ public static CipherString AesCbcEncrypt(byte[] plainBytes, SymmetricCryptoKey key)
+ {
+ var parts = AesCbcEncryptToParts(plainBytes, key);
+ return new CipherString(parts.Item1, Convert.ToBase64String(parts.Item2),
+ Convert.ToBase64String(parts.Item4), parts.Item3 != null ? Convert.ToBase64String(parts.Item3) : null);
+ }
+
+ public static byte[] AesCbcEncryptToBytes(byte[] plainBytes, SymmetricCryptoKey key)
+ {
+ var parts = AesCbcEncryptToParts(plainBytes, key);
+ var macLength = parts.Item3?.Length ?? 0;
+
+ var encBytes = new byte[1 + parts.Item2.Length + macLength + parts.Item4.Length];
+ encBytes[0] = (byte)parts.Item1;
+ parts.Item2.CopyTo(encBytes, 1);
+ if(parts.Item3 != null)
+ {
+ parts.Item3.CopyTo(encBytes, 1 + parts.Item2.Length);
+ }
+ parts.Item4.CopyTo(encBytes, 1 + parts.Item2.Length + macLength);
+ return encBytes;
+ }
+
+ private static Tuple AesCbcEncryptToParts(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 ct = WinRTCrypto.CryptographicEngine.Encrypt(cryptoKey, plainBytes, iv);
+ var mac = key.MacKey != null ? ComputeMac(ct, iv, key.MacKey) : null;
+
+ return new Tuple(key.EncryptionType, iv, mac, ct);
+ }
+
+ public static byte[] AesCbcDecrypt(CipherString encyptedValue, SymmetricCryptoKey key)
+ {
+ if(encyptedValue == null)
+ {
+ throw new ArgumentNullException(nameof(encyptedValue));
+ }
+
+ return AesCbcDecrypt(encyptedValue.EncryptionType, encyptedValue.CipherTextBytes,
+ encyptedValue.InitializationVectorBytes, encyptedValue.MacBytes, key);
+ }
+
+ public static byte[] AesCbcDecrypt(EncryptionType type, byte[] ct, byte[] iv, byte[] mac,
+ SymmetricCryptoKey key)
+ {
+ if(key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ if(ct == null)
+ {
+ throw new ArgumentNullException(nameof(ct));
+ }
+
+ if(iv == null)
+ {
+ throw new ArgumentNullException(nameof(iv));
+ }
+
+ if(key.MacKey != null && mac == null)
+ {
+ throw new ArgumentNullException(nameof(mac));
+ }
+
+ if(key.EncryptionType != type)
+ {
+ throw new InvalidOperationException(nameof(type));
+ }
+
+ if(key.MacKey != null && mac != null)
+ {
+ var computedMacBytes = ComputeMac(ct, iv, key.MacKey);
+ if(!MacsEqual(computedMacBytes, mac))
+ {
+ throw new InvalidOperationException("MAC failed.");
+ }
+ }
+
+ var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
+ var cryptoKey = provider.CreateSymmetricKey(key.EncKey);
+ var decryptedBytes = WinRTCrypto.CryptographicEngine.Decrypt(cryptoKey, ct, iv);
+ return decryptedBytes;
+ }
+
+ public static byte[] RandomBytes(int length)
+ {
+ return WinRTCrypto.CryptographicBuffer.GenerateRandom(length);
+ }
+
+ public static byte[] ComputeMac(byte[] ctBytes, byte[] ivBytes, byte[] macKey)
+ {
+ if(ctBytes == null)
+ {
+ throw new ArgumentNullException(nameof(ctBytes));
+ }
+
+ if(ivBytes == null)
+ {
+ throw new ArgumentNullException(nameof(ivBytes));
+ }
+
+ return ComputeMac(ivBytes.Concat(ctBytes), macKey);
+ }
+
+ public static byte[] ComputeMac(IEnumerable dataBytes, byte[] macKey)
+ {
+ if(macKey == null)
+ {
+ throw new ArgumentNullException(nameof(macKey));
+ }
+
+ if(dataBytes == null)
+ {
+ throw new ArgumentNullException(nameof(dataBytes));
+ }
+
+ var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256);
+ var hasher = algorithm.CreateHash(macKey);
+ hasher.Append(dataBytes.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/
+ // ref: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy
+ public static bool MacsEqual(byte[] mac1, byte[] mac2)
+ {
+ var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256);
+ var hasher = algorithm.CreateHash(RandomBytes(32));
+
+ 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;
+ }
+
+ // ref: https://tools.ietf.org/html/rfc5869
+ public static byte[] HkdfExpand(byte[] prk, byte[] info, int size)
+ {
+ var hashLen = 32; // sha256
+ var okm = new byte[size];
+ var previousT = new byte[0];
+ var n = (int)Math.Ceiling((double)size / hashLen);
+ for(int i = 0; i < n; i++)
+ {
+ var t = new byte[previousT.Length + info.Length + 1];
+ previousT.CopyTo(t, 0);
+ info.CopyTo(t, previousT.Length);
+ t[t.Length - 1] = (byte)(i + 1);
+ previousT = ComputeMac(t, prk);
+ previousT.CopyTo(okm, i * hashLen);
+ }
+ return okm;
+ }
+ }
+}
diff --git a/src/App/Migration/Models/CipherString.cs b/src/App/Migration/Models/CipherString.cs
new file mode 100644
index 000000000..e9c61d74b
--- /dev/null
+++ b/src/App/Migration/Models/CipherString.cs
@@ -0,0 +1,117 @@
+using System;
+using Bit.Core.Enums;
+
+namespace Bit.App.Migration.Models
+{
+ public class CipherString
+ {
+ private string _decryptedValue;
+
+ public CipherString(string encryptedString)
+ {
+ if(string.IsNullOrWhiteSpace(encryptedString))
+ {
+ throw new ArgumentException(nameof(encryptedString));
+ }
+
+ var headerPieces = encryptedString.Split('.');
+ string[] encPieces;
+
+ EncryptionType encType;
+ if(headerPieces.Length == 2 && Enum.TryParse(headerPieces[0], out encType))
+ {
+ EncryptionType = encType;
+ encPieces = headerPieces[1].Split('|');
+ }
+ else if(headerPieces.Length == 1)
+ {
+ encPieces = headerPieces[0].Split('|');
+ EncryptionType = encPieces.Length == 3 ? EncryptionType.AesCbc128_HmacSha256_B64 :
+ EncryptionType.AesCbc256_B64;
+ }
+ else
+ {
+ throw new ArgumentException("Malformed header.");
+ }
+
+ switch(EncryptionType)
+ {
+ case EncryptionType.AesCbc256_B64:
+ if(encPieces.Length != 2)
+ {
+ throw new ArgumentException("Malformed encPieces.");
+ }
+ InitializationVector = encPieces[0];
+ CipherText = encPieces[1];
+ break;
+ case EncryptionType.AesCbc128_HmacSha256_B64:
+ case EncryptionType.AesCbc256_HmacSha256_B64:
+ if(encPieces.Length != 3)
+ {
+ throw new ArgumentException("Malformed encPieces.");
+ }
+ InitializationVector = encPieces[0];
+ CipherText = encPieces[1];
+ Mac = encPieces[2];
+ break;
+ case EncryptionType.Rsa2048_OaepSha256_B64:
+ case EncryptionType.Rsa2048_OaepSha1_B64:
+ if(encPieces.Length != 1)
+ {
+ throw new ArgumentException("Malformed encPieces.");
+ }
+ CipherText = encPieces[0];
+ break;
+ case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:
+ case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:
+ if(encPieces.Length != 2)
+ {
+ throw new ArgumentException("Malformed encPieces.");
+ }
+ CipherText = encPieces[0];
+ Mac = encPieces[1];
+ break;
+ default:
+ throw new ArgumentException("Unknown encType.");
+ }
+
+ EncryptedString = encryptedString;
+ }
+
+ public CipherString(EncryptionType encryptionType, string initializationVector, string cipherText,
+ string mac = null)
+ {
+ if(string.IsNullOrWhiteSpace(initializationVector))
+ {
+ throw new ArgumentNullException(nameof(initializationVector));
+ }
+
+ if(string.IsNullOrWhiteSpace(cipherText))
+ {
+ throw new ArgumentNullException(nameof(cipherText));
+ }
+
+ EncryptionType = encryptionType;
+ EncryptedString = string.Format("{0}.{1}|{2}", (byte)EncryptionType, initializationVector, cipherText);
+
+ if(!string.IsNullOrWhiteSpace(mac))
+ {
+ EncryptedString = string.Format("{0}|{1}", EncryptedString, mac);
+ }
+
+ CipherText = cipherText;
+ InitializationVector = initializationVector;
+ Mac = mac;
+ }
+
+ public EncryptionType EncryptionType { get; private set; }
+ public string EncryptedString { get; private set; }
+ public string InitializationVector { get; private set; }
+ public string CipherText { get; private set; }
+ public string Mac { get; private set; }
+ public byte[] InitializationVectorBytes => string.IsNullOrWhiteSpace(InitializationVector) ?
+ null : Convert.FromBase64String(InitializationVector);
+ public byte[] CipherTextBytes => Convert.FromBase64String(CipherText);
+ public byte[] MacBytes => Mac == null ? null : Convert.FromBase64String(Mac);
+ }
+}
diff --git a/src/App/Migration/Models/SymmetricCryptoKey.cs b/src/App/Migration/Models/SymmetricCryptoKey.cs
new file mode 100644
index 000000000..2a0572dba
--- /dev/null
+++ b/src/App/Migration/Models/SymmetricCryptoKey.cs
@@ -0,0 +1,62 @@
+using Bit.Core.Enums;
+using System;
+using System.Linq;
+
+namespace Bit.App.Migration.Models
+{
+ public class SymmetricCryptoKey
+ {
+ public SymmetricCryptoKey(byte[] rawBytes, EncryptionType? encType = null)
+ {
+ if(rawBytes == null || rawBytes.Length == 0)
+ {
+ throw new Exception("Must provide keyBytes.");
+ }
+
+ if(encType == null)
+ {
+ if(rawBytes.Length == 32)
+ {
+ encType = EncryptionType.AesCbc256_B64;
+ }
+ else if(rawBytes.Length == 64)
+ {
+ encType = EncryptionType.AesCbc256_HmacSha256_B64;
+ }
+ else
+ {
+ throw new Exception("Unable to determine encType.");
+ }
+ }
+
+ EncryptionType = encType.Value;
+ Key = rawBytes;
+
+ if(EncryptionType == EncryptionType.AesCbc256_B64 && Key.Length == 32)
+ {
+ EncKey = Key;
+ MacKey = null;
+ }
+ else if(EncryptionType == EncryptionType.AesCbc128_HmacSha256_B64 && Key.Length == 32)
+ {
+ EncKey = Key.Take(16).ToArray();
+ MacKey = Key.Skip(16).Take(16).ToArray();
+ }
+ else if(EncryptionType == EncryptionType.AesCbc256_HmacSha256_B64 && Key.Length == 64)
+ {
+ EncKey = Key.Take(32).ToArray();
+ MacKey = Key.Skip(32).Take(32).ToArray();
+ }
+ else
+ {
+ throw new Exception("Unsupported encType/key length.");
+ }
+ }
+
+ public byte[] Key { get; set; }
+ public string B64Key => Convert.ToBase64String(Key);
+ public byte[] EncKey { get; set; }
+ public byte[] MacKey { get; set; }
+ public EncryptionType EncryptionType { get; set; }
+ }
+}
diff --git a/src/App/Migration/SettingsShim.cs b/src/App/Migration/SettingsShim.cs
new file mode 100644
index 000000000..cf88a3caa
--- /dev/null
+++ b/src/App/Migration/SettingsShim.cs
@@ -0,0 +1,67 @@
+using System;
+
+namespace Bit.App.Migration
+{
+ public class SettingsShim
+ {
+ public bool Contains(string key)
+ {
+ return Xamarin.Essentials.Preferences.ContainsKey(key);
+ }
+
+ public string GetValueOrDefault(string key, string defaultValue)
+ {
+ return Xamarin.Essentials.Preferences.Get(key, defaultValue);
+ }
+
+ public DateTime GetValueOrDefault(string key, DateTime defaultValue)
+ {
+ return Xamarin.Essentials.Preferences.Get(key, defaultValue);
+ }
+
+ public bool GetValueOrDefault(string key, bool defaultValue)
+ {
+ return Xamarin.Essentials.Preferences.Get(key, defaultValue);
+ }
+
+ public int GetValueOrDefault(string key, int defaultValue)
+ {
+ return Xamarin.Essentials.Preferences.Get(key, defaultValue);
+ }
+
+ public long GetValueOrDefault(string key, long defaultValue)
+ {
+ return Xamarin.Essentials.Preferences.Get(key, defaultValue);
+ }
+
+ public void AddOrUpdateValue(string key, string value)
+ {
+ Xamarin.Essentials.Preferences.Set(key, value);
+ }
+
+ public void AddOrUpdateValue(string key, DateTime value)
+ {
+ Xamarin.Essentials.Preferences.Set(key, value);
+ }
+
+ public void AddOrUpdateValue(string key, bool value)
+ {
+ Xamarin.Essentials.Preferences.Set(key, value);
+ }
+
+ public void AddOrUpdateValue(string key, long value)
+ {
+ Xamarin.Essentials.Preferences.Set(key, value);
+ }
+
+ public void AddOrUpdateValue(string key, int value)
+ {
+ Xamarin.Essentials.Preferences.Set(key, value);
+ }
+
+ public void Remove(string key)
+ {
+ Xamarin.Essentials.Preferences.Remove(key);
+ }
+ }
+}