refactor for enc type header and cryptokey

This commit is contained in:
Kyle Spearrin 2017-04-19 23:16:09 -04:00
parent 0ebfe85d8e
commit e7f3b115a4
11 changed files with 211 additions and 85 deletions

View file

@ -14,6 +14,6 @@ namespace Bit.App.Abstractions
void LogOut();
Task<FullLoginResult> TokenPostAsync(string email, string masterPassword);
Task<LoginResult> TokenPostTwoFactorAsync(string token, string email, string masterPasswordHash, byte[] key);
Task<LoginResult> TokenPostTwoFactorAsync(string token, string email, string masterPasswordHash, CryptoKey key);
}
}

View file

@ -4,16 +4,15 @@ namespace Bit.App.Abstractions
{
public interface ICryptoService
{
string Base64Key { get; }
byte[] Key { get; set; }
byte[] PreviousKey { get; }
CryptoKey Key { get; set; }
CryptoKey PreviousKey { get; }
bool KeyChanged { get; }
string Decrypt(CipherString encyptedValue);
CipherString Encrypt(string plaintextValue);
byte[] MakeKeyFromPassword(string password, string salt);
string Decrypt(CipherString encyptedValue, CryptoKey key = null);
CipherString Encrypt(string plaintextValue, CryptoKey key = null);
CryptoKey MakeKeyFromPassword(string password, string salt);
string MakeKeyFromPasswordBase64(string password, string salt);
byte[] HashPassword(byte[] key, string password);
string HashPasswordBase64(byte[] key, string password);
byte[] HashPassword(CryptoKey key, string password);
string HashPasswordBase64(CryptoKey key, string password);
}
}

View file

@ -80,6 +80,7 @@
<Compile Include="Controls\FormEntryCell.cs" />
<Compile Include="Controls\PinControl.cs" />
<Compile Include="Controls\VaultListViewCell.cs" />
<Compile Include="Enums\EncryptionType.cs" />
<Compile Include="Enums\LockType.cs" />
<Compile Include="Enums\CipherType.cs" />
<Compile Include="Enums\PushType.cs" />
@ -108,6 +109,7 @@
<Compile Include="Models\Api\LoginDataModel.cs" />
<Compile Include="Models\Cipher.cs" />
<Compile Include="Models\CipherString.cs" />
<Compile Include="Models\CryptoKey.cs" />
<Compile Include="Models\Data\SettingsData.cs" />
<Compile Include="Models\Data\FolderData.cs" />
<Compile Include="Abstractions\IDataObject.cs" />

View file

@ -0,0 +1,10 @@
namespace Bit.App.Enums
{
public enum EncryptionType : byte
{
AesCbc256_B64 = 0,
AesCbc128_HmacSha256_B64 = 1,
AesCbc256_HmacSha256_B64 = 2,
RsaOaep_Sha256_B64 = 3
}
}

View file

@ -1,6 +1,7 @@
using System;
using Bit.App.Abstractions;
using XLabs.Ioc;
using Bit.App.Enums;
namespace Bit.App.Models
{
@ -15,10 +16,58 @@ namespace Bit.App.Models
throw new ArgumentException(nameof(encryptedString));
}
var headerPieces = encryptedString.Split('.');
string[] encPieces;
if(headerPieces.Length == 2 && Enum.TryParse(headerPieces[0], out EncryptionType 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.RsaOaep_Sha256_B64:
if(encPieces.Length != 1)
{
throw new ArgumentException("Malformed encPieces.");
}
CipherText = encPieces[0];
break;
default:
throw new ArgumentException("Unknown encType.");
}
EncryptedString = encryptedString;
}
public CipherString(string initializationVector, string cipherText, string mac = null)
public CipherString(EncryptionType encryptionType, string initializationVector, string cipherText, string mac = null)
{
if(string.IsNullOrWhiteSpace(initializationVector))
{
@ -30,30 +79,24 @@ namespace Bit.App.Models
throw new ArgumentNullException(nameof(cipherText));
}
EncryptedString = string.Format("{0}|{1}", initializationVector, 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 => EncryptedString?.Split('|')[0] ?? null;
public string CipherText => EncryptedString?.Split('|')[1] ?? null;
public string Mac
{
get
{
var pieces = EncryptedString?.Split('|') ?? new string[0];
if(pieces.Length > 2)
{
return pieces[2];
}
return null;
}
}
public string InitializationVector { get; private set; }
public string CipherText { get; private set; }
public string Mac { get; private set; }
public byte[] InitializationVectorBytes => Convert.FromBase64String(InitializationVector);
public byte[] CipherTextBytes => Convert.FromBase64String(CipherText);
public byte[] MacBytes => Mac == null ? null : Convert.FromBase64String(Mac);

View file

@ -0,0 +1,62 @@
using Bit.App.Enums;
using System;
using System.Linq;
namespace Bit.App.Models
{
public class CryptoKey
{
public CryptoKey(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; }
}
}

View file

@ -9,7 +9,7 @@
public class FullLoginResult : LoginResult
{
public bool TwoFactorRequired { get; set; }
public byte[] Key { get; set; }
public CryptoKey Key { get; set; }
public string MasterPasswordHash { get; set; }
}
}

View file

@ -120,7 +120,7 @@ namespace Bit.App.Pages
}
var key = _cryptoService.MakeKeyFromPassword(PasswordCell.Entry.Text, _authService.Email);
if(key.SequenceEqual(_cryptoService.Key))
if(key.Key.SequenceEqual(_cryptoService.Key.Key))
{
_settings.AddOrUpdateValue(Constants.Locked, false);
await Navigation.PopModalAsync();

View file

@ -7,6 +7,7 @@ using XLabs.Ioc;
using Acr.UserDialogs;
using System.Threading.Tasks;
using PushNotification.Plugin.Abstractions;
using Bit.App.Models;
namespace Bit.App.Pages
{
@ -19,9 +20,9 @@ namespace Bit.App.Pages
private IPushNotification _pushNotification;
private readonly string _email;
private readonly string _masterPasswordHash;
private readonly byte[] _key;
private readonly CryptoKey _key;
public LoginTwoFactorPage(string email, string masterPasswordHash, byte[] key)
public LoginTwoFactorPage(string email, string masterPasswordHash, CryptoKey key)
: base(updateActivity: false)
{
_email = email;

View file

@ -6,8 +6,6 @@ using Bit.App.Models.Api;
using Plugin.Settings.Abstractions;
using Bit.App.Models;
using System.Linq;
using Xamarin.Forms;
using PushNotification.Plugin.Abstractions;
namespace Bit.App.Services
{
@ -237,7 +235,7 @@ namespace Bit.App.Services
}
public async Task<LoginResult> TokenPostTwoFactorAsync(string token, string email, string masterPasswordHash,
byte[] key)
CryptoKey key)
{
var result = new LoginResult();
@ -263,7 +261,7 @@ namespace Bit.App.Services
return result;
}
private void ProcessLoginSuccess(byte[] key, TokenResponse response)
private void ProcessLoginSuccess(CryptoKey key, TokenResponse response)
{
_cryptoService.Key = key;
_tokenService.Token = response.AccessToken;

View file

@ -5,7 +5,6 @@ using Bit.App.Abstractions;
using Bit.App.Models;
using PCLCrypto;
using System.Linq;
using Xamarin.Forms;
namespace Bit.App.Services
{
@ -17,8 +16,9 @@ namespace Bit.App.Services
private readonly ISecureStorageService _secureStorage;
private readonly IKeyDerivationService _keyDerivationService;
private byte[] _key;
private byte[] _previousKey;
private CryptoKey _key;
private CryptoKey _legacyEtmKey;
private CryptoKey _previousKey;
public CryptoService(
ISecureStorageService secureStorage,
@ -28,13 +28,13 @@ namespace Bit.App.Services
_keyDerivationService = keyDerivationService;
}
public byte[] Key
public CryptoKey Key
{
get
{
if(_key == null)
{
_key = _secureStorage.Retrieve(KeyKey);
_key = new CryptoKey(_secureStorage.Retrieve(KeyKey));
}
return _key;
@ -43,37 +43,25 @@ namespace Bit.App.Services
{
if(value != null)
{
_secureStorage.Store(KeyKey, value);
_secureStorage.Store(KeyKey, value.Key);
}
else
{
PreviousKey = _key;
_secureStorage.Delete(KeyKey);
_key = null;
_legacyEtmKey = null;
}
}
}
public string Base64Key
{
get
{
if(Key == null)
{
return null;
}
return Convert.ToBase64String(Key);
}
}
public byte[] PreviousKey
public CryptoKey PreviousKey
{
get
{
if(_previousKey == null)
{
_previousKey = _secureStorage.Retrieve(PreviousKeyKey);
_previousKey = new CryptoKey(_secureStorage.Retrieve(PreviousKeyKey));
}
return _previousKey;
@ -82,7 +70,7 @@ namespace Bit.App.Services
{
if(value != null)
{
_secureStorage.Store(PreviousKeyKey, value);
_secureStorage.Store(PreviousKeyKey, value.Key);
_previousKey = value;
}
}
@ -102,18 +90,20 @@ namespace Bit.App.Services
return Key != null;
}
return !PreviousKey.SequenceEqual(Key);
return !PreviousKey.Key.SequenceEqual(Key.Key);
}
}
public byte[] EncKey => Key?.Take(16).ToArray();
public byte[] MacKey => Key?.Skip(16).Take(16).ToArray();
public CipherString Encrypt(string plaintextValue, CryptoKey key = null)
{
if(key == null)
{
key = Key;
}
public CipherString Encrypt(string plaintextValue)
if(key == null)
{
if(Key == null)
{
throw new ArgumentNullException(nameof(Key));
throw new ArgumentNullException(nameof(key));
}
if(plaintextValue == null)
@ -124,23 +114,26 @@ namespace Bit.App.Services
var plaintextBytes = Encoding.UTF8.GetBytes(plaintextValue);
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
// TODO: Turn on whenever ready to support encrypt-then-mac
var cryptoKey = provider.CreateSymmetricKey(true ? EncKey : Key);
var cryptoKey = provider.CreateSymmetricKey(key.EncKey);
var iv = WinRTCrypto.CryptographicBuffer.GenerateRandom(provider.BlockLength);
var encryptedBytes = WinRTCrypto.CryptographicEngine.Encrypt(cryptoKey, plaintextBytes, iv);
// TODO: Turn on whenever ready to support encrypt-then-mac
var mac = true ? ComputeMac(encryptedBytes, iv) : null;
var mac = key.MacKey != null ? ComputeMac(encryptedBytes, iv, key.MacKey) : null;
return new CipherString(Convert.ToBase64String(iv), Convert.ToBase64String(encryptedBytes), mac);
return new CipherString(key.EncryptionType, Convert.ToBase64String(iv), Convert.ToBase64String(encryptedBytes), mac);
}
public string Decrypt(CipherString encyptedValue)
public string Decrypt(CipherString encyptedValue, CryptoKey key = null)
{
try
{
if(Key == null)
if(key == null)
{
throw new ArgumentNullException(nameof(Key));
key = Key;
}
if(key == null)
{
throw new ArgumentNullException(nameof(key));
}
if(encyptedValue == null)
@ -148,9 +141,27 @@ namespace Bit.App.Services
throw new ArgumentNullException(nameof(encyptedValue));
}
if(encyptedValue.Mac != null)
if(encyptedValue.EncryptionType == Enums.EncryptionType.AesCbc128_HmacSha256_B64 &&
key.EncryptionType == Enums.EncryptionType.AesCbc256_B64)
{
var computedMac = ComputeMac(encyptedValue.CipherTextBytes, encyptedValue.InitializationVectorBytes);
// Old encrypt-then-mac scheme, swap out the key
if(_legacyEtmKey == null)
{
_legacyEtmKey = new CryptoKey(key.Key, Enums.EncryptionType.AesCbc128_HmacSha256_B64);
}
key = _legacyEtmKey;
}
if(encyptedValue.EncryptionType != key.EncryptionType)
{
throw new ArgumentException("encType unavailable.");
}
if(key.MacKey != null && !string.IsNullOrWhiteSpace(encyptedValue.Mac))
{
var computedMac = ComputeMac(encyptedValue.CipherTextBytes,
encyptedValue.InitializationVectorBytes, key.MacKey);
if(computedMac != encyptedValue.Mac)
{
throw new InvalidOperationException("MAC failed.");
@ -158,7 +169,7 @@ namespace Bit.App.Services
}
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
var cryptoKey = provider.CreateSymmetricKey(encyptedValue.Mac != null ? EncKey : Key);
var cryptoKey = provider.CreateSymmetricKey(key.EncKey);
var decryptedBytes = WinRTCrypto.CryptographicEngine.Decrypt(cryptoKey, encyptedValue.CipherTextBytes,
encyptedValue.InitializationVectorBytes);
return Encoding.UTF8.GetString(decryptedBytes, 0, decryptedBytes.Length).TrimEnd('\0');
@ -170,11 +181,11 @@ namespace Bit.App.Services
}
}
private string ComputeMac(byte[] ctBytes, byte[] ivBytes)
private string ComputeMac(byte[] ctBytes, byte[] ivBytes, byte[] macKey)
{
if(MacKey == null)
if(macKey == null)
{
throw new ArgumentNullException(nameof(MacKey));
throw new ArgumentNullException(nameof(macKey));
}
if(ctBytes == null)
@ -188,13 +199,13 @@ namespace Bit.App.Services
}
var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256);
var hasher = algorithm.CreateHash(MacKey);
var hasher = algorithm.CreateHash(macKey);
hasher.Append(ivBytes.Concat(ctBytes).ToArray());
var mac = hasher.GetValueAndReset();
return Convert.ToBase64String(mac);
}
public byte[] MakeKeyFromPassword(string password, string salt)
public CryptoKey MakeKeyFromPassword(string password, string salt)
{
if(password == null)
{
@ -209,17 +220,17 @@ namespace Bit.App.Services
var passwordBytes = Encoding.UTF8.GetBytes(password);
var saltBytes = Encoding.UTF8.GetBytes(salt);
var key = _keyDerivationService.DeriveKey(passwordBytes, saltBytes, 5000);
return key;
var keyBytes = _keyDerivationService.DeriveKey(passwordBytes, saltBytes, 5000);
return new CryptoKey(keyBytes);
}
public string MakeKeyFromPasswordBase64(string password, string salt)
{
var key = MakeKeyFromPassword(password, salt);
return Convert.ToBase64String(key);
return Convert.ToBase64String(key.Key);
}
public byte[] HashPassword(byte[] key, string password)
public byte[] HashPassword(CryptoKey key, string password)
{
if(key == null)
{
@ -232,11 +243,11 @@ namespace Bit.App.Services
}
var passwordBytes = Encoding.UTF8.GetBytes(password);
var hash = _keyDerivationService.DeriveKey(key, passwordBytes, 1);
var hash = _keyDerivationService.DeriveKey(key.Key, passwordBytes, 1);
return hash;
}
public string HashPasswordBase64(byte[] key, string password)
public string HashPasswordBase64(CryptoKey key, string password)
{
var hash = HashPassword(key, password);
return Convert.ToBase64String(hash);