Saltar al contenido principal
it/blog/kyber/research/constante-tiempo-dart/

Tempo Costante in Dart — Limitazioni della VM

Di Xscriptor — Óscar Preciado5 min di lettura
TecnologiaCrittografiaRicercacrittografiapost-quantisticaKyberDarttempo costanteVMsicurezzaricercaXscriptor
Tempo Costante in Dart — Limitazioni della VM

Riepilogo

La sicurezza di Kyber in modalità IND-CCA2 dipende criticamente da operazioni che vengano eseguite in tempo costante: lo stesso numero di cicli di CPU indipendentemente dai valori dei dati segreti. Dart, come linguaggio eseguito su una VM con compilazione JIT e garbage collection, non offre garanzie di tempo costante. Questo documento analizza le limitazioni concrete identificate durante l'implementazione di xkyber_crypto.

Cosa Significa "Tempo Costante" in Crittografia?

Un'operazione è costante in tempo se la sua durata non dipende da valori segreti. Per esempio:

// COSTANTE: il ciclo esegue sempre 'len' iterazioni
uint8_t ct_compare(const uint8_t *a, const uint8_t *b, size_t len) {
    uint8_t result = 0;
    for (size_t i = 0; i < len; i++) {
        result |= a[i] ^ b[i];  // XOR: ogni byte contribuisce ugualmente
    }
    return result;
}

// NON COSTANTE: early exit filtra la posizione del primo byte diverso
bool non_ct_compare(const uint8_t *a, const uint8_t *b, size_t len) {
    for (size_t i = 0; i < len; i++) {
        if (a[i] != b[i]) return false;  // il tempo dipende da dove fallisce
    }
    return true;
}

La versione costante assicura che un attaccante che misuri il tempo di confronto non possa dedurre quanti byte coincidono.

Il Problema Strutturale di Dart

1. Compilazione JIT e Riordinamento

Dart usa compilazione Just-In-Time (JIT) durante lo sviluppo e compilazione Ahead-Of-Time (AOT) per la produzione. In entrambi i casi, il compilatore può riordinare, fondere o eliminare operazioni che il programmatore assumeva come invarianti:

// Intenzione: accumulare XOR senza early exit
bool verify(Uint8List a, Uint8List b) {
  if (a.length != b.length) return false;
  int r = 0;
  for (int i = 0; i < a.length; i++) {
    r |= a[i] ^ b[i];
  }
  return r == 0;
}

Problemi potenziali:

  • Dart VM potrebbe ottimizzare il ciclo se rileva che r non viene usato fino alla fine, ma non ci sono garanzie.
  • Il compilatore AOT (gen_snapshot) può applicare trasformazioni che rompono l'illusione di tempo costante.
  • Non esiste un attributo @ConstantTime né una direttiva del compilatore per preservare la semantica temporale.

2. Garbage Collection (GC)

Dart ha un GC generazionale con barriere di scrittura (write barriers). Quando il codice crittografico scrive in una lista, la VM può eseguire codice aggiuntivo non deterministico:

r.coeffs[8 * i + j] = aj - bj;  // scrive? attiva una write barrier?

Ogni scrittura può attivare il GC se il generatore giovane si riempie. Questo introduce variazioni di tempo di microsecondi o millisecondi — ordini di grandezza maggiori delle differenze che un attacco di timing cerca di sfruttare.

3. Bounds Checking nelle Liste

Ogni accesso a List<int> in Dart verifica che l'indice sia nel range:

a.coeffs[i] = barrettReduce(a.coeffs[i]);  // bounds check ad ogni accesso

Sebbene il bounds checking sia costante per indici validi, la VM può lanciare eccezioni (lente, non costanti) su indici non validi. Peggio: l'ottimizzatore può elidere bounds checking in alcuni cicli e non in altri, introducendo differenze visibili.

4. Divisione Intera e Modulo

La riduzione di Montgomery richiede:

int r = (a + t * KYBER_Q) ~/ 65536;  // divisione intera in Dart

In C, ~/ 65536 viene compilato come uno shift a destra di 16 bit (>> 16), che è costante in tempo. In Dart, ~/ è una divisione intera che la JIT può o meno ottimizzare a seconda della piattaforma e della fase di compilazione.

5. Random.secure() ed Entropia

final Random rnd = Random.secure();
return Uint8List.fromList(
    List<int>.generate(length, (_) => rnd.nextInt(256)));

Random.secure() dipende dalla piattaforma sottostante. Nei browser web (Dart compilato in JavaScript), usa window.crypto.getRandomValues(), che può esaurire l'entropia del sistema e bloccarsi. In Flutter nativo, usa il generatore di casualità del sistema operativo. Non c'è controllo sul tempo di risposta.

Punti Critici Identificati

Operazione File Rischio Impatto
Confronto di ciphertext (FO transform) verify.dart:19 Critico Un timing attack qui rompe IND-CCA2 completamente; permette di falsificare ciphertext
Decodifica di polinomi poly.dart:44-67 Alto La serializzazione/deserializzazione con shift dipende dai dati; può far trapelare il messaggio m
Accesso a zeta in NTT ntt.dart:442 Medio Accesso sequenziale a array, ma bounds checking e write barrier possono far trapelare
Riduzione Montgomery reduce.dart:15-23 Medio Divisione intera (~/) non garantita come costante dalla VM
Compress/Decompress poly.dart:70-124 Alto Moltiplicazioni e divisioni con valori che dipendono da dati segreti
Campionamento CBD poly.dart:32-41 Medio Shift di bit (>>) senza garanzie di uniformità temporale

Confronto con Linguaggi che Offrono Garanzie

Linguaggio Garanzia di tempo costante Meccanismo
C (con cura) (se il compilatore coopera) volatile, __attribute__((const)), ispezione assembly
Rust Parziale subtle crate, ct-lib
Go Limitato crypto/subtle con ConstantTimeCompare
Java No (senza cure estreme) javax.crypto con implementazioni native
Dart No Nessun meccanismo per tempo costante

Conclusione

La VM di Dart non è stata progettata per crittografia ad alta sicurezza. Non c'è:

  • Un tipo ConstantTimeList<T> che eviti bounds checking
  • Un attributo @PreserveControlFlow per evitare che il JIT ottimizzi cicli
  • Una funzione nativa di confronto costante verificata dalla comunità

Qualsiasi implementazione di Kyber in Dart puro che non ricorra a estensioni native (FFI con C/Rust) è vulnerabile ad attacchi di temporizzazione. L'unica soluzione praticabile nell'ecosistema Dart/Flutter per crittografia post-quantistica è usare binding a librerie native come liboqs o l'implementazione di riferimento di pq-crystals tramite dart:ffi.

Il repository xkyber_crypto è archiviato come testimonianza che la correttezza funzionale non è sufficiente: la sicurezza crittografica richiede controllo sulla macchina che il linguaggio non fornisce.