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
rnon 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
@ConstantTimené 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) | Sì (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
@PreserveControlFlowper 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.
