Browse Source

Replace scrypt with libsodium's abstractions

Vishnu Mohandas 4 years ago
parent
commit
fed7cda7f4

+ 33 - 38
lib/core/configuration.dart

@@ -21,11 +21,8 @@ class Configuration {
   static const hasOptedForE2EKey = "has_opted_for_e2e_encryption";
   static const hasOptedForE2EKey = "has_opted_for_e2e_encryption";
   static const foldersToBackUpKey = "folders_to_back_up";
   static const foldersToBackUpKey = "folders_to_back_up";
   static const keyKey = "key";
   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;
   SharedPreferences _preferences;
   FlutterSecureStorage _secureStorage;
   FlutterSecureStorage _secureStorage;
@@ -46,38 +43,45 @@ class Configuration {
   }
   }
 
 
   Future<KeyAttributes> generateAndSaveKey(String passphrase) async {
   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);
     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(
     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 setKey(Sodium.bin2base64(key));
+    await setEncryptedKey(encryptedKeyData.encryptedData.base64);
     await setKeyAttributes(attributes);
     await setKeyAttributes(attributes);
     return attributes;
     return attributes;
   }
   }
 
 
   Future<void> decryptAndSaveKey(
   Future<void> decryptAndSaveKey(
       String passphrase, KeyAttributes attributes) async {
       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 =
     bool correctPassphrase =
-        Sodium.bin2base64(calculatedKekHash) == attributes.kekHash;
+        CryptoUtil.verifyHash(passphraseBytes, attributes.passphraseHash);
     if (!correctPassphrase) {
     if (!correctPassphrase) {
       throw Exception("Incorrect passphrase");
       throw Exception("Incorrect passphrase");
     }
     }
+    final kek = CryptoUtil.deriveKey(
+        passphraseBytes, Sodium.base642bin(attributes.kekSalt));
     final key = await CryptoUtil.decrypt(
     final key = await CryptoUtil.decrypt(
         Sodium.base642bin(attributes.encryptedKey),
         Sodium.base642bin(attributes.encryptedKey),
         kek,
         kek,
-        Sodium.base642bin(attributes.encryptedKeyIV));
+        Sodium.base642bin(attributes.keyDecryptionNonce));
     await setKey(Sodium.bin2base64(key));
     await setKey(Sodium.bin2base64(key));
   }
   }
 
 
@@ -142,31 +146,22 @@ class Configuration {
 
 
   Future<void> setKeyAttributes(KeyAttributes attributes) async {
   Future<void> setKeyAttributes(KeyAttributes attributes) async {
     await _preferences.setString(
     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() {
   KeyAttributes getKeyAttributes() {
-    if (_preferences.getString(keyEncryptedKey) == null) {
+    if (_preferences.getString(keyAttributesKey) == null) {
       return 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() {
   String getEncryptedKey() {
-    return _preferences.getString(keyEncryptedKey);
+    return _preferences.getString(encryptedKeyKey);
   }
   }
 
 
   Future<void> setKey(String key) async {
   Future<void> setKey(String key) async {

+ 8 - 0
lib/models/derived_key_result.dart

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

+ 38 - 25
lib/models/key_attributes.dart

@@ -1,43 +1,38 @@
 import 'dart:convert';
 import 'dart:convert';
 
 
 class KeyAttributes {
 class KeyAttributes {
+  final String passphraseHash;
   final String kekSalt;
   final String kekSalt;
-  final String kekHash;
-  final String kekHashSalt;
   final String encryptedKey;
   final String encryptedKey;
-  final String encryptedKeyIV;
+  final String keyDecryptionNonce;
 
 
-  KeyAttributes(
+  KeyAttributes({
+    this.passphraseHash,
     this.kekSalt,
     this.kekSalt,
-    this.kekHash,
-    this.kekHashSalt,
     this.encryptedKey,
     this.encryptedKey,
-    this.encryptedKeyIV,
-  );
+    this.keyDecryptionNonce,
+  });
 
 
   KeyAttributes copyWith({
   KeyAttributes copyWith({
+    String passphraseHash,
     String kekSalt,
     String kekSalt,
-    String kekHash,
-    String kekHashSalt,
     String encryptedKey,
     String encryptedKey,
-    String encryptedKeyIV,
+    String keyDecryptionNonce,
   }) {
   }) {
     return KeyAttributes(
     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() {
   Map<String, dynamic> toMap() {
     return {
     return {
+      'passphraseHash': passphraseHash,
       'kekSalt': kekSalt,
       'kekSalt': kekSalt,
-      'kekHash': kekHash,
-      'kekHashSalt': kekHashSalt,
       'encryptedKey': encryptedKey,
       'encryptedKey': encryptedKey,
-      'encryptedKeyIV': encryptedKeyIV,
+      'keyDecryptionNonce': keyDecryptionNonce,
     };
     };
   }
   }
 
 
@@ -45,11 +40,10 @@ class KeyAttributes {
     if (map == null) return null;
     if (map == null) return null;
 
 
     return KeyAttributes(
     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
   @override
   String toString() {
   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;
   }
   }
 }
 }

+ 5 - 6
lib/ui/settings_page.dart

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

+ 27 - 4
lib/utils/crypto_util.dart

@@ -8,7 +8,6 @@ import 'package:logging/logging.dart';
 import 'package:photos/models/encrypted_data_attributes.dart';
 import 'package:photos/models/encrypted_data_attributes.dart';
 import 'package:photos/models/encrypted_file_attributes.dart';
 import 'package:photos/models/encrypted_file_attributes.dart';
 import 'package:photos/models/encryption_attribute.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 encryptionChunkSize = 4 * 1024 * 1024;
 final int decryptionChunkSize =
 final int decryptionChunkSize =
@@ -152,8 +151,32 @@ class CryptoUtil {
     return Sodium.randombytesBuf(length);
     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;
   }
   }
 }
 }

+ 0 - 21
pubspec.lock

@@ -22,13 +22,6 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "1.6.0"
     version: "1.6.0"
-  asn1lib:
-    dependency: transitive
-    description:
-      name: asn1lib
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "0.6.5"
   async:
   async:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -443,13 +436,6 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "1.0.2"
     version: "1.0.2"
-  pc_steelcrypt:
-    dependency: transitive
-    description:
-      name: pc_steelcrypt
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "1.1.1"
   pedantic:
   pedantic:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -637,13 +623,6 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "1.9.3"
     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:
   stream_channel:
     dependency: transitive
     dependency: transitive
     description:
     description:

+ 0 - 1
pubspec.yaml

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