part of '../../settings_modal.dart'; // ── Megolm key export / import – top-level so compute() can use them ───────── const _kMegolmHeader = '-----BEGIN MEGOLM SESSION DATA-----'; const _kMegolmFooter = '-----END MEGOLM SESSION DATA-----'; // PBKDF2-HMAC-SHA512. args: {pass: Uint8List, salt: Uint8List, iter: int} Uint8List _runPbkdf2(Map args) { final pass = args['pass'] as Uint8List; final salt = args['salt'] as Uint8List; final iterations = args['iter'] as int; const blockLen = 64; // SHA-512 digest size const dkLen = 64; final hmac = mcrypto.Hmac(mcrypto.sha512, pass); final blockCount = (dkLen / blockLen).ceil(); final dk = Uint8List(blockCount * blockLen); for (var i = 1; i <= blockCount; i++) { final saltI = Uint8List(salt.length + 4); saltI.setRange(0, salt.length, salt); ByteData.sublistView(saltI, salt.length).setUint32(0, i); var u = Uint8List.fromList(hmac.convert(saltI).bytes); final block = Uint8List.fromList(u); for (var j = 1; j < iterations; j++) { u = Uint8List.fromList(hmac.convert(u).bytes); for (var k = 0; k < blockLen; k++) { block[k] ^= u[k]; } } dk.setRange((i - 1) * blockLen, i * blockLen, block); } return dk.sublist(0, dkLen); } Uint8List _aesCtr(Uint8List key, Uint8List iv, Uint8List data) { final cipher = pc.SICStreamCipher(pc.AESEngine()) ..init(true, pc.ParametersWithIV(pc.KeyParameter(key), iv)); final out = Uint8List(data.length); cipher.processBytes(data, 0, data.length, out, 0); return out; } // args: {passphrase: String, sessions: List} → encrypted String String _encryptMegolmKeys(Map args) { final pass = args['passphrase'] as String; final sessions = args['sessions'] as List; final rng = Random.secure(); final salt = Uint8List.fromList(List.generate(16, (_) => rng.nextInt(256))); // IV: 8 random bytes as nonce, last 8 bytes 0x00 (counter starts at 0) final iv = Uint8List(16); for (var i = 0; i < 8; i++) { iv[i] = rng.nextInt(256); } const iterations = 100000; final dk = _runPbkdf2({ 'pass': Uint8List.fromList(utf8.encode(pass)), 'salt': salt, 'iter': iterations, }); final aesKey = dk.sublist(0, 32); final hmacKey = dk.sublist(32, 64); final plaintext = Uint8List.fromList(utf8.encode(jsonEncode(sessions))); final encrypted = _aesCtr(aesKey, iv, plaintext); final iterBe = Uint8List(4)..buffer.asByteData().setUint32(0, iterations); // header bytes: 0x01 | salt(16) | iv(16) | iterBe(4) | encrypted(n) final header = Uint8List(1 + 16 + 16 + 4 + encrypted.length); var off = 0; header[off++] = 0x01; header.setRange(off, off += 16, salt); header.setRange(off, off += 16, iv); header.setRange(off, off += 4, iterBe); header.setRange(off, off + encrypted.length, encrypted); final mac = Uint8List.fromList( mcrypto.Hmac(mcrypto.sha256, hmacKey).convert(header).bytes, ); final full = Uint8List(header.length + 32); full.setRange(0, header.length, header); full.setRange(header.length, full.length, mac); return '$_kMegolmHeader\n${base64.encode(full)}\n$_kMegolmFooter'; } // args: {passphrase: String, data: List} → List sessions List _decryptMegolmKeys(Map args) { final pass = args['passphrase'] as String; final raw = Uint8List.fromList(args['data'] as List); if (raw.length < 1 + 16 + 16 + 4 + 32 || raw[0] != 0x01) { throw Exception('Ungültiges Dateiformat (Version ${raw.isEmpty ? "?" : raw[0]}).'); } final salt = raw.sublist(1, 17); final iv = raw.sublist(17, 33); final iterations = raw.buffer.asByteData().getUint32(33); final encrypted = raw.sublist(37, raw.length - 32); final storedMac = raw.sublist(raw.length - 32); if (iterations < 1 || iterations > 10000000) { throw Exception('Iterationsanzahl außerhalb des erlaubten Bereichs.'); } final dk = _runPbkdf2({ 'pass': Uint8List.fromList(utf8.encode(pass)), 'salt': Uint8List.fromList(salt), 'iter': iterations, }); final aesKey = dk.sublist(0, 32); final hmacKey = dk.sublist(32, 64); // Verify MAC over all bytes except the appended MAC itself final toMac = raw.sublist(0, raw.length - 32); final expectedMac = mcrypto.Hmac(mcrypto.sha256, hmacKey).convert(toMac).bytes; var macOk = true; for (var i = 0; i < 32; i++) { if (expectedMac[i] != storedMac[i]) { macOk = false; break; } } if (!macOk) throw Exception('Falsches Passwort oder beschädigte Datei.'); final decrypted = _aesCtr(aesKey, Uint8List.fromList(iv), Uint8List.fromList(encrypted)); return jsonDecode(utf8.decode(decrypted)) as List; }