Verify the correctness of the passphrase entered
This commit is contained in:
parent
a5d3305cbf
commit
bc36bf8f5e
11 changed files with 161 additions and 31 deletions
|
@ -1,12 +1,13 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'dart:io' as io;
|
||||
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:photos/utils/crypto_util.dart';
|
||||
import 'package:photos/models/key_attributes.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'package:photos/utils/crypto_util.dart';
|
||||
|
||||
class Configuration {
|
||||
Configuration._privateConstructor();
|
||||
static final Configuration instance = Configuration._privateConstructor();
|
||||
|
@ -18,8 +19,10 @@ class Configuration {
|
|||
static const hasOptedForE2EKey = "has_opted_for_e2e_encryption";
|
||||
static const keyKey = "key";
|
||||
static const keyEncryptedKey = "encrypted_key";
|
||||
|
||||
static final String iv = base64.encode(List.filled(16, 0));
|
||||
static const keyKekSalt = "kek_salt";
|
||||
static const keyKekHash = "kek_hash";
|
||||
static const keyKekHashSalt = "kek_hash_salt";
|
||||
static const keyEncryptedKeyIV = "encrypted_key_iv";
|
||||
|
||||
SharedPreferences _preferences;
|
||||
FlutterSecureStorage _secureStorage;
|
||||
|
@ -39,13 +42,35 @@ class Configuration {
|
|||
_key = await _secureStorage.read(key: keyKey);
|
||||
}
|
||||
|
||||
Future<void> generateAndSaveKey(String passphrase) async {
|
||||
Future<KeyAttributes> generateAndSaveKey(String passphrase) async {
|
||||
final key = CryptoUtil.getBase64EncodedSecureRandomString(length: 32);
|
||||
final kekSalt = CryptoUtil.getBase64EncodedSecureRandomString(length: 32);
|
||||
final kek =
|
||||
base64.encode(CryptoUtil.scrypt(passphrase, kekSalt, 32).codeUnits);
|
||||
final kekHashSalt =
|
||||
CryptoUtil.getBase64EncodedSecureRandomString(length: 32);
|
||||
final kekHash = CryptoUtil.scrypt(kek, kekHashSalt, 32);
|
||||
final iv = CryptoUtil.getBase64EncodedSecureRandomString(length: 32);
|
||||
final encryptedKey = CryptoUtil.encryptToBase64(key, kek, iv);
|
||||
final attributes =
|
||||
KeyAttributes(kekSalt, kekHash, kekHashSalt, encryptedKey, iv);
|
||||
await setKey(key);
|
||||
await setKeyAttributes(attributes);
|
||||
return attributes;
|
||||
}
|
||||
|
||||
Future<void> decryptAndSaveKey(
|
||||
String passphrase, KeyAttributes attributes) async {
|
||||
final kek = base64.encode(
|
||||
CryptoUtil.scrypt(passphrase, attributes.kekSalt, 32).codeUnits);
|
||||
bool correctPassphrase =
|
||||
CryptoUtil.compareHash(kek, attributes.kekHash, attributes.kekHashSalt);
|
||||
if (!correctPassphrase) {
|
||||
throw Exception("Incorrect passphrase");
|
||||
}
|
||||
final key = CryptoUtil.decryptFromBase64(
|
||||
attributes.encryptedKey, kek, attributes.encryptedKeyIV);
|
||||
await setKey(key);
|
||||
final hashedPassphrase = sha256.convert(passphrase.codeUnits);
|
||||
final encryptedKey = CryptoUtil.encryptToBase64(
|
||||
key, base64.encode(hashedPassphrase.bytes), iv);
|
||||
await setEncryptedKey(encryptedKey);
|
||||
}
|
||||
|
||||
String getEndpoint() {
|
||||
|
@ -96,8 +121,21 @@ class Configuration {
|
|||
// return _preferences.getBool(hasOptedForE2EKey);
|
||||
}
|
||||
|
||||
Future<void> setEncryptedKey(String encryptedKey) async {
|
||||
await _preferences.setString(keyEncryptedKey, encryptedKey);
|
||||
Future<void> setKeyAttributes(KeyAttributes attributes) async {
|
||||
await _preferences.setString(keyKekSalt, attributes.kekSalt);
|
||||
await _preferences.setString(keyKekHash, attributes.kekHash);
|
||||
await _preferences.setString(keyKekHashSalt, attributes.kekHashSalt);
|
||||
await _preferences.setString(keyEncryptedKey, attributes.encryptedKey);
|
||||
await _preferences.setString(keyEncryptedKeyIV, attributes.encryptedKeyIV);
|
||||
}
|
||||
|
||||
KeyAttributes getKeyAttributes() {
|
||||
return KeyAttributes(
|
||||
_preferences.getString(keyKekSalt),
|
||||
_preferences.getString(keyKekHash),
|
||||
_preferences.getString(keyKekHashSalt),
|
||||
_preferences.getString(keyEncryptedKey),
|
||||
_preferences.getString(keyEncryptedKeyIV));
|
||||
}
|
||||
|
||||
String getEncryptedKey() {
|
||||
|
@ -109,14 +147,6 @@ class Configuration {
|
|||
_key = key;
|
||||
}
|
||||
|
||||
Future<void> decryptEncryptedKey(String passphrase) async {
|
||||
final hashedPassphrase = sha256.convert(passphrase.codeUnits);
|
||||
final encryptedKey = getEncryptedKey();
|
||||
final key = CryptoUtil.decryptFromBase64(
|
||||
encryptedKey, base64.encode(hashedPassphrase.bytes), iv);
|
||||
await setKey(key);
|
||||
}
|
||||
|
||||
String getKey() {
|
||||
return _key;
|
||||
}
|
||||
|
|
|
@ -60,6 +60,8 @@ class FaceSearchManager {
|
|||
file.deviceFolder,
|
||||
file.creationTime,
|
||||
file.modificationTime,
|
||||
file.encryptedKey,
|
||||
file.iv,
|
||||
alternateTitle: getHEICFileNameForJPG(file)));
|
||||
} catch (e) {
|
||||
// Not available locally
|
||||
|
|
|
@ -70,8 +70,8 @@ class FolderSharingService {
|
|||
try {
|
||||
var existingPhoto =
|
||||
await FilesDB.instance.getMatchingRemoteFile(file.uploadedFileID);
|
||||
await FilesDB.instance.update(
|
||||
existingPhoto.generatedID, file.uploadedFileID, file.updationTime);
|
||||
await FilesDB.instance.update(existingPhoto.generatedID,
|
||||
file.uploadedFileID, file.updationTime, file.encryptedKey, file.iv);
|
||||
} catch (e) {
|
||||
await FilesDB.instance.insert(file);
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ class File {
|
|||
creationTime = json["creationTime"];
|
||||
modificationTime = json["modificationTime"];
|
||||
updationTime = json["updationTime"];
|
||||
encryptedKey = json["encryptedKey"];
|
||||
iv = json["iv"];
|
||||
}
|
||||
|
||||
static Future<File> fromAsset(
|
||||
|
|
60
lib/models/key_attributes.dart
Normal file
60
lib/models/key_attributes.dart
Normal file
|
@ -0,0 +1,60 @@
|
|||
import 'dart:convert';
|
||||
|
||||
class KeyAttributes {
|
||||
final String kekSalt;
|
||||
final String kekHash;
|
||||
final String kekHashSalt;
|
||||
final String encryptedKey;
|
||||
final String encryptedKeyIV;
|
||||
|
||||
KeyAttributes(
|
||||
this.kekSalt,
|
||||
this.kekHash,
|
||||
this.kekHashSalt,
|
||||
this.encryptedKey,
|
||||
this.encryptedKeyIV,
|
||||
);
|
||||
|
||||
KeyAttributes copyWith({
|
||||
String kekSalt,
|
||||
String kekHash,
|
||||
String kekHashSalt,
|
||||
String encryptedKey,
|
||||
String encryptedKeyIV,
|
||||
}) {
|
||||
return KeyAttributes(
|
||||
kekSalt ?? this.kekSalt,
|
||||
kekHash ?? this.kekHash,
|
||||
kekHashSalt ?? this.kekHashSalt,
|
||||
encryptedKey ?? this.encryptedKey,
|
||||
encryptedKeyIV ?? this.encryptedKeyIV,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'kekSalt': kekSalt,
|
||||
'kekHash': kekHash,
|
||||
'kekHashSalt': kekHashSalt,
|
||||
'encryptedKey': encryptedKey,
|
||||
'encryptedKeyIV': encryptedKeyIV,
|
||||
};
|
||||
}
|
||||
|
||||
factory KeyAttributes.fromMap(Map<String, dynamic> map) {
|
||||
if (map == null) return null;
|
||||
|
||||
return KeyAttributes(
|
||||
map['kekSalt'],
|
||||
map['kekHash'],
|
||||
map['kekHashSalt'],
|
||||
map['encryptedKey'],
|
||||
map['encryptedKeyIV'],
|
||||
);
|
||||
}
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory KeyAttributes.fromJson(String source) =>
|
||||
KeyAttributes.fromMap(json.decode(source));
|
||||
}
|
|
@ -71,8 +71,16 @@ class _PassphraseReentryPageState extends State<PassphraseReentryPage> {
|
|||
final dialog =
|
||||
createProgressDialog(context, "Please wait...");
|
||||
await dialog.show();
|
||||
await Configuration.instance
|
||||
.decryptEncryptedKey(_passphraseController.text);
|
||||
try {
|
||||
await Configuration.instance.decryptAndSaveKey(
|
||||
_passphraseController.text,
|
||||
Configuration.instance.getKeyAttributes());
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
showErrorDialog(context, "Incorrect passphrase",
|
||||
"Please try again.");
|
||||
return;
|
||||
}
|
||||
await dialog.hide();
|
||||
Bus.instance.fire(UserAuthenticatedEvent());
|
||||
Navigator.of(context)
|
||||
|
|
|
@ -350,7 +350,7 @@ class _PassphraseDialogState extends State<PassphraseDialog> {
|
|||
await Configuration.instance.generateAndSaveKey(_passphrase);
|
||||
await UserAuthenticator.instance.setEncryptedKeyOnServer();
|
||||
} else {
|
||||
await Configuration.instance.decryptEncryptedKey(_passphrase);
|
||||
await Configuration.instance.decryptAndSaveKey(_passphrase, null);
|
||||
}
|
||||
Bus.instance.fire(UserAuthenticatedEvent());
|
||||
},
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:photos/core/configuration.dart';
|
|||
import 'package:photos/core/event_bus.dart';
|
||||
|
||||
import 'package:photos/events/user_authenticated_event.dart';
|
||||
import 'package:photos/models/key_attributes.dart';
|
||||
import 'package:photos/ui/ott_verification_page.dart';
|
||||
import 'package:photos/ui/passphrase_entry_page.dart';
|
||||
import 'package:photos/ui/passphrase_reentry_page.dart';
|
||||
|
@ -103,7 +104,7 @@ class UserAuthenticator {
|
|||
.catchError((e) async {
|
||||
await dialog.hide();
|
||||
Configuration.instance.setKey(null);
|
||||
Configuration.instance.setEncryptedKey(null);
|
||||
Configuration.instance.setKeyAttributes(null);
|
||||
_logger.severe(e);
|
||||
showGenericErrorDialog(context);
|
||||
}).then((response) async {
|
||||
|
@ -113,7 +114,7 @@ class UserAuthenticator {
|
|||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
} else {
|
||||
Configuration.instance.setKey(null);
|
||||
Configuration.instance.setEncryptedKey(null);
|
||||
Configuration.instance.setKeyAttributes(null);
|
||||
showGenericErrorDialog(context);
|
||||
}
|
||||
});
|
||||
|
@ -178,9 +179,10 @@ class UserAuthenticator {
|
|||
void _saveConfiguration(Response response) {
|
||||
Configuration.instance.setUserID(response.data["id"]);
|
||||
Configuration.instance.setToken(response.data["token"]);
|
||||
final String encryptedKey = response.data["encryptedKey"];
|
||||
if (encryptedKey != null && encryptedKey.isNotEmpty) {
|
||||
Configuration.instance.setEncryptedKey(encryptedKey);
|
||||
final keyAttributes = response.data["encryptedKey"];
|
||||
if (keyAttributes != null) {
|
||||
Configuration.instance
|
||||
.setKeyAttributes(KeyAttributes.fromMap(keyAttributes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import 'package:encrypt/encrypt.dart';
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:photos/core/configuration.dart';
|
||||
import 'package:steel_crypt/steel_crypt.dart' as steel;
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class CryptoUtil {
|
||||
|
@ -14,6 +15,16 @@ class CryptoUtil {
|
|||
return SecureRandom(length).base64;
|
||||
}
|
||||
|
||||
static String scrypt(String passphrase, String salt, int length) {
|
||||
return steel.PassCrypt.scrypt()
|
||||
.hash(salt: salt, inp: passphrase, len: length);
|
||||
}
|
||||
|
||||
static bool compareHash(String plainText, String hash, String salt) {
|
||||
return steel.PassCrypt.scrypt()
|
||||
.check(plain: plainText, hashed: hash, salt: salt);
|
||||
}
|
||||
|
||||
static String encryptToBase64(
|
||||
String plainText, String base64Key, String base64IV) {
|
||||
final encrypter = AES(Key.fromBase64(base64Key));
|
||||
|
|
14
pubspec.lock
14
pubspec.lock
|
@ -471,6 +471,13 @@ 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:
|
||||
|
@ -665,6 +672,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.9.5"
|
||||
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:
|
||||
|
|
|
@ -65,6 +65,7 @@ dependencies:
|
|||
flutter_svg: ^0.18.1
|
||||
crisp: ^0.1.3
|
||||
uuid: 2.2.2
|
||||
steel_crypt: ^2.2.2+1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
Loading…
Add table
Reference in a new issue