Replace scrypt with libsodium's abstractions

This commit is contained in:
Vishnu Mohandas 2020-09-29 00:02:53 +05:30
parent b06190fbf2
commit fed7cda7f4
7 changed files with 111 additions and 95 deletions

View file

@ -21,11 +21,8 @@ class Configuration {
static const hasOptedForE2EKey = "has_opted_for_e2e_encryption";
static const foldersToBackUpKey = "folders_to_back_up";
static const keyKey = "key";
static const keyEncryptedKey = "encrypted_key";
static const keyKekSalt = "kek_salt";
static const keyKekHash = "kek_hash";
static const keyKekHashSalt = "kek_hash_salt";
static const keyEncryptedKeyIV = "encrypted_key_iv";
static const encryptedKeyKey = "encrypted_key";
static const keyAttributesKey = "key_attributes";
SharedPreferences _preferences;
FlutterSecureStorage _secureStorage;
@ -46,38 +43,45 @@ class Configuration {
}
Future<KeyAttributes> generateAndSaveKey(String passphrase) async {
final key = CryptoUtil.getSecureRandomBytes(length: 32);
final kekSalt = CryptoUtil.getSecureRandomBytes(length: 32);
final kek = CryptoUtil.scrypt(utf8.encode(passphrase), kekSalt);
final kekHashSalt = CryptoUtil.getSecureRandomBytes(length: 32);
final kekHash = CryptoUtil.scrypt(kek, kekHashSalt);
// Create a master key
final key = CryptoUtil.generateMasterKey();
// Derive a key from the passphrase that will be used to encrypt and
// decrypt the master key
final kekSalt = CryptoUtil.getSaltToDeriveKey();
final kek = CryptoUtil.deriveKey(utf8.encode(passphrase), kekSalt);
// Encrypt the key with this derived key
final encryptedKeyData = await CryptoUtil.encrypt(key, key: kek);
// Hash the passphrase so that its correctness can be compared later
final passphraseHash = await CryptoUtil.hash(utf8.encode(passphrase));
final attributes = KeyAttributes(
Sodium.bin2base64(kekSalt),
Sodium.bin2base64(kekHash),
Sodium.bin2base64(kekHashSalt),
encryptedKeyData.encryptedData.base64,
encryptedKeyData.nonce.base64);
passphraseHash: passphraseHash,
kekSalt: Sodium.bin2base64(kekSalt),
encryptedKey: encryptedKeyData.encryptedData.base64,
keyDecryptionNonce: encryptedKeyData.nonce.base64,
);
await setKey(Sodium.bin2base64(key));
await setEncryptedKey(encryptedKeyData.encryptedData.base64);
await setKeyAttributes(attributes);
return attributes;
}
Future<void> decryptAndSaveKey(
String passphrase, KeyAttributes attributes) async {
final kek = CryptoUtil.scrypt(
utf8.encode(passphrase), base64.decode(attributes.kekSalt));
final calculatedKekHash =
CryptoUtil.scrypt(kek, base64.decode(attributes.kekHashSalt));
final passphraseBytes = utf8.encode(passphrase);
bool correctPassphrase =
Sodium.bin2base64(calculatedKekHash) == attributes.kekHash;
CryptoUtil.verifyHash(passphraseBytes, attributes.passphraseHash);
if (!correctPassphrase) {
throw Exception("Incorrect passphrase");
}
final kek = CryptoUtil.deriveKey(
passphraseBytes, Sodium.base642bin(attributes.kekSalt));
final key = await CryptoUtil.decrypt(
Sodium.base642bin(attributes.encryptedKey),
kek,
Sodium.base642bin(attributes.encryptedKeyIV));
Sodium.base642bin(attributes.keyDecryptionNonce));
await setKey(Sodium.bin2base64(key));
}
@ -142,31 +146,22 @@ class Configuration {
Future<void> setKeyAttributes(KeyAttributes attributes) async {
await _preferences.setString(
keyKekSalt, attributes == null ? null : attributes.kekSalt);
await _preferences.setString(
keyKekHash, attributes == null ? null : attributes.kekHash);
await _preferences.setString(
keyKekHashSalt, attributes == null ? null : attributes.kekHashSalt);
await _preferences.setString(
keyEncryptedKey, attributes == null ? null : attributes.encryptedKey);
await _preferences.setString(keyEncryptedKeyIV,
attributes == null ? null : attributes.encryptedKeyIV);
keyAttributesKey, attributes == null ? null : attributes.toJson());
}
KeyAttributes getKeyAttributes() {
if (_preferences.getString(keyEncryptedKey) == null) {
if (_preferences.getString(keyAttributesKey) == null) {
return null;
}
return KeyAttributes(
_preferences.getString(keyKekSalt),
_preferences.getString(keyKekHash),
_preferences.getString(keyKekHashSalt),
_preferences.getString(keyEncryptedKey),
_preferences.getString(keyEncryptedKeyIV));
return KeyAttributes.fromJson(_preferences.getString(keyAttributesKey));
}
Future<void> setEncryptedKey(String encryptedKey) async {
await _preferences.setString(encryptedKeyKey, encryptedKey);
}
String getEncryptedKey() {
return _preferences.getString(keyEncryptedKey);
return _preferences.getString(encryptedKeyKey);
}
Future<void> setKey(String key) async {

View file

@ -0,0 +1,8 @@
import 'dart:typed_data';
class DerivedKeyResult {
final Uint8List key;
final Uint8List salt;
DerivedKeyResult(this.key, this.salt);
}

View file

@ -1,43 +1,38 @@
import 'dart:convert';
class KeyAttributes {
final String passphraseHash;
final String kekSalt;
final String kekHash;
final String kekHashSalt;
final String encryptedKey;
final String encryptedKeyIV;
final String keyDecryptionNonce;
KeyAttributes(
KeyAttributes({
this.passphraseHash,
this.kekSalt,
this.kekHash,
this.kekHashSalt,
this.encryptedKey,
this.encryptedKeyIV,
);
this.keyDecryptionNonce,
});
KeyAttributes copyWith({
String passphraseHash,
String kekSalt,
String kekHash,
String kekHashSalt,
String encryptedKey,
String encryptedKeyIV,
String keyDecryptionNonce,
}) {
return KeyAttributes(
kekSalt ?? this.kekSalt,
kekHash ?? this.kekHash,
kekHashSalt ?? this.kekHashSalt,
encryptedKey ?? this.encryptedKey,
encryptedKeyIV ?? this.encryptedKeyIV,
passphraseHash: passphraseHash ?? this.passphraseHash,
kekSalt: kekSalt ?? this.kekSalt,
encryptedKey: encryptedKey ?? this.encryptedKey,
keyDecryptionNonce: keyDecryptionNonce ?? this.keyDecryptionNonce,
);
}
Map<String, dynamic> toMap() {
return {
'passphraseHash': passphraseHash,
'kekSalt': kekSalt,
'kekHash': kekHash,
'kekHashSalt': kekHashSalt,
'encryptedKey': encryptedKey,
'encryptedKeyIV': encryptedKeyIV,
'keyDecryptionNonce': keyDecryptionNonce,
};
}
@ -45,11 +40,10 @@ class KeyAttributes {
if (map == null) return null;
return KeyAttributes(
map['kekSalt'],
map['kekHash'],
map['kekHashSalt'],
map['encryptedKey'],
map['encryptedKeyIV'],
passphraseHash: map['passphraseHash'],
kekSalt: map['kekSalt'],
encryptedKey: map['encryptedKey'],
keyDecryptionNonce: map['keyDecryptionNonce'],
);
}
@ -60,6 +54,25 @@ class KeyAttributes {
@override
String toString() {
return 'KeyAttributes(kekSalt: $kekSalt, kekHash: $kekHash, kekHashSalt: $kekHashSalt, encryptedKey: $encryptedKey, encryptedKeyIV: $encryptedKeyIV)';
return 'KeyAttributes(passphraseHash: $passphraseHash, kekSalt: $kekSalt, encryptedKey: $encryptedKey, keyDecryptionNonce: $keyDecryptionNonce)';
}
@override
bool operator ==(Object o) {
if (identical(this, o)) return true;
return o is KeyAttributes &&
o.passphraseHash == passphraseHash &&
o.kekSalt == kekSalt &&
o.encryptedKey == encryptedKey &&
o.keyDecryptionNonce == keyDecryptionNonce;
}
@override
int get hashCode {
return passphraseHash.hashCode ^
kekSalt.hashCode ^
encryptedKey.hashCode ^
keyDecryptionNonce.hashCode;
}
}

View file

@ -294,18 +294,17 @@ class DebugWidget extends StatelessWidget {
Text("Encrypted Key", style: TextStyle(fontWeight: FontWeight.bold)),
Text(keyAttributes.encryptedKey),
Padding(padding: EdgeInsets.all(12)),
Text("Encrypted Key IV",
Text("Key Decryption Nonce",
style: TextStyle(fontWeight: FontWeight.bold)),
Text(keyAttributes.encryptedKeyIV),
Text(keyAttributes.keyDecryptionNonce),
Padding(padding: EdgeInsets.all(12)),
Text("KEK Salt", style: TextStyle(fontWeight: FontWeight.bold)),
Text(keyAttributes.kekSalt),
Padding(padding: EdgeInsets.all(12)),
Text("KEK Hash", style: TextStyle(fontWeight: FontWeight.bold)),
Text(keyAttributes.kekHash),
Text("Passphrase Hash",
style: TextStyle(fontWeight: FontWeight.bold)),
Text(keyAttributes.passphraseHash),
Padding(padding: EdgeInsets.all(12)),
Text("KEK Hash Salt", style: TextStyle(fontWeight: FontWeight.bold)),
Text(keyAttributes.kekHashSalt),
]),
),
actions: [

View file

@ -8,7 +8,6 @@ import 'package:logging/logging.dart';
import 'package:photos/models/encrypted_data_attributes.dart';
import 'package:photos/models/encrypted_file_attributes.dart';
import 'package:photos/models/encryption_attribute.dart';
import 'package:steel_crypt/steel_crypt.dart' as steel;
final int encryptionChunkSize = 4 * 1024 * 1024;
final int decryptionChunkSize =
@ -152,8 +151,32 @@ class CryptoUtil {
return Sodium.randombytesBuf(length);
}
static Uint8List scrypt(Uint8List plainText, Uint8List salt) {
return steel.PassCryptRaw.scrypt()
.hash(salt: salt, plain: plainText, len: 32);
static Uint8List generateMasterKey() {
return Sodium.cryptoKdfKeygen();
}
static Uint8List getSaltToDeriveKey() {
return Sodium.randombytesBuf(Sodium.cryptoPwhashSaltbytes);
}
static Uint8List deriveKey(Uint8List passphrase, Uint8List salt) {
return Sodium.cryptoPwhash(
Sodium.cryptoSecretboxKeybytes,
passphrase,
salt,
Sodium.cryptoPwhashOpslimitInteractive,
Sodium.cryptoPwhashMemlimitInteractive,
Sodium.cryptoPwhashAlgDefault);
}
static Future<String> hash(Uint8List passphrase) async {
return Sodium.cryptoPwhashStr(
passphrase,
Sodium.cryptoPwhashOpslimitSensitive,
Sodium.cryptoPwhashMemlimitSensitive);
}
static bool verifyHash(Uint8List passphrase, String hash) {
return Sodium.cryptoPwhashStrVerify(hash, passphrase) == 0;
}
}

View file

@ -22,13 +22,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.6.0"
asn1lib:
dependency: transitive
description:
name: asn1lib
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.5"
async:
dependency: transitive
description:
@ -443,13 +436,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
pc_steelcrypt:
dependency: transitive
description:
name: pc_steelcrypt
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1"
pedantic:
dependency: transitive
description:
@ -637,13 +623,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.3"
steel_crypt:
dependency: "direct main"
description:
name: steel_crypt
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.2+1"
stream_channel:
dependency: transitive
description:

View file

@ -62,7 +62,6 @@ dependencies:
uni_links: ^0.4.0
crisp: ^0.1.3
uuid: 2.2.2
steel_crypt: ^2.2.2+1
flutter_sodium: ^0.1.4
dev_dependencies: