Explorar o código

Switch to libsodium for file encryption and decryption

Vishnu Mohandas %!s(int64=4) %!d(string=hai) anos
pai
achega
727a1684ce

+ 30 - 12
lib/db/files_db.dart

@@ -1,6 +1,7 @@
 import 'dart:io';
 
 import 'package:logging/logging.dart';
+import 'package:photos/models/decryption_params.dart';
 import 'package:photos/models/file_type.dart';
 import 'package:photos/models/location.dart';
 import 'package:photos/models/file.dart';
@@ -31,8 +32,9 @@ class FilesDB {
   static final columnCreationTime = 'creation_time';
   static final columnModificationTime = 'modification_time';
   static final columnUpdationTime = 'updation_time';
-  static final columnEncryptedPassword = 'encrypted_password';
-  static final columnEncryptedPasswordIV = 'encrypted_password_iv';
+  static final columnFileDecryptionParams = 'file_decryption_params';
+  static final columnThumbnailDecryptionParams = 'thumbnail_decryption_params';
+  static final columnMetadataDecryptionParams = 'metadata_decryption_params';
 
   // make this a singleton class
   FilesDB._privateConstructor();
@@ -74,8 +76,9 @@ class FilesDB {
             $columnCreationTime TEXT NOT NULL,
             $columnModificationTime TEXT NOT NULL,
             $columnUpdationTime TEXT,
-            $columnEncryptedPassword TEXT,
-            $columnEncryptedPasswordIV TEXT
+            $columnFileDecryptionParams TEXT,
+            $columnThumbnailDecryptionParams TEXT,
+            $columnMetadataDecryptionParams TEXT
           )
           ''');
   }
@@ -224,15 +227,18 @@ class FilesDB {
     int generatedID,
     int uploadedID,
     int updationTime,
-    String encryptedKey,
-    String iv,
+    DecryptionParams fileDecryptionParams,
+    DecryptionParams thumbnailDecryptionParams,
+    DecryptionParams metadataDecryptionParams,
   ) async {
     final db = await instance.database;
     final values = new Map<String, dynamic>();
     values[columnUploadedFileID] = uploadedID;
     values[columnUpdationTime] = updationTime;
-    values[columnEncryptedPassword] = encryptedKey;
-    values[columnEncryptedPasswordIV] = iv;
+    values[columnFileDecryptionParams] = fileDecryptionParams.toJson();
+    values[columnThumbnailDecryptionParams] =
+        thumbnailDecryptionParams.toJson();
+    values[columnMetadataDecryptionParams] = metadataDecryptionParams.toJson();
     return await db.update(
       table,
       values,
@@ -384,8 +390,16 @@ class FilesDB {
     row[columnCreationTime] = file.creationTime;
     row[columnModificationTime] = file.modificationTime;
     row[columnUpdationTime] = file.updationTime;
-    row[columnEncryptedPassword] = file.encryptedPassword;
-    row[columnEncryptedPasswordIV] = file.encryptedPasswordIV;
+    row[columnFileDecryptionParams] = file.fileDecryptionParams == null
+        ? null
+        : file.fileDecryptionParams.toJson();
+    row[columnThumbnailDecryptionParams] =
+        file.thumbnailDecryptionParams == null
+            ? null
+            : file.thumbnailDecryptionParams.toJson();
+    row[columnMetadataDecryptionParams] = file.metadataDecryptionParams == null
+        ? null
+        : file.metadataDecryptionParams.toJson();
     return row;
   }
 
@@ -408,8 +422,12 @@ class FilesDB {
     file.updationTime = row[columnUpdationTime] == null
         ? -1
         : int.parse(row[columnUpdationTime]);
-    file.encryptedPassword = row[columnEncryptedPassword];
-    file.encryptedPasswordIV = row[columnEncryptedPasswordIV];
+    file.fileDecryptionParams =
+        DecryptionParams.fromJson(row[columnFileDecryptionParams]);
+    file.thumbnailDecryptionParams =
+        DecryptionParams.fromJson(row[columnThumbnailDecryptionParams]);
+    file.metadataDecryptionParams =
+        DecryptionParams.fromJson(row[columnMetadataDecryptionParams]);
     return file;
   }
 }

+ 15 - 9
lib/file_downloader.dart

@@ -5,6 +5,7 @@ import 'package:logging/logging.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/core/event_bus.dart';
 import 'package:photos/events/remote_sync_event.dart';
+import 'package:photos/models/decryption_params.dart';
 import 'package:photos/models/file.dart';
 import 'package:photos/utils/crypto_util.dart';
 
@@ -28,18 +29,23 @@ class DiffFetcher {
           if (response != null) {
             Bus.instance.fire(RemoteSyncEvent(true));
             final diff = response.data["diff"] as List;
-            for (final fileItem in diff) {
+            for (final item in diff) {
               final file = File();
-              file.uploadedFileID = fileItem["id"];
-              file.ownerID = fileItem["ownerID"];
-              file.updationTime = fileItem["updationTime"];
+              file.uploadedFileID = item["id"];
+              file.ownerID = item["ownerID"];
+              file.updationTime = item["updationTime"];
               file.isEncrypted = true;
-              file.encryptedPassword = fileItem["encryptedPassword"];
-              file.encryptedPasswordIV = fileItem["encryptedPasswordIV"];
+              file.fileDecryptionParams =
+                  DecryptionParams.fromMap(item["file"]["decryptionParams"]);
+              file.thumbnailDecryptionParams = DecryptionParams.fromMap(
+                  item["thumbnail"]["decryptionParams"]);
+              file.metadataDecryptionParams = DecryptionParams.fromMap(
+                  item["metadata"]["decryptionParams"]);
               Map<String, dynamic> metadata = jsonDecode(utf8.decode(
-                  await CryptoUtil.decryptDataToData(
-                      base64.decode(fileItem["encryptedMetadata"]),
-                      file.getPassword())));
+                  await CryptoUtil.decryptWithDecryptionParams(
+                      base64.decode(item["metadata"]["encryptedData"]),
+                      file.metadataDecryptionParams,
+                      Configuration.instance.getBase64EncodedKey())));
               file.applyMetadata(metadata);
               files.add(file);
             }

+ 57 - 46
lib/file_uploader.dart

@@ -4,8 +4,8 @@ import 'package:dio/dio.dart';
 import 'package:logging/logging.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/core/constants.dart';
+import 'package:photos/models/decryption_params.dart';
 import 'package:photos/models/file.dart';
-import 'package:photos/models/file_type.dart';
 import 'package:photos/models/upload_url.dart';
 import 'package:photos/utils/crypto_util.dart';
 import 'package:photos/utils/file_name_util.dart';
@@ -41,46 +41,30 @@ class FileUploader {
     });
   }
 
-  // TODO: Remove encryption and decryption time logging
   Future<File> encryptAndUploadFile(File file) async {
     _logger.info("Uploading " + file.toString());
 
-    final password = CryptoUtil.getSecureRandomString(length: 32);
-    final iv = CryptoUtil.getSecureRandomBytes(length: 16);
-    final base64EncodedIV = base64.encode(iv);
-    final encryptedKey = CryptoUtil.aesEncrypt(
-        utf8.encode(password), Configuration.instance.getKey(), iv);
-    final base64EncodedEncryptedKey = base64.encode(encryptedKey);
-
-    final encryptedFileName = file.generatedID.toString() + ".aes";
+    final encryptedFileName = file.generatedID.toString() + ".encrypted";
     final tempDirectory = Configuration.instance.getTempDirectory();
     final encryptedFilePath = tempDirectory + encryptedFileName;
 
-    _logger.info("File size " +
-        (await (await file.getAsset()).file).lengthSync().toString());
-    final encryptionStartTime = DateTime.now().millisecondsSinceEpoch;
-    if (file.fileType == FileType.image) {
-      await CryptoUtil.encryptDataToFile(
-          await getBytesFromDisk(file), encryptedFilePath, password);
-    } else {
-      await CryptoUtil.encryptFileToFile(
-          (await (await file.getAsset()).originFile).path,
-          encryptedFilePath,
-          password);
-    }
-    final encryptionStopTime = DateTime.now().millisecondsSinceEpoch;
-    _logger.info("Encryption time: " +
-        (encryptionStopTime - encryptionStartTime).toString());
-
-    final decryptionStartTime = DateTime.now().millisecondsSinceEpoch;
-    await CryptoUtil.decryptFileToData(encryptedFilePath, password);
-    final decryptionStopTime = DateTime.now().millisecondsSinceEpoch;
-    _logger.info("Decryption time: " +
-        (decryptionStopTime - decryptionStartTime).toString());
+    final sourceFile = (await (await file.getAsset()).originFile);
+    final encryptedFile = io.File(encryptedFilePath);
+    final fileAttributes =
+        await CryptoUtil.chachaEncrypt(sourceFile, encryptedFile);
 
     final fileUploadURL = await getUploadURL();
-    String fileObjectKey =
-        await putFile(fileUploadURL, io.File(encryptedFilePath));
+    String fileObjectKey = await putFile(fileUploadURL, encryptedFile);
+
+    final encryptedFileKey = await CryptoUtil.encrypt(
+      fileAttributes.key.bytes,
+      key: Configuration.instance.getKey(),
+    );
+    final fileDecryptionParams = DecryptionParams(
+      encryptedKey: encryptedFileKey.encryptedData.base64,
+      keyDecryptionNonce: encryptedFileKey.nonce.base64,
+      header: fileAttributes.header.base64,
+    );
 
     final thumbnailData = (await (await file.getAsset()).thumbDataWithSize(
       THUMBNAIL_LARGE_SIZE,
@@ -88,24 +72,50 @@ class FileUploader {
       quality: 50,
     ));
     final encryptedThumbnailName =
-        file.generatedID.toString() + "_thumbnail.aes";
+        file.generatedID.toString() + "_thumbnail.encrypted";
     final encryptedThumbnailPath = tempDirectory + encryptedThumbnailName;
-    await CryptoUtil.encryptDataToFile(
-        thumbnailData, encryptedThumbnailPath, password);
+    final encryptedThumbnail = await CryptoUtil.encrypt(thumbnailData);
+    io.File(encryptedThumbnailPath)
+        .writeAsBytesSync(encryptedThumbnail.encryptedData.bytes);
 
     final thumbnailUploadURL = await getUploadURL();
     String thumbnailObjectKey =
         await putFile(thumbnailUploadURL, io.File(encryptedThumbnailPath));
 
+    final encryptedThumbnailKey = await CryptoUtil.encrypt(
+      encryptedThumbnail.key.bytes,
+      key: Configuration.instance.getKey(),
+    );
+    final thumbnailDecryptionParams = DecryptionParams(
+      encryptedKey: encryptedThumbnailKey.encryptedData.base64,
+      keyDecryptionNonce: encryptedThumbnailKey.nonce.base64,
+      nonce: encryptedThumbnail.nonce.base64,
+    );
+
     final metadata = jsonEncode(file.getMetadata());
-    final encryptedMetadata =
-        await CryptoUtil.encryptDataToData(utf8.encode(metadata), password);
+    final encryptedMetadata = await CryptoUtil.encrypt(utf8.encode(metadata));
+    final encryptedMetadataKey = await CryptoUtil.encrypt(
+      encryptedMetadata.key.bytes,
+      key: Configuration.instance.getKey(),
+    );
+    final metadataDecryptionParams = DecryptionParams(
+      encryptedKey: encryptedMetadataKey.encryptedData.base64,
+      keyDecryptionNonce: encryptedMetadataKey.nonce.base64,
+      nonce: encryptedMetadata.nonce.base64,
+    );
     final data = {
-      "fileObjectKey": fileObjectKey,
-      "thumbnailObjectKey": thumbnailObjectKey,
-      "encryptedMetadata": base64.encode(encryptedMetadata),
-      "encryptedPassword": base64EncodedEncryptedKey,
-      "encryptedPasswordIV": base64EncodedIV,
+      "file": {
+        "objectKey": fileObjectKey,
+        "decryptionParams": fileDecryptionParams.toMap(),
+      },
+      "thumbnail": {
+        "objectKey": thumbnailObjectKey,
+        "decryptionParams": thumbnailDecryptionParams,
+      },
+      "metadata": {
+        "encryptedData": encryptedMetadata.encryptedData.base64,
+        "decryptionParams": metadataDecryptionParams,
+      }
     };
     return _dio
         .post(
@@ -115,14 +125,15 @@ class FileUploader {
       data: data,
     )
         .then((response) {
-      io.File(encryptedFilePath).deleteSync();
+      encryptedFile.deleteSync();
       io.File(encryptedThumbnailPath).deleteSync();
       final data = response.data;
       file.uploadedFileID = data["id"];
       file.updationTime = data["updationTime"];
       file.ownerID = data["ownerID"];
-      file.encryptedPassword = base64EncodedEncryptedKey;
-      file.encryptedPasswordIV = base64EncodedIV;
+      file.fileDecryptionParams = fileDecryptionParams;
+      file.thumbnailDecryptionParams = thumbnailDecryptionParams;
+      file.metadataDecryptionParams = metadataDecryptionParams;
       return file;
     });
   }

+ 7 - 5
lib/folder_service.dart

@@ -71,11 +71,13 @@ class FolderSharingService {
         var existingPhoto =
             await FilesDB.instance.getMatchingRemoteFile(file.uploadedFileID);
         await FilesDB.instance.update(
-            existingPhoto.generatedID,
-            file.uploadedFileID,
-            file.updationTime,
-            file.encryptedPassword,
-            file.encryptedPasswordIV);
+          existingPhoto.generatedID,
+          file.uploadedFileID,
+          file.updationTime,
+          file.fileDecryptionParams,
+          file.thumbnailDecryptionParams,
+          file.metadataDecryptionParams,
+        );
       } catch (e) {
         await FilesDB.instance.insert(file);
       }

+ 5 - 5
lib/models/encrypted_file_attributes.dart

@@ -1,8 +1,8 @@
-import 'dart:typed_data';
+import 'package:photos/models/encryption_attribute.dart';
 
-class EncryptedFileAttributes {
-  final Uint8List key;
-  final Uint8List header;
+class ChaChaAttributes {
+  final EncryptionAttribute key;
+  final EncryptionAttribute header;
 
-  EncryptedFileAttributes(this.key, this.header);
+  ChaChaAttributes(this.key, this.header);
 }

+ 4 - 15
lib/models/file.dart

@@ -1,11 +1,9 @@
-import 'dart:convert';
-
 import 'package:photo_manager/photo_manager.dart';
 import 'package:path/path.dart';
 import 'package:photos/core/configuration.dart';
+import 'package:photos/models/decryption_params.dart';
 import 'package:photos/models/file_type.dart';
 import 'package:photos/models/location.dart';
-import 'package:photos/utils/crypto_util.dart';
 
 class File {
   int generatedID;
@@ -21,8 +19,9 @@ class File {
   int updationTime;
   Location location;
   FileType fileType;
-  String encryptedPassword;
-  String encryptedPasswordIV;
+  DecryptionParams fileDecryptionParams;
+  DecryptionParams thumbnailDecryptionParams;
+  DecryptionParams metadataDecryptionParams;
 
   File();
 
@@ -36,8 +35,6 @@ class File {
     creationTime = json["creationTime"];
     modificationTime = json["modificationTime"];
     updationTime = json["updationTime"];
-    encryptedPassword = json["encryptedPassword"];
-    encryptedPasswordIV = json["encryptedPasswordIV"];
   }
 
   static Future<File> fromAsset(
@@ -137,14 +134,6 @@ class File {
         Configuration.instance.getToken();
   }
 
-  String getPassword() {
-    if (encryptedPassword == null) {
-      return null;
-    }
-    return utf8.decode(CryptoUtil.aesDecrypt(base64.decode(encryptedPassword),
-        Configuration.instance.getKey(), base64.decode(encryptedPasswordIV)));
-  }
-
   @override
   String toString() {
     return '''File(generatedId: $generatedID, uploadedFileId: $uploadedFileID, 

+ 14 - 10
lib/photo_sync_manager.dart

@@ -223,11 +223,13 @@ class PhotoSyncManager {
           uploadedFile = await _uploader.uploadFile(file);
         }
         await _db.update(
-            file.generatedID,
-            uploadedFile.uploadedFileID,
-            uploadedFile.updationTime,
-            file.encryptedPassword,
-            file.encryptedPasswordIV);
+          file.generatedID,
+          uploadedFile.uploadedFileID,
+          uploadedFile.updationTime,
+          file.fileDecryptionParams,
+          file.thumbnailDecryptionParams,
+          file.metadataDecryptionParams,
+        );
         Bus.instance.fire(PhotoUploadEvent(
             completed: i + 1, total: filesToBeUploaded.length));
       } catch (e) {
@@ -248,11 +250,13 @@ class PhotoSyncManager {
             file.modificationTime,
             alternateTitle: getHEICFileNameForJPG(file));
         await _db.update(
-            existingPhoto.generatedID,
-            file.uploadedFileID,
-            file.updationTime,
-            file.encryptedPassword,
-            file.encryptedPasswordIV);
+          existingPhoto.generatedID,
+          file.uploadedFileID,
+          file.updationTime,
+          file.fileDecryptionParams,
+          file.thumbnailDecryptionParams,
+          file.metadataDecryptionParams,
+        );
       } catch (e) {
         file.localID = null; // File uploaded from a different device
         await _db.insert(file);

+ 37 - 4
lib/utils/crypto_util.dart

@@ -8,7 +8,10 @@ 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';
 
@@ -19,7 +22,36 @@ class CryptoUtil {
   static int decryptionBlockSize =
       encryptionBlockSize + Sodium.cryptoSecretstreamXchacha20poly1305Abytes;
 
-  static Future<EncryptedFileAttributes> chachaEncrypt(
+  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);
+    return EncryptedData(
+        EncryptionAttribute(bytes: key),
+        EncryptionAttribute(bytes: nonce),
+        EncryptionAttribute(bytes: encryptedData));
+  }
+
+  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 {
@@ -62,13 +94,14 @@ class CryptoUtil {
         (DateTime.now().millisecondsSinceEpoch - encryptionStartTime)
             .toString());
 
-    return EncryptedFileAttributes(key, initPushResult.header);
+    return ChaChaAttributes(EncryptionAttribute(bytes: key),
+        EncryptionAttribute(bytes: initPushResult.header));
   }
 
   static Future<void> chachaDecrypt(
     io.File sourceFile,
     io.File destinationFile,
-    EncryptedFileAttributes attributes,
+    ChaChaAttributes attributes,
   ) async {
     var decryptionStartTime = DateTime.now().millisecondsSinceEpoch;
 
@@ -79,7 +112,7 @@ class CryptoUtil {
     final outputFile =
         await (destinationFile.open(mode: io.FileMode.writeOnlyAppend));
     final pullState = Sodium.cryptoSecretstreamXchacha20poly1305InitPull(
-        attributes.header, attributes.key);
+        attributes.header.bytes, attributes.key.bytes);
 
     var bytesRead = 0;
     var tag = Sodium.cryptoSecretstreamXchacha20poly1305TagMessage;

+ 29 - 11
lib/utils/file_util.dart

@@ -13,6 +13,8 @@ import 'package:photos/core/cache/video_cache_manager.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/core/constants.dart';
 import 'package:photos/db/files_db.dart';
+import 'package:photos/models/encrypted_file_attributes.dart';
+import 'package:photos/models/encryption_attribute.dart';
 import 'package:photos/models/file.dart';
 import 'package:photos/models/file_type.dart';
 
@@ -139,23 +141,36 @@ Future<io.File> getThumbnailFromServer(File file) async {
 
 Future<io.File> _downloadAndDecrypt(File file, BaseCacheManager cacheManager,
     {ProgressCallback progressCallback}) async {
-  final temporaryPath = Configuration.instance.getTempDirectory() +
+  final encryptedFilePath = Configuration.instance.getTempDirectory() +
+      file.generatedID.toString() +
+      ".encrypted";
+  final decryptedFilePath = Configuration.instance.getTempDirectory() +
       file.generatedID.toString() +
-      ".aes";
+      ".decrypted";
+  final encryptedFile = io.File(encryptedFilePath);
+  final decryptedFile = io.File(decryptedFilePath);
   return Dio()
       .download(
     file.getDownloadUrl(),
-    temporaryPath,
+    encryptedFilePath,
     onReceiveProgress: progressCallback,
   )
       .then((_) async {
-    final data =
-        await CryptoUtil.decryptFileToData(temporaryPath, file.getPassword());
-    io.File(temporaryPath).deleteSync();
+    var attributes = ChaChaAttributes(
+        EncryptionAttribute(base64: file.fileDecryptionParams.header),
+        EncryptionAttribute(
+            bytes: await CryptoUtil.decrypt(
+          file.fileDecryptionParams.encryptedKey,
+          Configuration.instance.getBase64EncodedKey(),
+          file.fileDecryptionParams.nonce,
+        )));
+    await CryptoUtil.chachaDecrypt(encryptedFile, decryptedFile, attributes);
+    encryptedFile.deleteSync();
+    decryptedFile.deleteSync();
     final fileExtension = extension(file.title).substring(1).toLowerCase();
     return cacheManager.putFile(
       file.getDownloadUrl(),
-      data,
+      decryptedFile.readAsBytesSync(),
       eTag: file.getDownloadUrl(),
       maxAge: Duration(days: 365),
       fileExtension: fileExtension,
@@ -166,11 +181,14 @@ Future<io.File> _downloadAndDecrypt(File file, BaseCacheManager cacheManager,
 Future<io.File> _downloadAndDecryptThumbnail(File file) async {
   final temporaryPath = Configuration.instance.getTempDirectory() +
       file.generatedID.toString() +
-      "_thumbnail.aes";
+      "_thumbnail.decrypted";
   return Dio().download(file.getThumbnailUrl(), temporaryPath).then((_) async {
-    final data =
-        await CryptoUtil.decryptFileToData(temporaryPath, file.getPassword());
-    io.File(temporaryPath).deleteSync();
+    final encryptedFile = io.File(temporaryPath);
+    final data = await CryptoUtil.decryptWithDecryptionParams(
+        encryptedFile.readAsBytesSync(),
+        file.thumbnailDecryptionParams,
+        Configuration.instance.getBase64EncodedKey());
+    encryptedFile.deleteSync();
     return ThumbnailCacheManager().putFile(
       file.getThumbnailUrl(),
       data,