crypto_util.dart 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. // @dart=2.9
  2. import 'dart:io' as io;
  3. import 'dart:typed_data';
  4. import 'package:computer/computer.dart';
  5. import 'package:flutter_sodium/flutter_sodium.dart';
  6. import 'package:logging/logging.dart';
  7. import 'package:photos/models/derived_key_result.dart';
  8. import 'package:photos/models/encryption_result.dart';
  9. const int encryptionChunkSize = 4 * 1024 * 1024;
  10. final int decryptionChunkSize =
  11. encryptionChunkSize + Sodium.cryptoSecretstreamXchacha20poly1305Abytes;
  12. Uint8List cryptoSecretboxEasy(Map<String, dynamic> args) {
  13. return Sodium.cryptoSecretboxEasy(args["source"], args["nonce"], args["key"]);
  14. }
  15. Uint8List cryptoSecretboxOpenEasy(Map<String, dynamic> args) {
  16. return Sodium.cryptoSecretboxOpenEasy(
  17. args["cipher"],
  18. args["nonce"],
  19. args["key"],
  20. );
  21. }
  22. Uint8List cryptoPwHash(Map<String, dynamic> args) {
  23. return Sodium.cryptoPwhash(
  24. Sodium.cryptoSecretboxKeybytes,
  25. args["password"],
  26. args["salt"],
  27. args["opsLimit"],
  28. args["memLimit"],
  29. Sodium.cryptoPwhashAlgDefault,
  30. );
  31. }
  32. Uint8List cryptoGenericHash(Map<String, dynamic> args) {
  33. final sourceFile = io.File(args["sourceFilePath"]);
  34. final sourceFileLength = sourceFile.lengthSync();
  35. final inputFile = sourceFile.openSync(mode: io.FileMode.read);
  36. final state =
  37. Sodium.cryptoGenerichashInit(null, Sodium.cryptoGenerichashBytesMax);
  38. var bytesRead = 0;
  39. bool isDone = false;
  40. while (!isDone) {
  41. var chunkSize = encryptionChunkSize;
  42. if (bytesRead + chunkSize >= sourceFileLength) {
  43. chunkSize = sourceFileLength - bytesRead;
  44. isDone = true;
  45. }
  46. final buffer = inputFile.readSync(chunkSize);
  47. bytesRead += chunkSize;
  48. Sodium.cryptoGenerichashUpdate(state, buffer);
  49. }
  50. inputFile.closeSync();
  51. return Sodium.cryptoGenerichashFinal(state, Sodium.cryptoGenerichashBytesMax);
  52. }
  53. EncryptionResult chachaEncryptData(Map<String, dynamic> args) {
  54. final initPushResult =
  55. Sodium.cryptoSecretstreamXchacha20poly1305InitPush(args["key"]);
  56. final encryptedData = Sodium.cryptoSecretstreamXchacha20poly1305Push(
  57. initPushResult.state,
  58. args["source"],
  59. null,
  60. Sodium.cryptoSecretstreamXchacha20poly1305TagFinal,
  61. );
  62. return EncryptionResult(
  63. encryptedData: encryptedData,
  64. header: initPushResult.header,
  65. );
  66. }
  67. Future<EncryptionResult> chachaEncryptFile(Map<String, dynamic> args) async {
  68. final encryptionStartTime = DateTime.now().millisecondsSinceEpoch;
  69. final logger = Logger("ChaChaEncrypt");
  70. final sourceFile = io.File(args["sourceFilePath"]);
  71. final destinationFile = io.File(args["destinationFilePath"]);
  72. final sourceFileLength = await sourceFile.length();
  73. logger.info("Encrypting file of size " + sourceFileLength.toString());
  74. final inputFile = sourceFile.openSync(mode: io.FileMode.read);
  75. final key = args["key"] ?? Sodium.cryptoSecretstreamXchacha20poly1305Keygen();
  76. final initPushResult =
  77. Sodium.cryptoSecretstreamXchacha20poly1305InitPush(key);
  78. var bytesRead = 0;
  79. var tag = Sodium.cryptoSecretstreamXchacha20poly1305TagMessage;
  80. while (tag != Sodium.cryptoSecretstreamXchacha20poly1305TagFinal) {
  81. var chunkSize = encryptionChunkSize;
  82. if (bytesRead + chunkSize >= sourceFileLength) {
  83. chunkSize = sourceFileLength - bytesRead;
  84. tag = Sodium.cryptoSecretstreamXchacha20poly1305TagFinal;
  85. }
  86. final buffer = inputFile.readSync(chunkSize);
  87. bytesRead += chunkSize;
  88. final encryptedData = Sodium.cryptoSecretstreamXchacha20poly1305Push(
  89. initPushResult.state,
  90. buffer,
  91. null,
  92. tag,
  93. );
  94. await destinationFile.writeAsBytes(encryptedData, mode: io.FileMode.append);
  95. }
  96. inputFile.closeSync();
  97. logger.info(
  98. "Encryption time: " +
  99. (DateTime.now().millisecondsSinceEpoch - encryptionStartTime)
  100. .toString(),
  101. );
  102. return EncryptionResult(key: key, header: initPushResult.header);
  103. }
  104. Future<void> chachaDecryptFile(Map<String, dynamic> args) async {
  105. final logger = Logger("ChaChaDecrypt");
  106. final decryptionStartTime = DateTime.now().millisecondsSinceEpoch;
  107. final sourceFile = io.File(args["sourceFilePath"]);
  108. final destinationFile = io.File(args["destinationFilePath"]);
  109. final sourceFileLength = await sourceFile.length();
  110. logger.info("Decrypting file of size " + sourceFileLength.toString());
  111. final inputFile = sourceFile.openSync(mode: io.FileMode.read);
  112. final pullState = Sodium.cryptoSecretstreamXchacha20poly1305InitPull(
  113. args["header"],
  114. args["key"],
  115. );
  116. var bytesRead = 0;
  117. var tag = Sodium.cryptoSecretstreamXchacha20poly1305TagMessage;
  118. while (tag != Sodium.cryptoSecretstreamXchacha20poly1305TagFinal) {
  119. var chunkSize = decryptionChunkSize;
  120. if (bytesRead + chunkSize >= sourceFileLength) {
  121. chunkSize = sourceFileLength - bytesRead;
  122. }
  123. final buffer = inputFile.readSync(chunkSize);
  124. bytesRead += chunkSize;
  125. final pullResult =
  126. Sodium.cryptoSecretstreamXchacha20poly1305Pull(pullState, buffer, null);
  127. await destinationFile.writeAsBytes(pullResult.m, mode: io.FileMode.append);
  128. tag = pullResult.tag;
  129. }
  130. inputFile.closeSync();
  131. logger.info(
  132. "ChaCha20 Decryption time: " +
  133. (DateTime.now().millisecondsSinceEpoch - decryptionStartTime)
  134. .toString(),
  135. );
  136. }
  137. Uint8List chachaDecryptData(Map<String, dynamic> args) {
  138. final pullState = Sodium.cryptoSecretstreamXchacha20poly1305InitPull(
  139. args["header"],
  140. args["key"],
  141. );
  142. final pullResult = Sodium.cryptoSecretstreamXchacha20poly1305Pull(
  143. pullState,
  144. args["source"],
  145. null,
  146. );
  147. return pullResult.m;
  148. }
  149. class CryptoUtil {
  150. static final Computer _computer = Computer();
  151. static init() {
  152. _computer.turnOn(workersCount: 4);
  153. Sodium.init();
  154. }
  155. static EncryptionResult encryptSync(Uint8List source, Uint8List key) {
  156. final nonce = Sodium.randombytesBuf(Sodium.cryptoSecretboxNoncebytes);
  157. final args = <String, dynamic>{};
  158. args["source"] = source;
  159. args["nonce"] = nonce;
  160. args["key"] = key;
  161. final encryptedData = cryptoSecretboxEasy(args);
  162. return EncryptionResult(
  163. key: key,
  164. nonce: nonce,
  165. encryptedData: encryptedData,
  166. );
  167. }
  168. static Future<Uint8List> decrypt(
  169. Uint8List cipher,
  170. Uint8List key,
  171. Uint8List nonce,
  172. ) async {
  173. final args = <String, dynamic>{};
  174. args["cipher"] = cipher;
  175. args["nonce"] = nonce;
  176. args["key"] = key;
  177. return _computer.compute(cryptoSecretboxOpenEasy, param: args);
  178. }
  179. static Uint8List decryptSync(
  180. Uint8List cipher,
  181. Uint8List key,
  182. Uint8List nonce,
  183. ) {
  184. assert(key != null, "key can not be null");
  185. final args = <String, dynamic>{};
  186. args["cipher"] = cipher;
  187. args["nonce"] = nonce;
  188. args["key"] = key;
  189. return cryptoSecretboxOpenEasy(args);
  190. }
  191. static Future<EncryptionResult> encryptChaCha(
  192. Uint8List source,
  193. Uint8List key,
  194. ) async {
  195. final args = <String, dynamic>{};
  196. args["source"] = source;
  197. args["key"] = key;
  198. return _computer.compute(chachaEncryptData, param: args);
  199. }
  200. static Future<Uint8List> decryptChaCha(
  201. Uint8List source,
  202. Uint8List key,
  203. Uint8List header,
  204. ) async {
  205. final args = <String, dynamic>{};
  206. args["source"] = source;
  207. args["key"] = key;
  208. args["header"] = header;
  209. return _computer.compute(chachaDecryptData, param: args);
  210. }
  211. static Future<EncryptionResult> encryptFile(
  212. String sourceFilePath,
  213. String destinationFilePath, {
  214. Uint8List key,
  215. }) {
  216. final args = <String, dynamic>{};
  217. args["sourceFilePath"] = sourceFilePath;
  218. args["destinationFilePath"] = destinationFilePath;
  219. args["key"] = key;
  220. return _computer.compute(chachaEncryptFile, param: args);
  221. }
  222. static Future<void> decryptFile(
  223. String sourceFilePath,
  224. String destinationFilePath,
  225. Uint8List header,
  226. Uint8List key,
  227. ) {
  228. final args = <String, dynamic>{};
  229. args["sourceFilePath"] = sourceFilePath;
  230. args["destinationFilePath"] = destinationFilePath;
  231. args["header"] = header;
  232. args["key"] = key;
  233. return _computer.compute(chachaDecryptFile, param: args);
  234. }
  235. static Uint8List generateKey() {
  236. return Sodium.cryptoSecretboxKeygen();
  237. }
  238. static Uint8List getSaltToDeriveKey() {
  239. return Sodium.randombytesBuf(Sodium.cryptoPwhashSaltbytes);
  240. }
  241. static Future<KeyPair> generateKeyPair() async {
  242. return Sodium.cryptoBoxKeypair();
  243. }
  244. static Uint8List openSealSync(
  245. Uint8List input,
  246. Uint8List publicKey,
  247. Uint8List secretKey,
  248. ) {
  249. return Sodium.cryptoBoxSealOpen(input, publicKey, secretKey);
  250. }
  251. static Uint8List sealSync(Uint8List input, Uint8List publicKey) {
  252. return Sodium.cryptoBoxSeal(input, publicKey);
  253. }
  254. static Future<DerivedKeyResult> deriveSensitiveKey(
  255. Uint8List password,
  256. Uint8List salt,
  257. ) async {
  258. final logger = Logger("pwhash");
  259. int memLimit = Sodium.cryptoPwhashMemlimitSensitive;
  260. int opsLimit = Sodium.cryptoPwhashOpslimitSensitive;
  261. Uint8List key;
  262. while (memLimit > Sodium.cryptoPwhashMemlimitMin &&
  263. opsLimit < Sodium.cryptoPwhashOpslimitMax) {
  264. try {
  265. key = await deriveKey(password, salt, memLimit, opsLimit);
  266. return DerivedKeyResult(key, memLimit, opsLimit);
  267. } catch (e, s) {
  268. logger.severe(e, s);
  269. }
  270. memLimit = (memLimit / 2).round();
  271. opsLimit = opsLimit * 2;
  272. }
  273. throw UnsupportedError("Cannot perform this operation on this device");
  274. }
  275. static Future<Uint8List> deriveKey(
  276. Uint8List password,
  277. Uint8List salt,
  278. int memLimit,
  279. int opsLimit,
  280. ) {
  281. return _computer.compute(
  282. cryptoPwHash,
  283. param: {
  284. "password": password,
  285. "salt": salt,
  286. "memLimit": memLimit,
  287. "opsLimit": opsLimit,
  288. },
  289. );
  290. }
  291. static Future<Uint8List> getHash(io.File source) {
  292. return _computer.compute(
  293. cryptoGenericHash,
  294. param: {
  295. "sourceFilePath": source.path,
  296. },
  297. );
  298. }
  299. }