Move expensive encryption and decryption operations to a separate isolate
This commit is contained in:
parent
fc44023887
commit
f2b10a4168
6 changed files with 154 additions and 107 deletions
|
@ -1,6 +1,7 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:photos/core/configuration.dart';
|
||||
import 'package:photos/core/event_bus.dart';
|
||||
|
@ -41,11 +42,18 @@ class DiffFetcher {
|
|||
item["thumbnail"]["decryptionParams"]);
|
||||
file.metadataDecryptionParams = DecryptionParams.fromMap(
|
||||
item["metadata"]["decryptionParams"]);
|
||||
Map<String, dynamic> metadata = jsonDecode(utf8.decode(
|
||||
await CryptoUtil.decryptWithDecryptionParams(
|
||||
base64.decode(item["metadata"]["encryptedData"]),
|
||||
file.metadataDecryptionParams,
|
||||
Configuration.instance.getBase64EncodedKey())));
|
||||
final metadataDecryptionKey = await CryptoUtil.decrypt(
|
||||
Sodium.base642bin(file.metadataDecryptionParams.encryptedKey),
|
||||
Configuration.instance.getKey(),
|
||||
Sodium.base642bin(
|
||||
file.metadataDecryptionParams.keyDecryptionNonce));
|
||||
final encodedMetadata = await CryptoUtil.decrypt(
|
||||
Sodium.base642bin(item["metadata"]["encryptedData"]),
|
||||
metadataDecryptionKey,
|
||||
Sodium.base642bin(file.metadataDecryptionParams.nonce),
|
||||
);
|
||||
Map<String, dynamic> metadata =
|
||||
jsonDecode(utf8.decode(encodedMetadata));
|
||||
file.applyMetadata(metadata);
|
||||
files.add(file);
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ class FileUploader {
|
|||
final sourceFile = (await (await file.getAsset()).originFile);
|
||||
final encryptedFile = io.File(encryptedFilePath);
|
||||
final fileAttributes =
|
||||
await CryptoUtil.chachaEncrypt(sourceFile, encryptedFile);
|
||||
await CryptoUtil.encryptFile(sourceFile.path, encryptedFilePath);
|
||||
|
||||
final fileUploadURL = await getUploadURL();
|
||||
String fileObjectKey = await putFile(fileUploadURL, encryptedFile);
|
||||
|
|
|
@ -10,6 +10,7 @@ import 'package:photos/file_downloader.dart';
|
|||
import 'package:photos/file_repository.dart';
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
import 'package:photos/file_uploader.dart';
|
||||
import 'package:photos/models/file_type.dart';
|
||||
import 'package:photos/utils/file_name_util.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
@ -215,6 +216,9 @@ class PhotoSyncManager {
|
|||
return;
|
||||
}
|
||||
File file = filesToBeUploaded[i];
|
||||
if (file.fileType == FileType.video) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
var uploadedFile;
|
||||
if (Configuration.instance.hasOptedForE2E()) {
|
||||
|
|
|
@ -4,31 +4,111 @@ import 'dart:io' as io;
|
|||
import 'package:aes_crypt/aes_crypt.dart';
|
||||
import 'package:computer/computer.dart';
|
||||
import 'package:encrypt/encrypt.dart';
|
||||
import 'package:encrypt/encrypt.dart' as e;
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import 'package:photos/core/configuration.dart';
|
||||
import 'package:photos/models/decryption_params.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;
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
final int encryptionChunkSize = 4 * 1024 * 1024;
|
||||
final int decryptionChunkSize =
|
||||
encryptionChunkSize + Sodium.cryptoSecretstreamXchacha20poly1305Abytes;
|
||||
|
||||
Uint8List cryptoSecretboxEasy(Map<String, dynamic> args) {
|
||||
return Sodium.cryptoSecretboxEasy(args["source"], args["nonce"], args["key"]);
|
||||
}
|
||||
|
||||
Uint8List cryptoSecretboxOpenEasy(Map<String, dynamic> args) {
|
||||
return Sodium.cryptoSecretboxOpenEasy(
|
||||
args["cipher"], args["nonce"], args["key"]);
|
||||
}
|
||||
|
||||
ChaChaAttributes chachaEncrypt(Map<String, dynamic> args) {
|
||||
final encryptionStartTime = DateTime.now().millisecondsSinceEpoch;
|
||||
final logger = Logger("ChaChaEncrypt");
|
||||
final sourceFile = io.File(args["sourceFilePath"]);
|
||||
final destinationFile = io.File(args["destinationFilePath"]);
|
||||
final sourceFileLength = sourceFile.lengthSync();
|
||||
logger.info("Encrypting file of size " + sourceFileLength.toString());
|
||||
|
||||
final inputFile = sourceFile.openSync(mode: io.FileMode.read);
|
||||
final key = Sodium.cryptoSecretstreamXchacha20poly1305Keygen();
|
||||
final initPushResult =
|
||||
Sodium.cryptoSecretstreamXchacha20poly1305InitPush(key);
|
||||
var bytesRead = 0;
|
||||
var tag = Sodium.cryptoSecretstreamXchacha20poly1305TagMessage;
|
||||
while (tag != Sodium.cryptoSecretstreamXchacha20poly1305TagFinal) {
|
||||
var chunkSize = encryptionChunkSize;
|
||||
if (bytesRead + chunkSize >= sourceFileLength) {
|
||||
chunkSize = sourceFileLength - bytesRead;
|
||||
tag = Sodium.cryptoSecretstreamXchacha20poly1305TagFinal;
|
||||
}
|
||||
final buffer = inputFile.readSync(chunkSize);
|
||||
bytesRead += chunkSize;
|
||||
final encryptedData = Sodium.cryptoSecretstreamXchacha20poly1305Push(
|
||||
initPushResult.state, buffer, null, tag);
|
||||
destinationFile.writeAsBytesSync(encryptedData, mode: io.FileMode.append);
|
||||
}
|
||||
inputFile.closeSync();
|
||||
|
||||
logger.info("Encryption time: " +
|
||||
(DateTime.now().millisecondsSinceEpoch - encryptionStartTime).toString());
|
||||
|
||||
return ChaChaAttributes(EncryptionAttribute(bytes: key),
|
||||
EncryptionAttribute(bytes: initPushResult.header));
|
||||
}
|
||||
|
||||
void chachaDecrypt(Map<String, dynamic> args) {
|
||||
final logger = Logger("ChaChaDecrypt");
|
||||
final decryptionStartTime = DateTime.now().millisecondsSinceEpoch;
|
||||
final sourceFile = io.File(args["sourceFilePath"]);
|
||||
final destinationFile = io.File(args["destinationFilePath"]);
|
||||
final sourceFileLength = sourceFile.lengthSync();
|
||||
logger.info("Decrypting file of size " + sourceFileLength.toString());
|
||||
|
||||
final inputFile = sourceFile.openSync(mode: io.FileMode.read);
|
||||
final pullState = Sodium.cryptoSecretstreamXchacha20poly1305InitPull(
|
||||
args["header"], args["key"]);
|
||||
|
||||
var bytesRead = 0;
|
||||
var tag = Sodium.cryptoSecretstreamXchacha20poly1305TagMessage;
|
||||
while (tag != Sodium.cryptoSecretstreamXchacha20poly1305TagFinal) {
|
||||
var chunkSize = decryptionChunkSize;
|
||||
if (bytesRead + chunkSize >= sourceFileLength) {
|
||||
chunkSize = sourceFileLength - bytesRead;
|
||||
}
|
||||
final buffer = inputFile.readSync(chunkSize);
|
||||
bytesRead += chunkSize;
|
||||
final pullResult =
|
||||
Sodium.cryptoSecretstreamXchacha20poly1305Pull(pullState, buffer, null);
|
||||
destinationFile.writeAsBytesSync(pullResult.m, mode: io.FileMode.append);
|
||||
tag = pullResult.tag;
|
||||
}
|
||||
inputFile.closeSync();
|
||||
|
||||
logger.info("ChaCha20 Decryption time: " +
|
||||
(DateTime.now().millisecondsSinceEpoch - decryptionStartTime).toString());
|
||||
}
|
||||
|
||||
class CryptoUtil {
|
||||
static Logger _logger = Logger("CryptoUtil");
|
||||
|
||||
static int encryptionChunkSize = 4 * 1024 * 1024;
|
||||
static int decryptionChunkSize =
|
||||
encryptionChunkSize + Sodium.cryptoSecretstreamXchacha20poly1305Abytes;
|
||||
|
||||
static Future<EncryptedData> encrypt(Uint8List source,
|
||||
{Uint8List key}) async {
|
||||
if (key == null) {
|
||||
key = Sodium.cryptoSecretboxKeygen();
|
||||
}
|
||||
final nonce = Sodium.randombytesBuf(Sodium.cryptoSecretboxNoncebytes);
|
||||
final encryptedData = Sodium.cryptoSecretboxEasy(source, nonce, key);
|
||||
|
||||
final args = Map<String, dynamic>();
|
||||
args["source"] = source;
|
||||
args["nonce"] = nonce;
|
||||
args["key"] = key;
|
||||
final encryptedData = await Computer().compute(chachaDecrypt, param: args);
|
||||
|
||||
return EncryptedData(
|
||||
EncryptionAttribute(bytes: key),
|
||||
EncryptionAttribute(bytes: nonce),
|
||||
|
@ -36,93 +116,40 @@ class CryptoUtil {
|
|||
}
|
||||
|
||||
static Future<Uint8List> decrypt(
|
||||
String base64Cipher, String base64Key, String base64Nonce) async {
|
||||
return Sodium.cryptoSecretboxOpenEasy(Sodium.base642bin(base64Cipher),
|
||||
Sodium.base642bin(base64Nonce), Sodium.base642bin(base64Key));
|
||||
}
|
||||
|
||||
static Future<Uint8List> decryptWithDecryptionParams(
|
||||
Uint8List source, DecryptionParams params, String base64Kek) async {
|
||||
final key = Sodium.cryptoSecretboxOpenEasy(
|
||||
Sodium.base642bin(params.encryptedKey),
|
||||
Sodium.base642bin(params.keyDecryptionNonce),
|
||||
Sodium.base642bin(base64Kek));
|
||||
return Sodium.cryptoSecretboxOpenEasy(
|
||||
source, Sodium.base642bin(params.nonce), key);
|
||||
}
|
||||
|
||||
static Future<ChaChaAttributes> chachaEncrypt(
|
||||
io.File sourceFile,
|
||||
io.File destinationFile,
|
||||
) async {
|
||||
var encryptionStartTime = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
final sourceFileLength = sourceFile.lengthSync();
|
||||
_logger.info("Encrypting file of size " + sourceFileLength.toString());
|
||||
|
||||
final inputFile = await (sourceFile.open(mode: io.FileMode.read));
|
||||
|
||||
final key = Sodium.cryptoSecretstreamXchacha20poly1305Keygen();
|
||||
final initPushResult =
|
||||
Sodium.cryptoSecretstreamXchacha20poly1305InitPush(key);
|
||||
|
||||
var bytesRead = 0;
|
||||
var tag = Sodium.cryptoSecretstreamXchacha20poly1305TagMessage;
|
||||
while (tag != Sodium.cryptoSecretstreamXchacha20poly1305TagFinal) {
|
||||
var chunkSize = encryptionChunkSize;
|
||||
if (bytesRead + chunkSize >= sourceFileLength) {
|
||||
chunkSize = sourceFileLength - bytesRead;
|
||||
tag = Sodium.cryptoSecretstreamXchacha20poly1305TagFinal;
|
||||
}
|
||||
final buffer = await inputFile.read(chunkSize);
|
||||
bytesRead += chunkSize;
|
||||
final encryptedData = Sodium.cryptoSecretstreamXchacha20poly1305Push(
|
||||
initPushResult.state, buffer, null, tag);
|
||||
destinationFile.writeAsBytesSync(encryptedData, mode: io.FileMode.append);
|
||||
Uint8List cipher, Uint8List key, Uint8List nonce,
|
||||
{bool background = false}) async {
|
||||
final args = Map<String, dynamic>();
|
||||
args["cipher"] = cipher;
|
||||
args["nonce"] = nonce;
|
||||
args["key"] = key;
|
||||
if (background) {
|
||||
return Computer().compute(chachaDecrypt, param: args);
|
||||
} else {
|
||||
return cryptoSecretboxOpenEasy(args);
|
||||
}
|
||||
await inputFile.close();
|
||||
|
||||
_logger.info("ChaCha20 Encryption time: " +
|
||||
(DateTime.now().millisecondsSinceEpoch - encryptionStartTime)
|
||||
.toString());
|
||||
|
||||
return ChaChaAttributes(EncryptionAttribute(bytes: key),
|
||||
EncryptionAttribute(bytes: initPushResult.header));
|
||||
}
|
||||
|
||||
static Future<void> chachaDecrypt(
|
||||
io.File sourceFile,
|
||||
io.File destinationFile,
|
||||
static Future<ChaChaAttributes> encryptFile(
|
||||
String sourceFilePath,
|
||||
String destinationFilePath,
|
||||
) {
|
||||
final args = Map<String, dynamic>();
|
||||
args["sourceFilePath"] = sourceFilePath;
|
||||
args["destinationFilePath"] = destinationFilePath;
|
||||
return Computer().compute(chachaDecrypt, param: args);
|
||||
}
|
||||
|
||||
static Future<void> decryptFile(
|
||||
String sourceFilePath,
|
||||
String destinationFilePath,
|
||||
ChaChaAttributes attributes,
|
||||
) async {
|
||||
var decryptionStartTime = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
final sourceFileLength = sourceFile.lengthSync();
|
||||
_logger.info("Decrypting file of size " + sourceFileLength.toString());
|
||||
|
||||
final inputFile = await (sourceFile.open(mode: io.FileMode.read));
|
||||
final pullState = Sodium.cryptoSecretstreamXchacha20poly1305InitPull(
|
||||
attributes.header.bytes, attributes.key.bytes);
|
||||
|
||||
var bytesRead = 0;
|
||||
var tag = Sodium.cryptoSecretstreamXchacha20poly1305TagMessage;
|
||||
while (tag != Sodium.cryptoSecretstreamXchacha20poly1305TagFinal) {
|
||||
var chunkSize = decryptionChunkSize;
|
||||
if (bytesRead + chunkSize >= sourceFileLength) {
|
||||
chunkSize = sourceFileLength - bytesRead;
|
||||
}
|
||||
final buffer = await inputFile.read(chunkSize);
|
||||
bytesRead += chunkSize;
|
||||
final pullResult = Sodium.cryptoSecretstreamXchacha20poly1305Pull(
|
||||
pullState, buffer, null);
|
||||
destinationFile.writeAsBytesSync(pullResult.m, mode: io.FileMode.append);
|
||||
tag = pullResult.tag;
|
||||
}
|
||||
await inputFile.close();
|
||||
|
||||
_logger.info("ChaCha20 Decryption time: " +
|
||||
(DateTime.now().millisecondsSinceEpoch - decryptionStartTime)
|
||||
.toString());
|
||||
) {
|
||||
final args = Map<String, dynamic>();
|
||||
args["sourceFilePath"] = sourceFilePath;
|
||||
args["destinationFilePath"] = destinationFilePath;
|
||||
args["header"] = attributes.header.bytes;
|
||||
args["key"] = attributes.key.bytes;
|
||||
return Computer().compute(chachaDecrypt, param: args);
|
||||
}
|
||||
|
||||
static Uint8List getSecureRandomBytes({int length = 32}) {
|
||||
|
@ -140,7 +167,7 @@ class CryptoUtil {
|
|||
|
||||
static Uint8List aesEncrypt(
|
||||
Uint8List plainText, Uint8List key, Uint8List iv) {
|
||||
final encrypter = AES(Key(key), mode: AESMode.cbc);
|
||||
final encrypter = AES(e.Key(key), mode: AESMode.cbc);
|
||||
return encrypter
|
||||
.encrypt(
|
||||
plainText,
|
||||
|
@ -151,7 +178,7 @@ class CryptoUtil {
|
|||
|
||||
static Uint8List aesDecrypt(
|
||||
Uint8List cipherText, Uint8List key, Uint8List iv) {
|
||||
final encrypter = AES(Key(key), mode: AESMode.cbc);
|
||||
final encrypter = AES(e.Key(key), mode: AESMode.cbc);
|
||||
return encrypter.decrypt(
|
||||
Encrypted(cipherText),
|
||||
iv: IV(iv),
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:io' as io;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
|
@ -159,13 +160,14 @@ Future<io.File> _downloadAndDecrypt(File file, BaseCacheManager cacheManager,
|
|||
var attributes = ChaChaAttributes(
|
||||
EncryptionAttribute(
|
||||
bytes: await CryptoUtil.decrypt(
|
||||
file.fileDecryptionParams.encryptedKey,
|
||||
Configuration.instance.getBase64EncodedKey(),
|
||||
file.fileDecryptionParams.keyDecryptionNonce,
|
||||
Sodium.base642bin(file.fileDecryptionParams.encryptedKey),
|
||||
Configuration.instance.getKey(),
|
||||
Sodium.base642bin(file.fileDecryptionParams.keyDecryptionNonce),
|
||||
)),
|
||||
EncryptionAttribute(base64: file.fileDecryptionParams.header),
|
||||
);
|
||||
await CryptoUtil.chachaDecrypt(encryptedFile, decryptedFile, attributes);
|
||||
await CryptoUtil.decryptFile(
|
||||
encryptedFilePath, decryptedFilePath, attributes);
|
||||
encryptedFile.deleteSync();
|
||||
final fileExtension = extension(file.title).substring(1).toLowerCase();
|
||||
final cachedFile = await cacheManager.putFile(
|
||||
|
@ -186,10 +188,15 @@ Future<io.File> _downloadAndDecryptThumbnail(File file) async {
|
|||
"_thumbnail.decrypted";
|
||||
return Dio().download(file.getThumbnailUrl(), temporaryPath).then((_) async {
|
||||
final encryptedFile = io.File(temporaryPath);
|
||||
final data = await CryptoUtil.decryptWithDecryptionParams(
|
||||
encryptedFile.readAsBytesSync(),
|
||||
file.thumbnailDecryptionParams,
|
||||
Configuration.instance.getBase64EncodedKey());
|
||||
final thumbnailDecryptionKey = await CryptoUtil.decrypt(
|
||||
Sodium.base642bin(file.thumbnailDecryptionParams.encryptedKey),
|
||||
Configuration.instance.getKey(),
|
||||
Sodium.base642bin(file.thumbnailDecryptionParams.keyDecryptionNonce));
|
||||
final data = await CryptoUtil.decrypt(
|
||||
encryptedFile.readAsBytesSync(),
|
||||
thumbnailDecryptionKey,
|
||||
Sodium.base642bin(file.thumbnailDecryptionParams.nonce),
|
||||
);
|
||||
encryptedFile.deleteSync();
|
||||
return ThumbnailCacheManager().putFile(
|
||||
file.getThumbnailUrl(),
|
||||
|
|
1
thirdparty/flutter_sodium
vendored
Submodule
1
thirdparty/flutter_sodium
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 5829370823578edec239080d3f831e84c2160396
|
Loading…
Add table
Reference in a new issue