Saltar al contenido principal
es/blog/kyber/la-capa-que-falla/

La capa que falla — La distancia del secreto — II

Por Xscriptor — Óscar Preciado7 min de lectura
FilosofíaTecnologíaCriptografíaEnsayocriptografíapost-cuánticaKyberFlutterDartcanal lateraltiming attackimplementaciónXscriptorÓscar Preciado
La capa que falla — La distancia del secreto — II

Un algoritmo perfecto implementado incorrectamente es, simplemente, inseguro.



Kyber es seguro. La matemática está publicada, revisada por pares, analizada durante años. Los parámetros están elegidos con márgenes conservadores. La estructura Module-LWE es elegante y sólida.

Pero Kyber no vive en un papel. Vive en código. Y el código tiene un problema que las ecuaciones no conocen: el tiempo no es uniforme.

El tiempo como oráculo

Los ataques de temporización —timing attacks— explotan una discrepancia fundamental entre el modelo matemático y la máquina real. En el modelo, una operación como x + y toma "una unidad de tiempo". En la máquina real, x + y puede tomar microsegundos diferentes según los valores de x e y, según el estado de la caché, según la predicción de saltos del procesador.

Para la criptografía, esto es una catástrofe: si el tiempo de descifrado depende del secreto, el atacante puede medir el tiempo y recuperar el secreto.

// Ejemplo ilustrativo: una comparación NO constante en tiempo
bool comparar(List<int> a, List<int> b) {
  if (a.length != b.length) return false;
  for (int i = 0; i < a.length; i++) {
    if (a[i] != b[i]) return false; // early exit: el tiempo depende de dónde falla
  }
  return true;
}

Esta función devuelve false en cuanto encuentra una discrepancia. Si el primer byte falla, tarda poco. Si falla el último, tarda más. Un atacante que pueda medir el tiempo de esta comparación puede deducir cuántos bytes acertó, y con eso romper la seguridad byte a byte.

Flutter y la promesa rota

Dart es un lenguaje que no garantiza tiempo constante a nivel de VM. No hay @ConstantTime ni un atributo del compilador que asegure que una operación tardará lo mismo independientemente de sus operandos.

Problema: Dart VM → sin garantías de tiempo constante
Contexto: Flutter para aplicaciones móviles multiplataforma

Riesgos concretos al implementar Kyber en Dart puro:

  1. Comparación de ciphertexts  →  timing del FO transform (el paso más crítico)
  2. Decodificación de polinomios →  timing de Compress/Decompress
  3. NTT (Number Theoretic Transform) →  acceso a tablas según índice secreto
  4. Muestreo CBD →  ramificación según bits del secreto
  5. Deserialización de claves →  longitud implícita o padding variable

Cada uno de estos puntos es un vector de ataque. No porque Kyber sea débil —las ecuaciones son correctas— sino porque la implementación en Dart filtra información a través del tiempo.

El problema no es exclusivo de Dart. Cualquier lenguaje sin control fino sobre el tiempo de ejecución —JavaScript, Python, Java sin cuidados extremos— sufre del mismo problema. La diferencia es que Flutter/Dart se usa cada vez más para aplicaciones que necesitan criptografía (Signal, aplicaciones financieras, mensajería cifrada), y la tentación de "escribir Kyber en Dart puro, total, es post-cuántico" es grande.

Borges imaginó un imperio donde los cartógrafos crearon un mapa tan exacto que coincidía punto por punto con el territorio, hasta volverse inútil. En criptografía ocurre lo inverso: el paper es el mapa perfecto —cada operación definida, cada parámetro justificado, cada paso verificado— y la implementación es el territorio, que nunca coincide del todo con el mapa. Entre el papel y la máquina hay un espacio que ningún teorema puede cerrar.

Lo comprobé por mí mismo. Durante mi fase de aprendizaje implementé Kyber en Dart/Flutter —xkyber_crypto— siguiendo FIPS 203 al pie de la letra: NTT, CBD, FO transform, Compress/Decompress. Los tests de caja negra pasaban. Los vectores de test de la especificación coincidían byte a byte. La implementación era matemáticamente correcta. Y sin embargo, era criptográficamente insostenible.

El paper asume un modelo ideal donde cada instrucción cuesta lo mismo. El territorio —la VM de Dart, el compilador JIT, la jerarquía de caché del procesador— no hace esa suposición. El paper no menciona que el acceso a una tabla NTT según un índice derivado del secreto puede filtrar el secreto. El paper no sabe qué es una caché.

Sartre dijo que el infierno son los otros. Para el paper de Kyber, los otros son el mundo físico donde tiene que ejecutarse: la RAM, el recolector de basura, el predictor de saltos, la VM que reordena instrucciones sin pedir permiso. El repositorio está archivado desde noviembre de 2025 como testimonio de que la distancia entre el mapa y el territorio no siempre puede recorrerse.

La diferencia entre el mapa y el territorio

La investigación sobre implementación de Kyber documenta con precisión las optimizaciones necesarias para que el código sea constante en tiempo: el uso de Montgomery reduction en lugar de división, la ausencia de if en funciones de muestreo, el acceso secuencial a tablas NTT sin dependencia de datos secretos.

Capa      Qué promete               Qué puede fallar
─────     ───────────               ────────────────
Matemática Seguridad LWE            No hay fallo (correcta)
Algoritmo FO transform CCA-secure   No hay fallo (correcto)
Lenguaje  Expresividad              Sin garantías de tiempo constante
CPU       Ejecución                  Caché, predictores, pipeline

Cada capa introduce nuevos riesgos. Un algoritmo matemáticamente perfecto, implementado en un lenguaje que no garantiza tiempo constante, ejecutado en una CPU que optimiza dinámicamente: la seguridad prometida por las ecuaciones se filtra por cada rendija del hardware.

Sartre escribió que "el infierno son los otros". En criptografía, el infierno son las otras capas: el compilador que reordena operaciones, la caché que filtra accesos, el recolector de basura que introduce pausas medibles.

// Implementación constante en tiempo (C) — cada rama tarda lo mismo
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: no hay early exit
    }
    return result;  // 0 si iguales, != 0 si diferentes
}

En C, esta función es constante en tiempo (asumiendo que el compilador no la optimiza). En Dart, no hay forma de garantizar que la VM compile esto como un bucle sin ramificación dependiente de datos. La misma lógica produce resultados de seguridad diferentes según el lenguaje.

Lo que no está en el paper

El paper de Kyber (Bos et al., 2018) describe el algoritmo. No describe cómo implementarlo de forma segura en cada plataforma. No advierte que una comparación ingenua de ciphertexts en Dart puede filtrar la clave. No menciona que la VM de Dart puede introducir variaciones de tiempo que anulan la seguridad del FO transform.

Esta distancia —entre el paper y el producto— es donde ocurren la mayoría de las vulnerabilidades criptográficas reales. No en las ecuaciones. En las decisiones de implementación.

Capa ¿Dónde está documentado el riesgo?
Algoritmo Paper, FIPS 203
Parámetros Paper, FIPS 203
Implementación segura en C Referencia de pq-crystals
Implementación segura en Rust Bindings, envolturas
Implementación segura en Dart No existe documentación oficial
Implementación en Flutter No existe

No es que Kyber no pueda implementarse de forma segura en Dart. Es que hacerlo requiere un nivel de cuidado —y de conocimiento sobre la VM de Dart— que no está documentado en ningún lado. Cada implementación en Dart puro es, hoy por hoy, una apuesta.


En III: El límite del error, exploraremos la frontera física de la criptografía: la energía necesaria para romper un esquema, el límite de Landauer, y por qué incluso Kyber —como todo lo demás— está destinado a caer, aunque quizás no por las razones que esperamos.


Referencias cruzadas con la investigación: