Replace scrypt with libsodium's abstractions
This commit is contained in:
parent
b06190fbf2
commit
fed7cda7f4
7 changed files with 111 additions and 95 deletions
|
@ -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 {
|
||||
|
|
8
lib/models/derived_key_result.dart
Normal file
8
lib/models/derived_key_result.dart
Normal file
|
@ -0,0 +1,8 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
class DerivedKeyResult {
|
||||
final Uint8List key;
|
||||
final Uint8List salt;
|
||||
|
||||
DerivedKeyResult(this.key, this.salt);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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: [
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
21
pubspec.lock
21
pubspec.lock
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Add table
Reference in a new issue