Verify the correctness of the passphrase entered

This commit is contained in:
Vishnu Mohandas 2020-09-05 14:23:23 +05:30
parent a5d3305cbf
commit bc36bf8f5e
11 changed files with 161 additions and 31 deletions

View file

@ -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;
}

View file

@ -60,6 +60,8 @@ class FaceSearchManager {
file.deviceFolder,
file.creationTime,
file.modificationTime,
file.encryptedKey,
file.iv,
alternateTitle: getHEICFileNameForJPG(file)));
} catch (e) {
// Not available locally

View file

@ -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);
}

View 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(

View 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));
}

View file

@ -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)

View file

@ -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());
},

View file

@ -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));
}
}
}

View file

@ -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));

View file

@ -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:

View file

@ -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: