2020-08-10 23:47:22 +00:00
|
|
|
import 'dart:typed_data';
|
|
|
|
|
2020-09-03 15:38:46 +00:00
|
|
|
import 'dart:io' as io;
|
2020-08-10 23:47:22 +00:00
|
|
|
import 'package:aes_crypt/aes_crypt.dart';
|
2020-08-13 23:04:32 +00:00
|
|
|
import 'package:computer/computer.dart';
|
2020-08-09 20:39:11 +00:00
|
|
|
import 'package:encrypt/encrypt.dart';
|
2020-09-25 15:45:00 +00:00
|
|
|
import 'package:flutter_sodium/flutter_sodium.dart';
|
|
|
|
import 'package:logging/logging.dart';
|
2020-08-09 20:39:11 +00:00
|
|
|
|
2020-09-03 15:38:46 +00:00
|
|
|
import 'package:photos/core/configuration.dart';
|
2020-09-25 15:45:00 +00:00
|
|
|
import 'package:photos/models/encrypted_file_attributes.dart';
|
2020-09-05 08:53:23 +00:00
|
|
|
import 'package:steel_crypt/steel_crypt.dart' as steel;
|
2020-09-03 15:38:46 +00:00
|
|
|
import 'package:uuid/uuid.dart';
|
|
|
|
|
2020-08-09 20:39:11 +00:00
|
|
|
class CryptoUtil {
|
2020-09-25 15:45:00 +00:00
|
|
|
static Logger _logger = Logger("CryptoUtil");
|
|
|
|
|
|
|
|
static int encryptionBlockSize = 4 * 1024 * 1024;
|
|
|
|
static int decryptionBlockSize =
|
|
|
|
encryptionBlockSize + Sodium.cryptoSecretstreamXchacha20poly1305Abytes;
|
|
|
|
|
|
|
|
static Future<EncryptedFileAttributes> 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 outputFile =
|
|
|
|
await (destinationFile.open(mode: io.FileMode.writeOnlyAppend));
|
|
|
|
|
|
|
|
final key = Sodium.cryptoSecretstreamXchacha20poly1305Keygen();
|
|
|
|
final initPushResult =
|
|
|
|
Sodium.cryptoSecretstreamXchacha20poly1305InitPush(key);
|
|
|
|
|
|
|
|
var bytesRead = 0;
|
|
|
|
var encryptionTag = Sodium.cryptoSecretstreamXchacha20poly1305TagMessage;
|
|
|
|
while (
|
|
|
|
encryptionTag != Sodium.cryptoSecretstreamXchacha20poly1305TagFinal) {
|
|
|
|
bool isLastBlock = false;
|
|
|
|
var blockLength = encryptionBlockSize;
|
|
|
|
if (bytesRead + blockLength >= sourceFileLength) {
|
|
|
|
blockLength = sourceFileLength - bytesRead;
|
|
|
|
isLastBlock = true;
|
|
|
|
}
|
|
|
|
final blockData = await inputFile.read(blockLength);
|
|
|
|
bytesRead += blockLength;
|
|
|
|
if (isLastBlock) {
|
|
|
|
encryptionTag = Sodium.cryptoSecretstreamXchacha20poly1305TagFinal;
|
|
|
|
}
|
|
|
|
final encryptedData = Sodium.cryptoSecretstreamXchacha20poly1305Push(
|
|
|
|
initPushResult.state, blockData, null, encryptionTag);
|
|
|
|
outputFile.writeFromSync(encryptedData);
|
|
|
|
}
|
|
|
|
await inputFile.close();
|
|
|
|
await outputFile.close();
|
|
|
|
|
|
|
|
_logger.info("ChaCha20 Encryption time: " +
|
|
|
|
(DateTime.now().millisecondsSinceEpoch - encryptionStartTime)
|
|
|
|
.toString());
|
|
|
|
|
|
|
|
return EncryptedFileAttributes(key, initPushResult.header);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Future<void> chachaDecrypt(
|
|
|
|
io.File sourceFile,
|
|
|
|
io.File destinationFile,
|
|
|
|
EncryptedFileAttributes 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 outputFile =
|
|
|
|
await (destinationFile.open(mode: io.FileMode.writeOnlyAppend));
|
|
|
|
final pullState = Sodium.cryptoSecretstreamXchacha20poly1305InitPull(
|
|
|
|
attributes.header, attributes.key);
|
|
|
|
|
|
|
|
var bytesRead = 0;
|
|
|
|
var tag = Sodium.cryptoSecretstreamXchacha20poly1305TagMessage;
|
|
|
|
while (tag != Sodium.cryptoSecretstreamXchacha20poly1305TagFinal) {
|
|
|
|
var blockLength = decryptionBlockSize;
|
|
|
|
if (bytesRead + blockLength >= sourceFileLength) {
|
|
|
|
blockLength = sourceFileLength - bytesRead;
|
|
|
|
}
|
|
|
|
final blockData = await inputFile.read(blockLength);
|
|
|
|
bytesRead += blockLength;
|
|
|
|
final pullResult = Sodium.cryptoSecretstreamXchacha20poly1305Pull(
|
|
|
|
pullState, blockData, null);
|
|
|
|
outputFile.writeFromSync(pullResult.m);
|
|
|
|
tag = pullResult.tag;
|
|
|
|
}
|
|
|
|
await inputFile.close();
|
|
|
|
await outputFile.close();
|
|
|
|
|
|
|
|
_logger.info("ChaCha20 Decryption time: " +
|
|
|
|
(DateTime.now().millisecondsSinceEpoch - decryptionStartTime)
|
|
|
|
.toString());
|
|
|
|
}
|
|
|
|
|
2020-09-09 09:05:24 +00:00
|
|
|
static Uint8List getSecureRandomBytes({int length = 32}) {
|
|
|
|
return SecureRandom(length).bytes;
|
2020-09-05 08:53:23 +00:00
|
|
|
}
|
|
|
|
|
2020-09-09 22:27:54 +00:00
|
|
|
static String getSecureRandomString({int length = 32}) {
|
2020-09-09 23:48:11 +00:00
|
|
|
return SecureRandom(length).base64;
|
2020-09-09 22:27:54 +00:00
|
|
|
}
|
|
|
|
|
2020-09-09 09:05:24 +00:00
|
|
|
static Uint8List scrypt(Uint8List plainText, Uint8List salt) {
|
2020-09-09 21:30:04 +00:00
|
|
|
return steel.PassCryptRaw.scrypt()
|
|
|
|
.hash(salt: salt, plain: plainText, len: 32);
|
2020-09-09 09:05:24 +00:00
|
|
|
}
|
|
|
|
|
2020-09-09 19:31:14 +00:00
|
|
|
static Uint8List aesEncrypt(
|
|
|
|
Uint8List plainText, Uint8List key, Uint8List iv) {
|
2020-09-09 20:47:51 +00:00
|
|
|
final encrypter = AES(Key(key), mode: AESMode.cbc);
|
2020-08-11 23:04:16 +00:00
|
|
|
return encrypter
|
|
|
|
.encrypt(
|
2020-09-09 19:31:14 +00:00
|
|
|
plainText,
|
|
|
|
iv: IV(iv),
|
2020-08-11 23:04:16 +00:00
|
|
|
)
|
2020-09-09 19:31:14 +00:00
|
|
|
.bytes;
|
2020-08-09 20:39:11 +00:00
|
|
|
}
|
|
|
|
|
2020-09-09 21:09:41 +00:00
|
|
|
static Uint8List aesDecrypt(
|
|
|
|
Uint8List cipherText, Uint8List key, Uint8List iv) {
|
|
|
|
final encrypter = AES(Key(key), mode: AESMode.cbc);
|
|
|
|
return encrypter.decrypt(
|
|
|
|
Encrypted(cipherText),
|
|
|
|
iv: IV(iv),
|
|
|
|
);
|
2020-08-09 20:39:11 +00:00
|
|
|
}
|
2020-08-10 23:47:22 +00:00
|
|
|
|
2020-08-13 21:00:40 +00:00
|
|
|
static Future<String> encryptFileToFile(
|
2020-09-09 22:27:54 +00:00
|
|
|
String sourcePath, String destinationPath, String password) async {
|
2020-09-09 21:09:41 +00:00
|
|
|
final args = Map<String, dynamic>();
|
2020-09-09 22:27:54 +00:00
|
|
|
args["password"] = password;
|
2020-08-13 21:00:40 +00:00
|
|
|
args["source"] = sourcePath;
|
|
|
|
args["destination"] = destinationPath;
|
2020-08-13 23:04:32 +00:00
|
|
|
return Computer().compute(runEncryptFileToFile, param: args);
|
2020-08-10 23:47:22 +00:00
|
|
|
}
|
2020-08-11 00:08:48 +00:00
|
|
|
|
2020-08-13 21:00:40 +00:00
|
|
|
static Future<String> encryptDataToFile(
|
2020-09-09 22:27:54 +00:00
|
|
|
Uint8List source, String destinationPath, String password) async {
|
2020-08-13 21:00:40 +00:00
|
|
|
final args = Map<String, dynamic>();
|
2020-09-09 22:27:54 +00:00
|
|
|
args["password"] = password;
|
2020-08-13 21:00:40 +00:00
|
|
|
args["source"] = source;
|
|
|
|
args["destination"] = destinationPath;
|
2020-08-13 23:04:32 +00:00
|
|
|
return Computer().compute(runEncryptDataToFile, param: args);
|
2020-08-11 00:08:48 +00:00
|
|
|
}
|
2020-08-11 23:04:16 +00:00
|
|
|
|
2020-09-09 22:27:54 +00:00
|
|
|
static Future<Uint8List> encryptDataToData(
|
|
|
|
Uint8List source, String password) async {
|
2020-09-03 15:38:46 +00:00
|
|
|
final destinationPath =
|
|
|
|
Configuration.instance.getTempDirectory() + Uuid().v4();
|
2020-09-09 22:27:54 +00:00
|
|
|
return encryptDataToFile(source, destinationPath, password).then((value) {
|
2020-09-03 15:38:46 +00:00
|
|
|
final file = io.File(destinationPath);
|
2020-09-06 06:29:30 +00:00
|
|
|
final data = file.readAsBytesSync();
|
2020-09-03 15:38:46 +00:00
|
|
|
file.deleteSync();
|
2020-09-09 22:27:54 +00:00
|
|
|
return data;
|
2020-09-03 15:38:46 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-09-09 21:09:41 +00:00
|
|
|
static Future<void> decryptFileToFile(
|
2020-09-09 22:27:54 +00:00
|
|
|
String sourcePath, String destinationPath, String password) async {
|
2020-09-09 21:09:41 +00:00
|
|
|
final args = Map<String, dynamic>();
|
2020-09-09 22:27:54 +00:00
|
|
|
args["password"] = password;
|
2020-08-13 21:00:40 +00:00
|
|
|
args["source"] = sourcePath;
|
|
|
|
args["destination"] = destinationPath;
|
2020-08-13 23:04:32 +00:00
|
|
|
return Computer().compute(runDecryptFileToFile, param: args);
|
2020-08-11 23:04:16 +00:00
|
|
|
}
|
|
|
|
|
2020-09-09 22:27:54 +00:00
|
|
|
static Future<Uint8List> decryptFileToData(
|
|
|
|
String sourcePath, String password) {
|
2020-09-09 21:09:41 +00:00
|
|
|
final args = Map<String, dynamic>();
|
2020-09-09 22:27:54 +00:00
|
|
|
args["password"] = password;
|
2020-08-13 21:00:40 +00:00
|
|
|
args["source"] = sourcePath;
|
2020-08-13 23:04:32 +00:00
|
|
|
return Computer().compute(runDecryptFileToData, param: args);
|
2020-08-12 23:17:15 +00:00
|
|
|
}
|
2020-09-03 15:38:46 +00:00
|
|
|
|
2020-09-09 22:27:54 +00:00
|
|
|
static Future<Uint8List> decryptDataToData(
|
|
|
|
Uint8List source, String password) {
|
2020-09-03 15:38:46 +00:00
|
|
|
final sourcePath = Configuration.instance.getTempDirectory() + Uuid().v4();
|
|
|
|
final file = io.File(sourcePath);
|
2020-09-09 19:31:14 +00:00
|
|
|
file.writeAsBytesSync(source);
|
2020-09-09 22:27:54 +00:00
|
|
|
return decryptFileToData(sourcePath, password).then((value) {
|
2020-09-03 15:38:46 +00:00
|
|
|
file.deleteSync();
|
2020-09-06 06:29:30 +00:00
|
|
|
return value;
|
2020-09-03 15:38:46 +00:00
|
|
|
});
|
|
|
|
}
|
2020-08-13 21:00:40 +00:00
|
|
|
}
|
2020-08-12 23:17:15 +00:00
|
|
|
|
2020-09-09 21:09:41 +00:00
|
|
|
Future<String> runEncryptFileToFile(Map<String, dynamic> args) {
|
2020-09-09 22:27:54 +00:00
|
|
|
final encrypter = getEncrypter(args["password"]);
|
2020-08-13 21:00:40 +00:00
|
|
|
return encrypter.encryptFile(args["source"], args["destination"]);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<String> runEncryptDataToFile(Map<String, dynamic> args) {
|
2020-09-09 22:27:54 +00:00
|
|
|
final encrypter = getEncrypter(args["password"]);
|
2020-08-13 21:00:40 +00:00
|
|
|
return encrypter.encryptDataToFile(args["source"], args["destination"]);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<String> runDecryptFileToFile(Map<String, dynamic> args) async {
|
2020-09-09 22:27:54 +00:00
|
|
|
final encrypter = getEncrypter(args["password"]);
|
2020-08-13 21:00:40 +00:00
|
|
|
return encrypter.decryptFile(args["source"], args["destination"]);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<Uint8List> runDecryptFileToData(Map<String, dynamic> args) async {
|
2020-09-09 22:27:54 +00:00
|
|
|
final encrypter = getEncrypter(args["password"]);
|
2020-08-13 21:00:40 +00:00
|
|
|
return encrypter.decryptDataFromFile(args["source"]);
|
|
|
|
}
|
|
|
|
|
2020-09-09 22:27:54 +00:00
|
|
|
AesCrypt getEncrypter(String password) {
|
|
|
|
final encrypter = AesCrypt(password);
|
2020-08-13 21:00:40 +00:00
|
|
|
encrypter.aesSetMode(AesMode.cbc);
|
|
|
|
encrypter.setOverwriteMode(AesCryptOwMode.on);
|
|
|
|
return encrypter;
|
2020-08-09 20:39:11 +00:00
|
|
|
}
|