Jelajahi Sumber

Encrypt and upload files

Vishnu Mohandas 5 tahun lalu
induk
melakukan
4b63196e34

+ 4 - 5
lib/core/configuration.dart

@@ -1,4 +1,3 @@
-import 'package:encrypt/encrypt.dart';
 import 'package:photos/utils/crypto_util.dart';
 import 'package:shared_preferences/shared_preferences.dart';
 
@@ -75,13 +74,13 @@ class Configuration {
     return _preferences.getBool(hasOptedForE2EKey);
   }
 
-  void generateAndSaveKey(String passphrase) async {
-    final key = SecureRandom(32).base64;
-    await _preferences.setString(keyKey, CryptoUtil.encrypt(key, passphrase));
+  Future<void> generateAndSaveKey(String passphrase) async {
+    final key = CryptoUtil.getBase64EncodedSecureRandomString(length: 32);
+    await _preferences.setString(keyKey, key);
   }
 
   String getKey(String passphrase) {
-    return CryptoUtil.decrypt(_preferences.getString(keyKey), passphrase);
+    return _preferences.getString(keyKey);
   }
 
   bool hasConfiguredAccount() {

+ 13 - 0
lib/models/file.dart

@@ -97,6 +97,19 @@ class File {
     }
   }
 
+  Map<String, dynamic> getMetadata() {
+    final metadata = Map<String, dynamic>();
+    metadata["localID"] = localID;
+    metadata["title"] = title;
+    metadata["deviceFolder"] = deviceFolder;
+    metadata["creationTime"] = creationTime;
+    metadata["modificationTime"] = modificationTime;
+    metadata["latitude"] = location.latitude;
+    metadata["longitude"] = location.longitude;
+    metadata["fileType"] = fileType.index;
+    return metadata;
+  }
+
   String getDownloadUrl() {
     return Configuration.instance.getHttpEndpoint() +
         "/files/download/" +

+ 11 - 0
lib/models/file_type.dart

@@ -4,6 +4,17 @@ enum FileType {
   other,
 }
 
+int getInt(FileType fileType) {
+  switch (fileType) {
+    case FileType.image:
+      return 0;
+    case FileType.video:
+      return 1;
+    default:
+      return -1;
+  }
+}
+
 FileType getFileType(int fileType) {
   switch (fileType) {
     case 0:

+ 49 - 5
lib/photo_sync_manager.dart

@@ -1,5 +1,6 @@
 import 'dart:async';
-import 'dart:io';
+import 'dart:convert';
+import 'dart:io' as io;
 import 'dart:math';
 
 import 'package:logging/logging.dart';
@@ -11,6 +12,7 @@ import 'package:photos/file_repository.dart';
 import 'package:path_provider/path_provider.dart';
 import 'package:photo_manager/photo_manager.dart';
 import 'package:photos/models/file_type.dart';
+import 'package:photos/utils/crypto_util.dart';
 import 'package:photos/utils/file_name_util.dart';
 import 'package:shared_preferences/shared_preferences.dart';
 import 'package:dio/dio.dart';
@@ -26,6 +28,7 @@ class PhotoSyncManager {
   bool _isSyncInProgress = false;
   Future<void> _existingSync;
   SharedPreferences _prefs;
+  String _encryptedFilesDirectory;
 
   static final _lastSyncTimeKey = "last_sync_time";
   static final _lastDBUpdationTimeKey = "last_db_updation_time";
@@ -76,8 +79,8 @@ class PhotoSyncManager {
     var lastDBUpdationTime = _prefs.getInt(_lastDBUpdationTimeKey);
     if (lastDBUpdationTime == null) {
       lastDBUpdationTime = 0;
-      await _initializeDirectories();
     }
+    await _initializeDirectories();
 
     final pathEntities =
         await _getGalleryList(lastDBUpdationTime, syncStartTime);
@@ -181,7 +184,12 @@ class PhotoSyncManager {
       }
       _logger.info("Uploading " + file.toString());
       try {
-        final uploadedFile = await _uploadFile(file);
+        var uploadedFile;
+        if (Configuration.instance.hasOptedForE2E()) {
+          uploadedFile = await _uploadEncryptedFile(file);
+        } else {
+          uploadedFile = await _uploadFile(file);
+        }
         await _db.update(file.generatedID, uploadedFile.uploadedFileID,
             uploadedFile.updationTime);
         _prefs.setInt(_lastSyncTimeKey, uploadedFile.updationTime);
@@ -236,6 +244,42 @@ class PhotoSyncManager {
     }
   }
 
+  Future<File> _uploadEncryptedFile(File file) async {
+    final filePath = (await (await file.getAsset()).originFile).path;
+    final encryptedFileName = file.generatedID.toString() + ".aes";
+    final encryptedFilePath = _encryptedFilesDirectory + encryptedFileName;
+    final fileIV = CryptoUtil.getBase64EncodedSecureRandomString(length: 16);
+    final key = Configuration.instance.getKey("hello");
+    await CryptoUtil.encryptFile(filePath, encryptedFilePath, key, fileIV);
+
+    final metadata = jsonEncode(file.getMetadata());
+    final metadataIV =
+        CryptoUtil.getBase64EncodedSecureRandomString(length: 16);
+    final encryptedMetadata = CryptoUtil.encrypt(metadata, key, metadataIV);
+    final formData = FormData.fromMap({
+      "file": MultipartFile.fromFileSync(encryptedFilePath,
+          filename: encryptedFileName),
+      "fileIV": fileIV,
+      "metadata": encryptedMetadata,
+      "metadataIV": metadataIV,
+    });
+    return _dio
+        .post(
+      Configuration.instance.getHttpEndpoint() + "/encrypted-files",
+      options:
+          Options(headers: {"X-Auth-Token": Configuration.instance.getToken()}),
+      data: formData,
+    )
+        .then((response) {
+      io.File(encryptedFilePath).deleteSync();
+      final data = response.data;
+      file.uploadedFileID = data["id"];
+      file.updationTime = data["updationTime"];
+      file.ownerID = data["ownerID"];
+      return file;
+    });
+  }
+
   Future<File> _uploadFile(File localPhoto) async {
     final title = getJPGFileNameForHEIC(localPhoto);
     final formData = FormData.fromMap({
@@ -283,8 +327,8 @@ class PhotoSyncManager {
 
   Future _initializeDirectories() async {
     final externalPath = (await getApplicationDocumentsDirectory()).path;
-    new Directory(externalPath + "/photos/thumbnails")
-        .createSync(recursive: true);
+    _encryptedFilesDirectory = externalPath + "/encrypted/files/";
+    new io.Directory(_encryptedFilesDirectory).createSync(recursive: true);
   }
 
   Future<bool> _insertFilesToDB(List<File> files, int timestamp) async {

+ 2 - 2
lib/ui/sign_in_widget.dart

@@ -338,9 +338,9 @@ class _PassphraseWidgetState extends State<PassphraseWidget> {
       actions: <Widget>[
         CupertinoDialogAction(
           child: Text('Save'),
-          onPressed: () {
+          onPressed: () async {
             Navigator.of(context).pop();
-            Configuration.instance.generateAndSaveKey(_passphrase);
+            await Configuration.instance.generateAndSaveKey(_passphrase);
             Bus.instance.fire(UserAuthenticatedEvent());
           },
         )

+ 23 - 5
lib/utils/crypto_util.dart

@@ -1,13 +1,31 @@
+import 'dart:typed_data';
+
+import 'package:aes_crypt/aes_crypt.dart';
 import 'package:encrypt/encrypt.dart';
 
 class CryptoUtil {
-  static String encrypt(String plainText, String key) {
-    final encrypter = Encrypter(AES(Key.fromUtf8(key)));
-    return encrypter.encrypt(plainText).base64;
+  static String getBase64EncodedSecureRandomString({int length = 32}) {
+    return SecureRandom(length).base64;
+  }
+
+  static String encrypt(String plainText, String base64Key, String base64IV) {
+    final encrypter = Encrypter(AES(Key.fromBase64(base64Key)));
+    final iv = base64IV == null ? null : IV.fromBase64(base64IV);
+    return encrypter.encrypt(plainText, iv: iv).base64;
   }
 
-  static String decrypt(String cipherText, String key) {
-    final encrypter = Encrypter(AES(Key.fromUtf8(key)));
+  static String decrypt(String cipherText, String base64Key) {
+    final encrypter = Encrypter(AES(Key.fromBase64(base64Key)));
     return encrypter.decrypt(Encrypted.fromBase64(cipherText));
   }
+
+  // Encrypts a file and returns the IV that was used
+  static Future<void> encryptFile(String sourcePath, String destinationPath,
+      String base64Key, String base64IV) async {
+    final encrypter = AesCrypt("hello");
+    encrypter.aesSetParams(Key.fromBase64(base64Key).bytes,
+        IV.fromBase64(base64IV).bytes, AesMode.cbc);
+    encrypter.setOverwriteMode(AesCryptOwMode.on);
+    await encrypter.encryptFile(sourcePath, destinationPath);
+  }
 }

+ 7 - 0
pubspec.lock

@@ -1,6 +1,13 @@
 # Generated by pub
 # See https://dart.dev/tools/pub/glossary#lockfile
 packages:
+  aes_crypt:
+    dependency: "direct main"
+    description:
+      name: aes_crypt
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.1.1"
   animate_do:
     dependency: "direct main"
     description:

+ 1 - 0
pubspec.yaml

@@ -23,6 +23,7 @@ dependencies:
   # The following adds the Cupertino Icons font to your application.
   # Use with the CupertinoIcons class for iOS style icons.
   encrypt: ^4.0.2
+  aes_crypt: ^0.1.1
   cupertino_icons: ^0.1.2
   photo_manager: ^0.5.7
   provider: ^3.1.0