Quellcode durchsuchen

Move expensive encryption and decryption operations to a separate isolate

Vishnu Mohandas vor 4 Jahren
Ursprung
Commit
f2b10a4168

+ 13 - 5
lib/file_downloader.dart

@@ -1,6 +1,7 @@
 import 'dart:convert';
 import 'dart:convert';
 
 
 import 'package:dio/dio.dart';
 import 'package:dio/dio.dart';
+import 'package:flutter_sodium/flutter_sodium.dart';
 import 'package:logging/logging.dart';
 import 'package:logging/logging.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/core/event_bus.dart';
 import 'package:photos/core/event_bus.dart';
@@ -41,11 +42,18 @@ class DiffFetcher {
                   item["thumbnail"]["decryptionParams"]);
                   item["thumbnail"]["decryptionParams"]);
               file.metadataDecryptionParams = DecryptionParams.fromMap(
               file.metadataDecryptionParams = DecryptionParams.fromMap(
                   item["metadata"]["decryptionParams"]);
                   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);
               file.applyMetadata(metadata);
               files.add(file);
               files.add(file);
             }
             }

+ 1 - 1
lib/file_uploader.dart

@@ -51,7 +51,7 @@ class FileUploader {
     final sourceFile = (await (await file.getAsset()).originFile);
     final sourceFile = (await (await file.getAsset()).originFile);
     final encryptedFile = io.File(encryptedFilePath);
     final encryptedFile = io.File(encryptedFilePath);
     final fileAttributes =
     final fileAttributes =
-        await CryptoUtil.chachaEncrypt(sourceFile, encryptedFile);
+        await CryptoUtil.encryptFile(sourceFile.path, encryptedFilePath);
 
 
     final fileUploadURL = await getUploadURL();
     final fileUploadURL = await getUploadURL();
     String fileObjectKey = await putFile(fileUploadURL, encryptedFile);
     String fileObjectKey = await putFile(fileUploadURL, encryptedFile);

+ 4 - 0
lib/photo_sync_manager.dart

@@ -10,6 +10,7 @@ import 'package:photos/file_downloader.dart';
 import 'package:photos/file_repository.dart';
 import 'package:photos/file_repository.dart';
 import 'package:photo_manager/photo_manager.dart';
 import 'package:photo_manager/photo_manager.dart';
 import 'package:photos/file_uploader.dart';
 import 'package:photos/file_uploader.dart';
+import 'package:photos/models/file_type.dart';
 import 'package:photos/utils/file_name_util.dart';
 import 'package:photos/utils/file_name_util.dart';
 import 'package:shared_preferences/shared_preferences.dart';
 import 'package:shared_preferences/shared_preferences.dart';
 import 'package:dio/dio.dart';
 import 'package:dio/dio.dart';
@@ -215,6 +216,9 @@ class PhotoSyncManager {
         return;
         return;
       }
       }
       File file = filesToBeUploaded[i];
       File file = filesToBeUploaded[i];
+      if (file.fileType == FileType.video) {
+        continue;
+      }
       try {
       try {
         var uploadedFile;
         var uploadedFile;
         if (Configuration.instance.hasOptedForE2E()) {
         if (Configuration.instance.hasOptedForE2E()) {

+ 118 - 91
lib/utils/crypto_util.dart

@@ -4,31 +4,111 @@ import 'dart:io' as io;
 import 'package:aes_crypt/aes_crypt.dart';
 import 'package:aes_crypt/aes_crypt.dart';
 import 'package:computer/computer.dart';
 import 'package:computer/computer.dart';
 import 'package:encrypt/encrypt.dart';
 import 'package:encrypt/encrypt.dart';
+import 'package:encrypt/encrypt.dart' as e;
 import 'package:flutter_sodium/flutter_sodium.dart';
 import 'package:flutter_sodium/flutter_sodium.dart';
 import 'package:logging/logging.dart';
 import 'package:logging/logging.dart';
 
 
 import 'package:photos/core/configuration.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_data_attributes.dart';
 import 'package:photos/models/encrypted_file_attributes.dart';
 import 'package:photos/models/encrypted_file_attributes.dart';
 import 'package:photos/models/encryption_attribute.dart';
 import 'package:photos/models/encryption_attribute.dart';
 import 'package:steel_crypt/steel_crypt.dart' as steel;
 import 'package:steel_crypt/steel_crypt.dart' as steel;
 import 'package:uuid/uuid.dart';
 import 'package:uuid/uuid.dart';
 
 
-class CryptoUtil {
-  static Logger _logger = Logger("CryptoUtil");
+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());
 
 
-  static int encryptionChunkSize = 4 * 1024 * 1024;
-  static int decryptionChunkSize =
-      encryptionChunkSize + Sodium.cryptoSecretstreamXchacha20poly1305Abytes;
+  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 Future<EncryptedData> encrypt(Uint8List source,
   static Future<EncryptedData> encrypt(Uint8List source,
       {Uint8List key}) async {
       {Uint8List key}) async {
     if (key == null) {
     if (key == null) {
       key = Sodium.cryptoSecretboxKeygen();
       key = Sodium.cryptoSecretboxKeygen();
     }
     }
     final nonce = Sodium.randombytesBuf(Sodium.cryptoSecretboxNoncebytes);
     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(
     return EncryptedData(
         EncryptionAttribute(bytes: key),
         EncryptionAttribute(bytes: key),
         EncryptionAttribute(bytes: nonce),
         EncryptionAttribute(bytes: nonce),
@@ -36,93 +116,40 @@ class CryptoUtil {
   }
   }
 
 
   static Future<Uint8List> decrypt(
   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<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> chachaDecrypt(
-    io.File sourceFile,
-    io.File destinationFile,
+  static Future<void> decryptFile(
+    String sourceFilePath,
+    String destinationFilePath,
     ChaChaAttributes attributes,
     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}) {
   static Uint8List getSecureRandomBytes({int length = 32}) {
@@ -140,7 +167,7 @@ class CryptoUtil {
 
 
   static Uint8List aesEncrypt(
   static Uint8List aesEncrypt(
       Uint8List plainText, Uint8List key, Uint8List iv) {
       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
     return encrypter
         .encrypt(
         .encrypt(
           plainText,
           plainText,
@@ -151,7 +178,7 @@ class CryptoUtil {
 
 
   static Uint8List aesDecrypt(
   static Uint8List aesDecrypt(
       Uint8List cipherText, Uint8List key, Uint8List iv) {
       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(
     return encrypter.decrypt(
       Encrypted(cipherText),
       Encrypted(cipherText),
       iv: IV(iv),
       iv: IV(iv),

+ 15 - 8
lib/utils/file_util.dart

@@ -1,6 +1,7 @@
 import 'dart:io' as io;
 import 'dart:io' as io;
 import 'dart:typed_data';
 import 'dart:typed_data';
 
 
+import 'package:flutter_sodium/flutter_sodium.dart';
 import 'package:path/path.dart';
 import 'package:path/path.dart';
 import 'package:dio/dio.dart';
 import 'package:dio/dio.dart';
 import 'package:flutter_cache_manager/flutter_cache_manager.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(
     var attributes = ChaChaAttributes(
       EncryptionAttribute(
       EncryptionAttribute(
           bytes: await CryptoUtil.decrypt(
           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),
       EncryptionAttribute(base64: file.fileDecryptionParams.header),
     );
     );
-    await CryptoUtil.chachaDecrypt(encryptedFile, decryptedFile, attributes);
+    await CryptoUtil.decryptFile(
+        encryptedFilePath, decryptedFilePath, attributes);
     encryptedFile.deleteSync();
     encryptedFile.deleteSync();
     final fileExtension = extension(file.title).substring(1).toLowerCase();
     final fileExtension = extension(file.title).substring(1).toLowerCase();
     final cachedFile = await cacheManager.putFile(
     final cachedFile = await cacheManager.putFile(
@@ -186,10 +188,15 @@ Future<io.File> _downloadAndDecryptThumbnail(File file) async {
       "_thumbnail.decrypted";
       "_thumbnail.decrypted";
   return Dio().download(file.getThumbnailUrl(), temporaryPath).then((_) async {
   return Dio().download(file.getThumbnailUrl(), temporaryPath).then((_) async {
     final encryptedFile = io.File(temporaryPath);
     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();
     encryptedFile.deleteSync();
     return ThumbnailCacheManager().putFile(
     return ThumbnailCacheManager().putFile(
       file.getThumbnailUrl(),
       file.getThumbnailUrl(),

+ 1 - 0
thirdparty/flutter_sodium

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