Move expensive encryption and decryption operations to a separate isolate

This commit is contained in:
Vishnu Mohandas 2020-09-28 21:24:40 +05:30
parent fc44023887
commit f2b10a4168
6 changed files with 154 additions and 107 deletions

View file

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

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

View file

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

View file

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

View file

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

@ -0,0 +1 @@
Subproject commit 5829370823578edec239080d3f831e84c2160396