crypto_util.dart 8.6 KB

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