CypherMask

Toolkit di messaggistica end-to-end per Flutter/Dart, con forward secrecy, post-compromise security e mitigazione dei metadati. Si ispira all’architettura del Signal Protocol, fornendo un layer semplice da integrare in applicazioni multipiattaforma.

NOTA: CypherMask è in sviluppo. Prima dell’uso in ambienti mission-critical o commerciali è necessario un audit di sicurezza indipendente.

ARCHITETTURA

  • X3DH (Extended Triple Diffie-Hellman): avvio asincrono delle sessioni usando pre-keys pubblicate su un server
  • Double Ratchet (roadmap): derivazione di chiavi per garantire PFS e PCS
  • AEAD: ChaCha20-Poly1305 (predefinito), opzionale AES-GCM
  • HKDF-SHA256: derivazione delle chiavi di sessione e dei message keys
  • Ed25519: autenticazione delle pre-keys e delle identità
  • Sealed sender (placeholder)
  • Media E2EE a livello applicativo:
    • Cifratura dei frame audio/video prima del trasporto (stile SFrame)
    • AAD con epoch/counter/codec per autenticità dei metadati
    • Framing JSON per l’invio su socket o DataChannel
    • Compatibile con WebRTC/SFU (l’infrastruttura non vede i contenuti)

CARATTERISTICHE PRINCIPALI

  • Multipiattaforma Supporto per Android, iOS, Web, Linux, macOS, Windows (via “cryptography_flutter” per accelerazione hardware).

  • Messaggi e file cifrati

    • API ad alto livello (encrypt/decrypt) e framing JSON per integrazione semplice
    • File transfer in chunk con MAC per segmento, manifesto con metadati ridotti, tamper detection
  • Chiamate E2EE (media)

    • Nuove API per cifrare/decifrare frame (audio/video) con ChaCha20-Poly1305
    • AAD che vincola epoch/counter al MAC per evitare replay/mis-binding
    • Funzioni di framing: buildMediaChunkFrame / parseMediaChunkFrame
    • Possibile rotazione chiavi per epoch (rekey applicativo)
  • Developer-friendly

    • Integrazione semplice in WebSocket/TCP/QUIC/WebRTC
    • JSON frames leggibili per debugging
    • Demo completa in “bin/main.dart” (handshake, messaggi, media, file)

BENCHMARK (Dart VM, Windows 10, Intel i7-6700k, “cryptography” puro)

Payload Encrypt (ms/op) Decrypt (ms/op) Encrypt MB/s Decrypt MB/s 128 B 1.70 0.84 0.075 0.152 1 KB 1.67 0.86 0.61 1.18 4 KB 1.80 0.95 2.27 4.27 16 KB 2.57 1.33 6.35 12.25

File transfer

File size Encrypt Decrypt Encrypt MB/s Decrypt MB/s 128 KB 12.6 ms 6.4 ms 10.4 20.6 1 MB 79.4 ms 47.3 ms 13.2 22.1 8 MB 611 ms 369 ms 13.7 22.6

REQUISITI

  • Flutter 3.16+
  • Dart SDK 3.3+
  • Dipendenze Dart: dependencies: cryptography: ^2.7.0 cryptography_flutter: ^2.4.0

INSTALLAZIONE (esempio con Git)

dependencies: cyphermask: git: url: https://github.com/edoardogin/cyphermask.git ref: main

QUICK EXAMPLE (messaggi)

import 'dart:convert'; import 'package:cyphermask/cyphermask.dart';

Future

// Bob pubblica un PreKeyBundle final (bundle, secret) = await PreKeys.generate(identity: bob);

// Alice avvia una sessione con il bundle di Bob final (sx, _) = await CypherMask().startSession(me: alice, peerBundle: bundle);

// Bob accetta la sessione final bobSession = await CypherMask().acceptSession( me: bob, localPreKeys: (bundle, secret), initPacket: sx.initPacket, initiatorStaticX25519: await alice.x25519PublicKey, );

final aliceSession = sx.session;

// Alice invia un messaggio final pkt = await CypherMask().encryptText(aliceSession, "Hello Bob!"); final wire = pkt.toBytes();

// Bob riceve e decifra final parsed = Packet.fromBytes(wire); final msg = await CypherMask().decryptText(bobSession, parsed); print("Bob ha ricevuto: $msg"); }

QUICK EXAMPLE (media E2EE semplificato)

final mask = CypherMask(); final aliceSession = /* ottenuta da X3DH /; final bobSession = / ottenuta da X3DH */;

int epoch = 0; int counter = 0;

// Invio frame (Alice → Bob): “opusBytes” è il payload codificato counter += 1; final mediaPacket = await mask.encryptMediaFrame( sender: aliceSession, encodedFrame: opusBytes, counter: counter, epoch: epoch, codec: 'opus', timestampMs: DateTime.now().millisecondsSinceEpoch, );

// Framing per trasporto (socket/DataChannel) final wire = CypherMask.buildMediaChunkFrame( packet: mediaPacket, counter: counter, epoch: epoch, codec: 'opus', timestampMs: DateTime.now().millisecondsSinceEpoch, );

// Ricezione (Bob): parse → decrypt → invio al decoder final parsed = CypherMask.parseMediaChunkFrame(wire); final raw = await mask.decryptMediaFrame( receiver: bobSession, packet: parsed.packet, counter: parsed.counter, epoch: parsed.epoch, codec: parsed.codec, timestampMs: parsed.timestampMs, ); // “raw” è il payload da passare al decoder Opus

ESEMPIO OUTPUT DEMO

=== Avvio demo CypherMask con socket in memoria ===

SETUP Alice e Bob generano le identità... SETUP Bob pubblica un PreKeyBundle con SPK e OPK.

HANDSHAKE Alice prepara init X3DH usando il bundle di Bob... HANDSHAKE Bob ha ricevuto initFrame: verifica firma e costruisce la sessione... HANDSHAKE Verifica firma di Alice: true HANDSHAKE Sessione Bob inizializzata.

MSG Alice cifra e invia un messaggio a Bob... MSG Bob riceve un messaggio, decifra...

Bob ha ricevuto: "Ciao Bob, messaggio segreto."

MSG Bob risponde con un ACK.

CALL Avvio demo media E2EE: Alice invia 100 frame Opus fittizi a Bob... CALL Bob ha decifrato 20/100 frame (bytes cumulati: 1643) CALL Bob ha decifrato 40/100 frame (bytes cumulati: 3395) CALL Rotazione epoch → 1 CALL Bob ha decifrato 60/100 frame (bytes cumulati: 5060) CALL Bob ha decifrato 80/100 frame (bytes cumulati: 6698) CALL Bob ha terminato la ricezione dei frame (tot bytes: 8351).

FILE Alice prepara un file di 300KB e lo cifra in chunk... FILE Bob riceve manifest: file="demo.bin", size=307200B, chunks=5 FILE Bob riceve chunk 1/5 FILE Bob riceve chunk 2/5 FILE Bob riceve chunk 3/5 FILE Bob riceve chunk 4/5 FILE Bob riceve chunk 5/5 FILE Bob ricostruisce il file: 307200 bytes (ok=true)

API PRINCIPALI

Identità e chiavi

  • Identity.generate() // genera Ed25519 + X25519

Pre-keys / bundle

  • PreKeys.generate(identity, count) // crea signed pre-key + one-time pre-keys
  • PreKeyBundle.toJson() / fromJson()

Handshake (X3DH)

  • SessionX3DH.initiate() / SessionX3DH.responder()
  • CypherMask.buildInitFrame() / parseInitFrame()
  • CypherMask.verifyInitSignature()

Messaggi

  • Session.encrypt({plaintext, aad}) // usa AEAD con AAD opzionale
  • Session.decrypt(packet, {aad}) // (modificata) supporta AAD
  • CypherMask.encryptText(session, text)
  • CypherMask.decryptText(session, packet)

File

  • CypherMask.encryptFile({session, fileBytes, fileName, ...})
  • CypherMask.decryptFile({session, manifest, chunks})
  • Framing: buildFileManifestFrame / parseFileManifestFrame buildFileChunkFrame / parseFileChunkFrame

Media (chiamate E2EE a livello applicativo)

  • CypherMask.encryptMediaFrame({sender, encodedFrame, counter, epoch, codec, timestampMs})
  • CypherMask.decryptMediaFrame({receiver, packet, counter, epoch, codec, timestampMs})
  • Framing: buildMediaChunkFrame / parseMediaChunkFrame
  • Nota: integrare con WebRTC è semplice usando DataChannel per trasporto dei frame cifrati; per flussi RTP “puri” applicare cifratura per-frame prima del send.

NOTE DI SICUREZZA

  • Verifica identità (safety number / QR) fuori banda per evitare MITM sociali
  • Evitare riuso nonce: gestito internamente dalla Session per ogni encrypt
  • Anti-replay: i counter monotoni sono verificati in decrypt (blocca riinvii/out-of-order)
  • Rotazione chiavi media: possibile via “epoch” e scambio sotto-chiavi nel canale messaggi
  • Per uso professionale serve audit esterno e hardening operativo (pinning, trust-on-first-use, ecc.)

LICENZA

Distribuito sotto licenza MIT. Vedere LICENSE.

Libraries

cyphermask
main