|
@@ -1,7 +1,9 @@
|
|
|
|
+import 'dart:convert';
|
|
import 'dart:io' as io;
|
|
import 'dart:io' as io;
|
|
import 'dart:typed_data';
|
|
import 'dart:typed_data';
|
|
|
|
|
|
import 'package:computer/computer.dart';
|
|
import 'package:computer/computer.dart';
|
|
|
|
+import 'package:ente_auth/core/errors.dart';
|
|
import 'package:ente_auth/models/derived_key_result.dart';
|
|
import 'package:ente_auth/models/derived_key_result.dart';
|
|
import 'package:ente_auth/models/encryption_result.dart';
|
|
import 'package:ente_auth/models/encryption_result.dart';
|
|
import 'package:ente_auth/utils/device_info.dart';
|
|
import 'package:ente_auth/utils/device_info.dart';
|
|
@@ -11,6 +13,10 @@ import 'package:logging/logging.dart';
|
|
const int encryptionChunkSize = 4 * 1024 * 1024;
|
|
const int encryptionChunkSize = 4 * 1024 * 1024;
|
|
final int decryptionChunkSize =
|
|
final int decryptionChunkSize =
|
|
encryptionChunkSize + Sodium.cryptoSecretstreamXchacha20poly1305Abytes;
|
|
encryptionChunkSize + Sodium.cryptoSecretstreamXchacha20poly1305Abytes;
|
|
|
|
+const int hashChunkSize = 4 * 1024 * 1024;
|
|
|
|
+const int loginSubKeyLen = 32;
|
|
|
|
+const int loginSubKeyId = 1;
|
|
|
|
+const String loginSubKeyContext = "loginctx";
|
|
|
|
|
|
Uint8List cryptoSecretboxEasy(Map<String, dynamic> args) {
|
|
Uint8List cryptoSecretboxEasy(Map<String, dynamic> args) {
|
|
return Sodium.cryptoSecretboxEasy(args["source"], args["nonce"], args["key"]);
|
|
return Sodium.cryptoSecretboxEasy(args["source"], args["nonce"], args["key"]);
|
|
@@ -31,35 +37,47 @@ Uint8List cryptoPwHash(Map<String, dynamic> args) {
|
|
args["salt"],
|
|
args["salt"],
|
|
args["opsLimit"],
|
|
args["opsLimit"],
|
|
args["memLimit"],
|
|
args["memLimit"],
|
|
- Sodium.cryptoPwhashAlgDefault,
|
|
|
|
|
|
+ Sodium.cryptoPwhashAlgArgon2id13,
|
|
);
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
-Uint8List cryptoGenericHash(Map<String, dynamic> args) {
|
|
|
|
|
|
+Uint8List cryptoKdfDeriveFromKey(
|
|
|
|
+ Map<String, dynamic> args,
|
|
|
|
+ ) {
|
|
|
|
+ return Sodium.cryptoKdfDeriveFromKey(
|
|
|
|
+ args["subkeyLen"],
|
|
|
|
+ args["subkeyId"],
|
|
|
|
+ args["context"],
|
|
|
|
+ args["key"],
|
|
|
|
+ );
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// Returns the hash for a given file, chunking it in batches of hashChunkSize
|
|
|
|
+Future<Uint8List> cryptoGenericHash(Map<String, dynamic> args) async {
|
|
final sourceFile = io.File(args["sourceFilePath"]);
|
|
final sourceFile = io.File(args["sourceFilePath"]);
|
|
- final sourceFileLength = sourceFile.lengthSync();
|
|
|
|
|
|
+ final sourceFileLength = await sourceFile.length();
|
|
final inputFile = sourceFile.openSync(mode: io.FileMode.read);
|
|
final inputFile = sourceFile.openSync(mode: io.FileMode.read);
|
|
final state =
|
|
final state =
|
|
- Sodium.cryptoGenerichashInit(null, Sodium.cryptoGenerichashBytesMax);
|
|
|
|
|
|
+ Sodium.cryptoGenerichashInit(null, Sodium.cryptoGenerichashBytesMax);
|
|
var bytesRead = 0;
|
|
var bytesRead = 0;
|
|
bool isDone = false;
|
|
bool isDone = false;
|
|
while (!isDone) {
|
|
while (!isDone) {
|
|
- var chunkSize = encryptionChunkSize;
|
|
|
|
|
|
+ var chunkSize = hashChunkSize;
|
|
if (bytesRead + chunkSize >= sourceFileLength) {
|
|
if (bytesRead + chunkSize >= sourceFileLength) {
|
|
chunkSize = sourceFileLength - bytesRead;
|
|
chunkSize = sourceFileLength - bytesRead;
|
|
isDone = true;
|
|
isDone = true;
|
|
}
|
|
}
|
|
- final buffer = inputFile.readSync(chunkSize);
|
|
|
|
|
|
+ final buffer = await inputFile.read(chunkSize);
|
|
bytesRead += chunkSize;
|
|
bytesRead += chunkSize;
|
|
Sodium.cryptoGenerichashUpdate(state, buffer);
|
|
Sodium.cryptoGenerichashUpdate(state, buffer);
|
|
}
|
|
}
|
|
- inputFile.closeSync();
|
|
|
|
|
|
+ await inputFile.close();
|
|
return Sodium.cryptoGenerichashFinal(state, Sodium.cryptoGenerichashBytesMax);
|
|
return Sodium.cryptoGenerichashFinal(state, Sodium.cryptoGenerichashBytesMax);
|
|
}
|
|
}
|
|
|
|
|
|
EncryptionResult chachaEncryptData(Map<String, dynamic> args) {
|
|
EncryptionResult chachaEncryptData(Map<String, dynamic> args) {
|
|
final initPushResult =
|
|
final initPushResult =
|
|
- Sodium.cryptoSecretstreamXchacha20poly1305InitPush(args["key"]);
|
|
|
|
|
|
+ Sodium.cryptoSecretstreamXchacha20poly1305InitPush(args["key"]);
|
|
final encryptedData = Sodium.cryptoSecretstreamXchacha20poly1305Push(
|
|
final encryptedData = Sodium.cryptoSecretstreamXchacha20poly1305Push(
|
|
initPushResult.state,
|
|
initPushResult.state,
|
|
args["source"],
|
|
args["source"],
|
|
@@ -72,6 +90,7 @@ EncryptionResult chachaEncryptData(Map<String, dynamic> args) {
|
|
);
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// Encrypts a given file, in chunks of encryptionChunkSize
|
|
Future<EncryptionResult> chachaEncryptFile(Map<String, dynamic> args) async {
|
|
Future<EncryptionResult> chachaEncryptFile(Map<String, dynamic> args) async {
|
|
final encryptionStartTime = DateTime.now().millisecondsSinceEpoch;
|
|
final encryptionStartTime = DateTime.now().millisecondsSinceEpoch;
|
|
final logger = Logger("ChaChaEncrypt");
|
|
final logger = Logger("ChaChaEncrypt");
|
|
@@ -83,7 +102,7 @@ Future<EncryptionResult> chachaEncryptFile(Map<String, dynamic> args) async {
|
|
final inputFile = sourceFile.openSync(mode: io.FileMode.read);
|
|
final inputFile = sourceFile.openSync(mode: io.FileMode.read);
|
|
final key = args["key"] ?? Sodium.cryptoSecretstreamXchacha20poly1305Keygen();
|
|
final key = args["key"] ?? Sodium.cryptoSecretstreamXchacha20poly1305Keygen();
|
|
final initPushResult =
|
|
final initPushResult =
|
|
- Sodium.cryptoSecretstreamXchacha20poly1305InitPush(key);
|
|
|
|
|
|
+ Sodium.cryptoSecretstreamXchacha20poly1305InitPush(key);
|
|
var bytesRead = 0;
|
|
var bytesRead = 0;
|
|
var tag = Sodium.cryptoSecretstreamXchacha20poly1305TagMessage;
|
|
var tag = Sodium.cryptoSecretstreamXchacha20poly1305TagMessage;
|
|
while (tag != Sodium.cryptoSecretstreamXchacha20poly1305TagFinal) {
|
|
while (tag != Sodium.cryptoSecretstreamXchacha20poly1305TagFinal) {
|
|
@@ -92,7 +111,7 @@ Future<EncryptionResult> chachaEncryptFile(Map<String, dynamic> args) async {
|
|
chunkSize = sourceFileLength - bytesRead;
|
|
chunkSize = sourceFileLength - bytesRead;
|
|
tag = Sodium.cryptoSecretstreamXchacha20poly1305TagFinal;
|
|
tag = Sodium.cryptoSecretstreamXchacha20poly1305TagFinal;
|
|
}
|
|
}
|
|
- final buffer = inputFile.readSync(chunkSize);
|
|
|
|
|
|
+ final buffer = await inputFile.read(chunkSize);
|
|
bytesRead += chunkSize;
|
|
bytesRead += chunkSize;
|
|
final encryptedData = Sodium.cryptoSecretstreamXchacha20poly1305Push(
|
|
final encryptedData = Sodium.cryptoSecretstreamXchacha20poly1305Push(
|
|
initPushResult.state,
|
|
initPushResult.state,
|
|
@@ -102,7 +121,7 @@ Future<EncryptionResult> chachaEncryptFile(Map<String, dynamic> args) async {
|
|
);
|
|
);
|
|
await destinationFile.writeAsBytes(encryptedData, mode: io.FileMode.append);
|
|
await destinationFile.writeAsBytes(encryptedData, mode: io.FileMode.append);
|
|
}
|
|
}
|
|
- inputFile.closeSync();
|
|
|
|
|
|
+ await inputFile.close();
|
|
|
|
|
|
logger.info(
|
|
logger.info(
|
|
"Encryption time: " +
|
|
"Encryption time: " +
|
|
@@ -134,10 +153,10 @@ Future<void> chachaDecryptFile(Map<String, dynamic> args) async {
|
|
if (bytesRead + chunkSize >= sourceFileLength) {
|
|
if (bytesRead + chunkSize >= sourceFileLength) {
|
|
chunkSize = sourceFileLength - bytesRead;
|
|
chunkSize = sourceFileLength - bytesRead;
|
|
}
|
|
}
|
|
- final buffer = inputFile.readSync(chunkSize);
|
|
|
|
|
|
+ final buffer = await inputFile.read(chunkSize);
|
|
bytesRead += chunkSize;
|
|
bytesRead += chunkSize;
|
|
final pullResult =
|
|
final pullResult =
|
|
- Sodium.cryptoSecretstreamXchacha20poly1305Pull(pullState, buffer, null);
|
|
|
|
|
|
+ Sodium.cryptoSecretstreamXchacha20poly1305Pull(pullState, buffer, null);
|
|
await destinationFile.writeAsBytes(pullResult.m, mode: io.FileMode.append);
|
|
await destinationFile.writeAsBytes(pullResult.m, mode: io.FileMode.append);
|
|
tag = pullResult.tag;
|
|
tag = pullResult.tag;
|
|
}
|
|
}
|
|
@@ -164,13 +183,43 @@ Uint8List chachaDecryptData(Map<String, dynamic> args) {
|
|
}
|
|
}
|
|
|
|
|
|
class CryptoUtil {
|
|
class CryptoUtil {
|
|
- static final Computer _computer = Computer();
|
|
|
|
|
|
+ // Note: workers are turned on during app startup.
|
|
|
|
+ static final Computer _computer = Computer.shared();
|
|
|
|
|
|
static init() {
|
|
static init() {
|
|
- _computer.turnOn(workersCount: 4);
|
|
|
|
- // Sodium.init();
|
|
|
|
|
|
+ Sodium.init();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ static Uint8List base642bin(String b64, {
|
|
|
|
+ String? ignore,
|
|
|
|
+ int variant = Sodium.base64VariantOriginal,
|
|
|
|
+ }) {
|
|
|
|
+ return Sodium.base642bin(b64, ignore: ignore, variant: variant);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ static String bin2base64(Uint8List bin, {
|
|
|
|
+ bool urlSafe = false,
|
|
|
|
+ }) {
|
|
|
|
+ return Sodium.bin2base64(
|
|
|
|
+ bin,
|
|
|
|
+ variant:
|
|
|
|
+ urlSafe ? Sodium.base64VariantUrlsafe : Sodium.base64VariantOriginal,
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ static String bin2hex(Uint8List bin) {
|
|
|
|
+ return Sodium.bin2hex(bin);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ static Uint8List hex2bin(String hex) {
|
|
|
|
+ return Sodium.hex2bin(hex);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // Encrypts the given source, with the given key and a randomly generated
|
|
|
|
+ // nonce, using XSalsa20 (w Poly1305 MAC).
|
|
|
|
+ // This function runs on the same thread as the caller, so should be used only
|
|
|
|
+ // for small amounts of data where thread switching can result in a degraded
|
|
|
|
+ // user experience
|
|
static EncryptionResult encryptSync(Uint8List source, Uint8List key) {
|
|
static EncryptionResult encryptSync(Uint8List source, Uint8List key) {
|
|
final nonce = Sodium.randombytesBuf(Sodium.cryptoSecretboxNoncebytes);
|
|
final nonce = Sodium.randombytesBuf(Sodium.cryptoSecretboxNoncebytes);
|
|
|
|
|
|
@@ -186,24 +235,30 @@ class CryptoUtil {
|
|
);
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
- static Future<Uint8List> decrypt(
|
|
|
|
- Uint8List cipher,
|
|
|
|
- Uint8List key,
|
|
|
|
- Uint8List nonce,
|
|
|
|
- ) async {
|
|
|
|
|
|
+ // Decrypts the given cipher, with the given key and nonce using XSalsa20
|
|
|
|
+ // (w Poly1305 MAC).
|
|
|
|
+ static Future<Uint8List> decrypt(Uint8List cipher,
|
|
|
|
+ Uint8List key,
|
|
|
|
+ Uint8List nonce,) async {
|
|
final args = <String, dynamic>{};
|
|
final args = <String, dynamic>{};
|
|
args["cipher"] = cipher;
|
|
args["cipher"] = cipher;
|
|
args["nonce"] = nonce;
|
|
args["nonce"] = nonce;
|
|
args["key"] = key;
|
|
args["key"] = key;
|
|
- return _computer.compute(cryptoSecretboxOpenEasy, param: args);
|
|
|
|
|
|
+ return _computer.compute(
|
|
|
|
+ cryptoSecretboxOpenEasy,
|
|
|
|
+ param: args,
|
|
|
|
+ taskName: "decrypt",
|
|
|
|
+ );
|
|
}
|
|
}
|
|
|
|
|
|
- static Uint8List decryptSync(
|
|
|
|
- Uint8List cipher,
|
|
|
|
- Uint8List? key,
|
|
|
|
- Uint8List nonce,
|
|
|
|
- ) {
|
|
|
|
- assert(key != null, "key can not be null");
|
|
|
|
|
|
+ // Decrypts the given cipher, with the given key and nonce using XSalsa20
|
|
|
|
+ // (w Poly1305 MAC).
|
|
|
|
+ // This function runs on the same thread as the caller, so should be used only
|
|
|
|
+ // for small amounts of data where thread switching can result in a degraded
|
|
|
|
+ // user experience
|
|
|
|
+ static Uint8List decryptSync(Uint8List cipher,
|
|
|
|
+ Uint8List key,
|
|
|
|
+ Uint8List nonce,) {
|
|
final args = <String, dynamic>{};
|
|
final args = <String, dynamic>{};
|
|
args["cipher"] = cipher;
|
|
args["cipher"] = cipher;
|
|
args["nonce"] = nonce;
|
|
args["nonce"] = nonce;
|
|
@@ -211,94 +266,130 @@ class CryptoUtil {
|
|
return cryptoSecretboxOpenEasy(args);
|
|
return cryptoSecretboxOpenEasy(args);
|
|
}
|
|
}
|
|
|
|
|
|
- static Future<EncryptionResult> encryptChaCha(
|
|
|
|
- Uint8List source,
|
|
|
|
- Uint8List key,
|
|
|
|
- ) async {
|
|
|
|
|
|
+ // Encrypts the given source, with the given key and a randomly generated
|
|
|
|
+ // nonce, using XChaCha20 (w Poly1305 MAC).
|
|
|
|
+ // This function runs on the isolate pool held by `_computer`.
|
|
|
|
+ // TODO: Remove "ChaCha", an implementation detail from the function name
|
|
|
|
+ static Future<EncryptionResult> encryptChaCha(Uint8List source,
|
|
|
|
+ Uint8List key,) async {
|
|
final args = <String, dynamic>{};
|
|
final args = <String, dynamic>{};
|
|
args["source"] = source;
|
|
args["source"] = source;
|
|
args["key"] = key;
|
|
args["key"] = key;
|
|
- return _computer.compute(chachaEncryptData, param: args);
|
|
|
|
|
|
+ return _computer.compute(
|
|
|
|
+ chachaEncryptData,
|
|
|
|
+ param: args,
|
|
|
|
+ taskName: "encryptChaCha",
|
|
|
|
+ );
|
|
}
|
|
}
|
|
|
|
|
|
- static Future<Uint8List> decryptChaCha(
|
|
|
|
- Uint8List source,
|
|
|
|
- Uint8List key,
|
|
|
|
- Uint8List header,
|
|
|
|
- ) async {
|
|
|
|
|
|
+ // Decrypts the given source, with the given key and header using XChaCha20
|
|
|
|
+ // (w Poly1305 MAC).
|
|
|
|
+ // TODO: Remove "ChaCha", an implementation detail from the function name
|
|
|
|
+ static Future<Uint8List> decryptChaCha(Uint8List source,
|
|
|
|
+ Uint8List key,
|
|
|
|
+ Uint8List header,) async {
|
|
final args = <String, dynamic>{};
|
|
final args = <String, dynamic>{};
|
|
args["source"] = source;
|
|
args["source"] = source;
|
|
args["key"] = key;
|
|
args["key"] = key;
|
|
args["header"] = header;
|
|
args["header"] = header;
|
|
- return _computer.compute(chachaDecryptData, param: args);
|
|
|
|
|
|
+ return _computer.compute(
|
|
|
|
+ chachaDecryptData,
|
|
|
|
+ param: args,
|
|
|
|
+ taskName: "decryptChaCha",
|
|
|
|
+ );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // Encrypts the file at sourceFilePath, with the key (if provided) and a
|
|
|
|
+ // randomly generated nonce using XChaCha20 (w Poly1305 MAC), and writes it
|
|
|
|
+ // to the destinationFilePath.
|
|
|
|
+ // If a key is not provided, one is generated and returned.
|
|
static Future<EncryptionResult> encryptFile(
|
|
static Future<EncryptionResult> encryptFile(
|
|
- String sourceFilePath,
|
|
|
|
- String destinationFilePath, {
|
|
|
|
- Uint8List? key,
|
|
|
|
- }) {
|
|
|
|
|
|
+ String sourceFilePath,
|
|
|
|
+ String destinationFilePath, {
|
|
|
|
+ Uint8List? key,
|
|
|
|
+ }) {
|
|
final args = <String, dynamic>{};
|
|
final args = <String, dynamic>{};
|
|
args["sourceFilePath"] = sourceFilePath;
|
|
args["sourceFilePath"] = sourceFilePath;
|
|
args["destinationFilePath"] = destinationFilePath;
|
|
args["destinationFilePath"] = destinationFilePath;
|
|
args["key"] = key;
|
|
args["key"] = key;
|
|
- return _computer.compute(chachaEncryptFile, param: args);
|
|
|
|
|
|
+ return _computer.compute(
|
|
|
|
+ chachaEncryptFile,
|
|
|
|
+ param: args,
|
|
|
|
+ taskName: "encryptFile",
|
|
|
|
+ );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // Decrypts the file at sourceFilePath, with the given key and header using
|
|
|
|
+ // XChaCha20 (w Poly1305 MAC), and writes it to the destinationFilePath.
|
|
static Future<void> decryptFile(
|
|
static Future<void> decryptFile(
|
|
- String sourceFilePath,
|
|
|
|
- String destinationFilePath,
|
|
|
|
- Uint8List header,
|
|
|
|
- Uint8List key,
|
|
|
|
- ) {
|
|
|
|
|
|
+ String sourceFilePath,
|
|
|
|
+ String destinationFilePath,
|
|
|
|
+ Uint8List header,
|
|
|
|
+ Uint8List key,) {
|
|
final args = <String, dynamic>{};
|
|
final args = <String, dynamic>{};
|
|
args["sourceFilePath"] = sourceFilePath;
|
|
args["sourceFilePath"] = sourceFilePath;
|
|
args["destinationFilePath"] = destinationFilePath;
|
|
args["destinationFilePath"] = destinationFilePath;
|
|
args["header"] = header;
|
|
args["header"] = header;
|
|
args["key"] = key;
|
|
args["key"] = key;
|
|
- return _computer.compute(chachaDecryptFile, param: args);
|
|
|
|
|
|
+ return _computer.compute(
|
|
|
|
+ chachaDecryptFile,
|
|
|
|
+ param: args,
|
|
|
|
+ taskName: "decryptFile",
|
|
|
|
+ );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // Generates and returns a 256-bit key.
|
|
static Uint8List generateKey() {
|
|
static Uint8List generateKey() {
|
|
return Sodium.cryptoSecretboxKeygen();
|
|
return Sodium.cryptoSecretboxKeygen();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // Generates and returns a random byte buffer of length
|
|
|
|
+ // crypto_pwhash_SALTBYTES (16)
|
|
static Uint8List getSaltToDeriveKey() {
|
|
static Uint8List getSaltToDeriveKey() {
|
|
return Sodium.randombytesBuf(Sodium.cryptoPwhashSaltbytes);
|
|
return Sodium.randombytesBuf(Sodium.cryptoPwhashSaltbytes);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // Generates and returns a secret key and the corresponding public key.
|
|
static Future<KeyPair> generateKeyPair() async {
|
|
static Future<KeyPair> generateKeyPair() async {
|
|
return Sodium.cryptoBoxKeypair();
|
|
return Sodium.cryptoBoxKeypair();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // Decrypts the input using the given publicKey-secretKey pair
|
|
static Uint8List openSealSync(
|
|
static Uint8List openSealSync(
|
|
- Uint8List input,
|
|
|
|
- Uint8List publicKey,
|
|
|
|
- Uint8List secretKey,
|
|
|
|
- ) {
|
|
|
|
|
|
+ Uint8List input,
|
|
|
|
+ Uint8List publicKey,
|
|
|
|
+ Uint8List secretKey,
|
|
|
|
+ ) {
|
|
return Sodium.cryptoBoxSealOpen(input, publicKey, secretKey);
|
|
return Sodium.cryptoBoxSealOpen(input, publicKey, secretKey);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // Encrypts the input using the given publicKey
|
|
static Uint8List sealSync(Uint8List input, Uint8List publicKey) {
|
|
static Uint8List sealSync(Uint8List input, Uint8List publicKey) {
|
|
return Sodium.cryptoBoxSeal(input, publicKey);
|
|
return Sodium.cryptoBoxSeal(input, publicKey);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // Derives a key for a given password and salt using Argon2id, v1.3.
|
|
|
|
+ // The function first attempts to derive a key with both memLimit and opsLimit
|
|
|
|
+ // set to their Sensitive variants.
|
|
|
|
+ // If this fails, say on a device with insufficient RAM, we retry by halving
|
|
|
|
+ // the memLimit and doubling the opsLimit, while ensuring that we stay within
|
|
|
|
+ // the min and max limits for both parameters.
|
|
|
|
+ // At all points, we ensure that the product of these two variables (the area
|
|
|
|
+ // under the graph that determines the amount of work required) is a constant.
|
|
static Future<DerivedKeyResult> deriveSensitiveKey(
|
|
static Future<DerivedKeyResult> deriveSensitiveKey(
|
|
- Uint8List password,
|
|
|
|
- Uint8List salt,
|
|
|
|
- ) async {
|
|
|
|
|
|
+ Uint8List password,
|
|
|
|
+ Uint8List salt,
|
|
|
|
+ ) async {
|
|
final logger = Logger("pwhash");
|
|
final logger = Logger("pwhash");
|
|
- // Default with 1 GB mem and 4 ops limit
|
|
|
|
int memLimit = Sodium.cryptoPwhashMemlimitSensitive;
|
|
int memLimit = Sodium.cryptoPwhashMemlimitSensitive;
|
|
int opsLimit = Sodium.cryptoPwhashOpslimitSensitive;
|
|
int opsLimit = Sodium.cryptoPwhashOpslimitSensitive;
|
|
-
|
|
|
|
if (await isLowSpecDevice()) {
|
|
if (await isLowSpecDevice()) {
|
|
logger.info("low spec device detected");
|
|
logger.info("low spec device detected");
|
|
// When sensitive memLimit (1 GB) is used, on low spec device the OS might
|
|
// When sensitive memLimit (1 GB) is used, on low spec device the OS might
|
|
- // kill the app with OOM. To avoid that, start with 256 MB and
|
|
|
|
|
|
+ // kill the app with OOM. To avoid that, start with 256 MB and
|
|
// corresponding ops limit (16).
|
|
// corresponding ops limit (16).
|
|
// This ensures that the product of these two variables
|
|
// This ensures that the product of these two variables
|
|
- // (the area under the graph that determines the amount of work required)
|
|
|
|
|
|
+ // (the area under the graph that determines the amount of work required)
|
|
// stays the same
|
|
// stays the same
|
|
// SODIUM_CRYPTO_PWHASH_MEMLIMIT_SENSITIVE: 1073741824
|
|
// SODIUM_CRYPTO_PWHASH_MEMLIMIT_SENSITIVE: 1073741824
|
|
// SODIUM_CRYPTO_PWHASH_MEMLIMIT_MODERATE: 268435456
|
|
// SODIUM_CRYPTO_PWHASH_MEMLIMIT_MODERATE: 268435456
|
|
@@ -315,11 +406,8 @@ class CryptoUtil {
|
|
key = await deriveKey(password, salt, memLimit, opsLimit);
|
|
key = await deriveKey(password, salt, memLimit, opsLimit);
|
|
return DerivedKeyResult(key, memLimit, opsLimit);
|
|
return DerivedKeyResult(key, memLimit, opsLimit);
|
|
} catch (e, s) {
|
|
} catch (e, s) {
|
|
- logger.severe(
|
|
|
|
- "failed to derive memLimit: $memLimit and opsLimit: $opsLimit",
|
|
|
|
- e,
|
|
|
|
- s,
|
|
|
|
- );
|
|
|
|
|
|
+ logger.warning(
|
|
|
|
+ "failed to deriveKey mem: $memLimit, ops: $opsLimit", e, s,);
|
|
}
|
|
}
|
|
memLimit = (memLimit / 2).round();
|
|
memLimit = (memLimit / 2).round();
|
|
opsLimit = opsLimit * 2;
|
|
opsLimit = opsLimit * 2;
|
|
@@ -327,29 +415,76 @@ class CryptoUtil {
|
|
throw UnsupportedError("Cannot perform this operation on this device");
|
|
throw UnsupportedError("Cannot perform this operation on this device");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // Derives a key for the given password and salt, using Argon2id, v1.3
|
|
|
|
+ // with memory and ops limit hardcoded to their Interactive variants
|
|
|
|
+ // NOTE: This is only used while setting passwords for shared links, as an
|
|
|
|
+ // extra layer of authentication (atop the access token and collection key).
|
|
|
|
+ // More details @ https://ente.io/blog/building-shareable-links/
|
|
|
|
+ static Future<DerivedKeyResult> deriveInteractiveKey(
|
|
|
|
+ Uint8List password,
|
|
|
|
+ Uint8List salt,
|
|
|
|
+ ) async {
|
|
|
|
+ final int memLimit = Sodium.cryptoPwhashMemlimitInteractive;
|
|
|
|
+ final int opsLimit = Sodium.cryptoPwhashOpslimitInteractive;
|
|
|
|
+ final key = await deriveKey(password, salt, memLimit, opsLimit);
|
|
|
|
+ return DerivedKeyResult(key, memLimit, opsLimit);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Derives a key for a given password, salt, memLimit and opsLimit using
|
|
|
|
+ // Argon2id, v1.3.
|
|
static Future<Uint8List> deriveKey(
|
|
static Future<Uint8List> deriveKey(
|
|
- Uint8List password,
|
|
|
|
- Uint8List salt,
|
|
|
|
- int memLimit,
|
|
|
|
- int opsLimit,
|
|
|
|
- ) {
|
|
|
|
- return _computer.compute(
|
|
|
|
- cryptoPwHash,
|
|
|
|
|
|
+ Uint8List password,
|
|
|
|
+ Uint8List salt,
|
|
|
|
+ int memLimit,
|
|
|
|
+ int opsLimit,
|
|
|
|
+ ) {
|
|
|
|
+ try {
|
|
|
|
+ return _computer.compute(
|
|
|
|
+ cryptoPwHash,
|
|
|
|
+ param: {
|
|
|
|
+ "password": password,
|
|
|
|
+ "salt": salt,
|
|
|
|
+ "memLimit": memLimit,
|
|
|
|
+ "opsLimit": opsLimit,
|
|
|
|
+ },
|
|
|
|
+ taskName: "deriveKey",
|
|
|
|
+ );
|
|
|
|
+ } catch(e,s) {
|
|
|
|
+ final String errMessage = 'failed to deriveKey memLimit: $memLimit and '
|
|
|
|
+ 'opsLimit: $opsLimit';
|
|
|
|
+ Logger("CryptoUtilDeriveKey").warning(errMessage, e, s);
|
|
|
|
+ throw KeyDerivationError();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // derives a Login key as subKey from the given key by applying KDF
|
|
|
|
+ // (Key Derivation Function) with the `loginSubKeyId` and
|
|
|
|
+ // `loginSubKeyLen` and `loginSubKeyContext` as context
|
|
|
|
+ static Future<Uint8List> deriveLoginKey(
|
|
|
|
+ Uint8List key,
|
|
|
|
+ ) async {
|
|
|
|
+ final Uint8List derivedKey = await _computer.compute(
|
|
|
|
+ cryptoKdfDeriveFromKey,
|
|
param: {
|
|
param: {
|
|
- "password": password,
|
|
|
|
- "salt": salt,
|
|
|
|
- "memLimit": memLimit,
|
|
|
|
- "opsLimit": opsLimit,
|
|
|
|
|
|
+ "key": key,
|
|
|
|
+ "subkeyId": loginSubKeyId,
|
|
|
|
+ "subkeyLen": loginSubKeyLen,
|
|
|
|
+ "context": utf8.encode(loginSubKeyContext),
|
|
},
|
|
},
|
|
|
|
+ taskName: "deriveLoginKey",
|
|
);
|
|
);
|
|
|
|
+ // return the first 16 bytes of the derived key
|
|
|
|
+ return derivedKey.sublist(0, 16);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // Computes and returns the hash of the source file
|
|
static Future<Uint8List> getHash(io.File source) {
|
|
static Future<Uint8List> getHash(io.File source) {
|
|
return _computer.compute(
|
|
return _computer.compute(
|
|
cryptoGenericHash,
|
|
cryptoGenericHash,
|
|
param: {
|
|
param: {
|
|
"sourceFilePath": source.path,
|
|
"sourceFilePath": source.path,
|
|
},
|
|
},
|
|
|
|
+ taskName: "fileHash",
|
|
);
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|