SPS3: Poly1305-Einmal-MAC + Public-Ready README/LICENSE
Authentifizierung für CLI und Web: - Poly1305 (RFC 8439) pur in Python (bigint) und JS (BigInt), keine neue Dependency - MAC-Schluessel pro Nachricht frisch aus dem Pad (len+32), encrypt-then-MAC ueber Header + Ciphertext; reveal lehnt manipulierte/desynchrone Bilder hart ab - Stego-Format SPS2 -> SPS3 (16-B-Tag im Header); alte SPS2-Bilder werden noch gelesen, aber als unauthentifiziert markiert - Service-Worker-Cache auf v3 gebumpt - Interop-Runner (web/_interop_run.sh): RFC-Vektor cross-impl, beide Richtungen, Tamper-Erkennung README fuer Public ueberarbeitet (ehrliches Bedrohungsmodell, Voraussetzungen, Projektstruktur, Tests) + MIT-LICENSE. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
// Interop-Test v2: lädt EXAKT den Core aus der ausgelieferten index.html und
|
||||
// prüft Byte-Kompatibilität mit dem Python-CLI (richtungsgetrennte Schlüssel).
|
||||
// Interop-Test v3: lädt EXAKT den Core aus der ausgelieferten index.html und
|
||||
// prüft Byte-Kompatibilität mit dem Python-CLI (richtungsgetrennte Schlüssel,
|
||||
// SPS3 mit Poly1305-Einmal-MAC). Orchestriert von _interop_run.sh.
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
|
||||
const U = (p) => new Uint8Array(readFileSync(new URL(p, import.meta.url)));
|
||||
@@ -10,31 +11,54 @@ const SP = globalThis.SP;
|
||||
|
||||
let fails = 0;
|
||||
const ok = (c, m) => { console.log((c ? ' ✓ ' : ' ✗ ') + m); if (!c) fails++; };
|
||||
const hex = (s) => new Uint8Array(s.match(/../g).map((h) => parseInt(h, 16)));
|
||||
|
||||
// A: CLI (send.key) -> Web-Core (recv.key)
|
||||
// 0) Poly1305 gegen den RFC-8439-Testvektor (§2.5.2) — verankert beide Impls.
|
||||
{
|
||||
console.log('0) Poly1305 RFC 8439');
|
||||
const key = hex('85d6be7857556d337f4452fe42d506a80103808afb0db2fd4abff6af4149f51b');
|
||||
const msg = new TextEncoder().encode('Cryptographic Forum Research Group');
|
||||
const tag = SP.poly1305(msg, key);
|
||||
ok([...tag].map((b) => b.toString(16).padStart(2, '0')).join('')
|
||||
=== 'a8061dc1305136c6c22b8baf0c0127a9', 'Tag matcht RFC-Vektor');
|
||||
}
|
||||
|
||||
// A: CLI (send.key, SPS3) -> Web-Core (recv.key): extrahieren, MAC prüfen, entschlüsseln
|
||||
{
|
||||
const rgb = U('./_a_rgb.bin');
|
||||
const recv = SP.parseKey(U('../t2/rel_partner_seite/recv.key'));
|
||||
const info = SP.extract(rgb);
|
||||
console.log('A) CLI → Web');
|
||||
ok(info.version === SP.STEGO_VERSION, 'Container ist SPS3');
|
||||
ok(SP.sameStream(info.streamId, recv.streamId), 'stream_id passt zum recv.key');
|
||||
const macKey = recv.material.slice(info.offset + info.ciphertext.length,
|
||||
info.offset + info.ciphertext.length + SP.MAC_KEY_LEN);
|
||||
const expected = SP.poly1305(SP.concat(info.macInputHeader, info.ciphertext), macKey);
|
||||
ok(SP.ctEqual(expected, info.tag), 'MAC verifiziert (CLI-Tag == Web-Tag)');
|
||||
const plain = SP.otpXor(info.ciphertext, recv.material, info.offset);
|
||||
const text = new TextDecoder().decode(plain);
|
||||
console.log('A) CLI → Web');
|
||||
ok(SP.sameStream(info.streamId, recv.streamId), 'stream_id passt zum recv.key');
|
||||
ok(text === 'MsgA vom CLI 🕵️', 'Klartext: ' + JSON.stringify(text));
|
||||
|
||||
// Manipulation: ein Ciphertext-Bit kippen -> MAC muss anschlagen.
|
||||
const bad = info.ciphertext.slice(); bad[0] ^= 1;
|
||||
const badTag = SP.poly1305(SP.concat(info.macInputHeader, bad), macKey);
|
||||
ok(!SP.ctEqual(badTag, info.tag), 'gekipptes Bit wird vom MAC erkannt');
|
||||
}
|
||||
|
||||
// B: Web-Core (send.key) -> CLI (recv.key)
|
||||
// B: Web-Core (send.key, SPS3) -> CLI (recv.key)
|
||||
{
|
||||
const rgb = U('./_b_rgb.bin');
|
||||
const send = SP.parseKey(U('../t2/rel_partner_seite/send.key'));
|
||||
const msg = 'MsgB aus dem Browser-Core 🌐';
|
||||
console.log('B) Web → CLI');
|
||||
ok(send.role === SP.ROLE_SEND, 'partner send.key hat Rolle SEND');
|
||||
const plain = new TextEncoder().encode(msg);
|
||||
const ct = SP.otpXor(plain, send.material, send.sendOffset);
|
||||
const payload = SP.buildStego(send.streamId, send.sendOffset, ct);
|
||||
const macKey = send.material.slice(send.sendOffset + plain.length,
|
||||
send.sendOffset + plain.length + SP.MAC_KEY_LEN);
|
||||
const payload = SP.buildStego(send.streamId, send.sendOffset, ct, macKey);
|
||||
SP.embed(rgb, payload);
|
||||
writeFileSync(new URL('./_b_out_rgb.bin', import.meta.url), rgb);
|
||||
console.log('B) Web → CLI');
|
||||
ok(true, `eingebettet (${plain.length} B ab Offset ${send.sendOffset}) → _b_out_rgb.bin`);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user