refactor: settings_encryption.dart (1470 Zeilen) in 4 thematische part-Dateien unter encryption/ geteilt (M2, zeichenidentisch verifiziert)
Vorbereitung fuer Schnitt-Doc Schritt 3 (verification-Modul), ohne den Verschluesselungs-heiligen Code anzufassen: megolm_crypto (Export/Import- Krypto-Helfer), encryption_section (Enums + _EncryptionSection), recovery_key_dialogs (_ChangeRecoveryKeyDialog/_ShowKeyBody/_ConfirmKeyBody), fingerprint_passphrase (_FingerprintGroup/_PassphraseDialog). Rekonstruktion zeichenidentisch (1468 Code-Zeilen, ordinaler Vergleich), analyze sauber, 29 Tests gruen, App-Start ok (auth_log 17:43:14 loggedIn). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
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<String, dynamic> 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<dynamic>} → encrypted String
|
||||
String _encryptMegolmKeys(Map<String, dynamic> args) {
|
||||
final pass = args['passphrase'] as String;
|
||||
final sessions = args['sessions'] as List<dynamic>;
|
||||
|
||||
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<int>} → List<dynamic> sessions
|
||||
List<dynamic> _decryptMegolmKeys(Map<String, dynamic> args) {
|
||||
final pass = args['passphrase'] as String;
|
||||
final raw = Uint8List.fromList(args['data'] as List<int>);
|
||||
|
||||
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<dynamic>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user