import 'dart:typed_data'; import 'dart:io' as io; 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/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; final int encryptionChunkSize = 4 * 1024 * 1024; final int decryptionChunkSize = encryptionChunkSize + Sodium.cryptoSecretstreamXchacha20poly1305Abytes; Uint8List cryptoSecretboxEasy(Map args) { return Sodium.cryptoSecretboxEasy(args["source"], args["nonce"], args["key"]); } Uint8List cryptoSecretboxOpenEasy(Map args) { return Sodium.cryptoSecretboxOpenEasy( args["cipher"], args["nonce"], args["key"]); } ChaChaAttributes chachaEncrypt(Map 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 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 encrypt(Uint8List source, {Uint8List key}) async { if (key == null) { key = Sodium.cryptoSecretboxKeygen(); } final nonce = Sodium.randombytesBuf(Sodium.cryptoSecretboxNoncebytes); final args = Map(); args["source"] = source; args["nonce"] = nonce; args["key"] = key; final encryptedData = await Computer().compute(cryptoSecretboxEasy, param: args); return EncryptedData( EncryptionAttribute(bytes: key), EncryptionAttribute(bytes: nonce), EncryptionAttribute(bytes: encryptedData)); } static Future decrypt( Uint8List cipher, Uint8List key, Uint8List nonce, {bool background = false}) async { final args = Map(); args["cipher"] = cipher; args["nonce"] = nonce; args["key"] = key; if (background) { return Computer().compute(cryptoSecretboxOpenEasy, param: args); } else { return cryptoSecretboxOpenEasy(args); } } static Future encryptFile( String sourceFilePath, String destinationFilePath, ) { final args = Map(); args["sourceFilePath"] = sourceFilePath; args["destinationFilePath"] = destinationFilePath; return Computer().compute(chachaEncrypt, param: args); } static Future decryptFile( String sourceFilePath, String destinationFilePath, ChaChaAttributes attributes, ) { final args = Map(); 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}) { return SecureRandom(length).bytes; } static String getSecureRandomString({int length = 32}) { return SecureRandom(length).base64; } static Uint8List scrypt(Uint8List plainText, Uint8List salt) { return steel.PassCryptRaw.scrypt() .hash(salt: salt, plain: plainText, len: 32); } static Uint8List aesEncrypt( Uint8List plainText, Uint8List key, Uint8List iv) { final encrypter = AES(e.Key(key), mode: AESMode.cbc); return encrypter .encrypt( plainText, iv: IV(iv), ) .bytes; } static Uint8List aesDecrypt( Uint8List cipherText, Uint8List key, Uint8List iv) { final encrypter = AES(e.Key(key), mode: AESMode.cbc); return encrypter.decrypt( Encrypted(cipherText), iv: IV(iv), ); } }