|
| 1 | +import 'dart:convert'; |
| 2 | +import 'dart:io'; |
| 3 | +import 'package:cryptography/cryptography.dart'; |
| 4 | +import 'package:mutex/mutex.dart'; |
| 5 | +import 'package:cw_core/.secrets.g.dart' as secrets; |
| 6 | + |
| 7 | +final logMutex = Mutex(); |
| 8 | +final password = secrets.logPassword.isEmpty ? ':)' : secrets.logPassword; |
| 9 | +final salt = secrets.logSalt.isEmpty ? '(:' : secrets.logSalt; |
| 10 | + |
| 11 | +class EncryptionLogUtil { |
| 12 | + static final _algorithm = AesGcm.with256bits(); |
| 13 | + static SecretKey? cachedKey = null; |
| 14 | + static Future<SecretKey> _deriveKey() async { |
| 15 | + if (cachedKey != null) { |
| 16 | + return cachedKey!; |
| 17 | + } |
| 18 | + final pbkdf2 = Pbkdf2( |
| 19 | + macAlgorithm: Hmac.sha256(), |
| 20 | + iterations: 120000, // OWASP recommendation: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 |
| 21 | + bits: 256, |
| 22 | + ); |
| 23 | + final key = await pbkdf2.deriveKey( |
| 24 | + secretKey: SecretKey(utf8.encode(password)), |
| 25 | + nonce: utf8.encode(salt), |
| 26 | + ); |
| 27 | + cachedKey = key; |
| 28 | + return key; |
| 29 | + } |
| 30 | + |
| 31 | + static Future<void> write({required String path, required String data}) async { |
| 32 | + await logMutex.acquire(); |
| 33 | + try { |
| 34 | + final key = await _deriveKey(); |
| 35 | + final secretKey = await _algorithm.newSecretKey(); |
| 36 | + final iv = await secretKey.extractBytes(); |
| 37 | + |
| 38 | + final nonce = iv.sublist(0, 12); |
| 39 | + |
| 40 | + final secretBox = await _algorithm.encrypt( |
| 41 | + utf8.encode(data), |
| 42 | + secretKey: key, |
| 43 | + nonce: nonce, |
| 44 | + ); |
| 45 | + |
| 46 | + final line = base64.encode([...nonce, ...secretBox.cipherText, ...secretBox.mac.bytes]); |
| 47 | + File(path).writeAsStringSync("$line\n", mode: FileMode.append); |
| 48 | + } finally { |
| 49 | + logMutex.release(); |
| 50 | + } |
| 51 | + } |
| 52 | + |
| 53 | + static Future<String> read({required String path}) async { |
| 54 | + await logMutex.acquire(); |
| 55 | + try { |
| 56 | + final key = await _deriveKey(); |
| 57 | + final file = File(path); |
| 58 | + final lines = file.readAsLinesSync(); |
| 59 | + final sb = StringBuffer(); |
| 60 | + |
| 61 | + for (final line in lines) { |
| 62 | + try { |
| 63 | + final bytes = base64.decode(line); |
| 64 | + final nonce = bytes.sublist(0, 12); |
| 65 | + final cipherText = bytes.sublist(12, bytes.length - 16); |
| 66 | + final macBytes = bytes.sublist(bytes.length - 16); |
| 67 | + |
| 68 | + final secretBox = SecretBox( |
| 69 | + cipherText, |
| 70 | + nonce: nonce, |
| 71 | + mac: Mac(macBytes), |
| 72 | + ); |
| 73 | + |
| 74 | + final decrypted = await _algorithm.decrypt(secretBox, secretKey: key); |
| 75 | + sb.write(utf8.decode(decrypted)); |
| 76 | + } catch (_) { |
| 77 | + sb.writeln(line); |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + return sb.toString(); |
| 82 | + } finally { |
| 83 | + logMutex.release(); |
| 84 | + } |
| 85 | + } |
| 86 | +} |
0 commit comments