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

The Layer That Fails — The Distance of the Secret — II

By Xscriptor — Óscar Preciado7 min read
PhilosophyTechnologyCryptographyEssaycryptographypost-quantumKyberFlutterDartside-channeltiming attackimplementationXscriptorÓscar Preciado
The Layer That Fails — The Distance of the Secret — II

A perfect algorithm implemented incorrectly is, simply, insecure.



Kyber is secure. The mathematics is published, peer-reviewed, analyzed for years. The parameters are chosen with conservative margins. The Module-LWE structure is elegant and solid.

But Kyber does not live on paper. It lives in code. And code has a problem that equations do not know: time is not uniform.

Time as Oracle

Timing attacks exploit a fundamental discrepancy between the mathematical model and the real machine. In the model, an operation like x + y takes "one unit of time." In the real machine, x + y can take different microseconds depending on the values of x and y, depending on the cache state, depending on the processor's branch prediction.

For cryptography, this is a catastrophe: if decryption time depends on the secret, the attacker can measure the time and recover the secret.

// Illustrative example: a NON-constant-time comparison
bool compare(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: time depends on where it fails
  }
  return true;
}

This function returns false as soon as it finds a discrepancy. If the first byte fails, it takes little time. If the last byte fails, it takes longer. An attacker who can measure the time of this comparison can deduce how many bytes were correct, and with that break security byte by byte.

Flutter and the Broken Promise

Dart is a language that does not guarantee constant time at the VM level. There is no @ConstantTime nor a compiler attribute to ensure that an operation will take the same time regardless of its operands.

Problem: Dart VM → no constant-time guarantees
Context: Flutter for multiplatform mobile applications

Concrete risks when implementing Kyber in pure Dart:

  1. Ciphertext comparison  →  timing of the FO transform (the most critical step)
  2. Polynomial decoding    →  timing of Compress/Decompress
  3. NTT (Number Theoretic Transform) →  table access based on secret index
  4. CBD sampling           →  branching based on secret bits
  5. Key deserialization    →  implicit length or variable padding

Each of these points is an attack vector. Not because Kyber is weak — the equations are correct — but because the implementation in Dart leaks information through time.

The problem is not exclusive to Dart. Any language without fine-grained control over execution time — JavaScript, Python, Java without extreme care — suffers from the same issue. The difference is that Flutter/Dart is increasingly used for applications that need cryptography (Signal, financial applications, encrypted messaging), and the temptation to "write Kyber in pure Dart, it's post-quantum after all" is strong.

Borges imagined an empire where cartographers created a map so exact that it coincided point by point with the territory, to the point of becoming useless. In cryptography, the opposite occurs: the paper is the perfect map — every operation defined, every parameter justified, every step verified — and the implementation is the territory, which never quite matches the map. Between the paper and the machine lies a space that no theorem can close.

I verified this myself. During my learning phase I implemented Kyber in Dart/Flutter — xkyber_crypto — following FIPS 203 to the letter: NTT, CBD, FO transform, Compress/Decompress. The black-box tests passed. The test vectors from the specification matched byte for byte. The implementation was mathematically correct. And yet, it was cryptographically unsustainable.

The paper assumes an ideal model where each instruction costs the same. The territory — the Dart VM, the JIT compiler, the processor cache hierarchy — does not make that assumption. The paper does not mention that accessing an NTT table using an index derived from the secret can leak the secret. The paper does not know what a cache is.

Sartre said that hell is other people. For the Kyber paper, the others are the physical world where it has to run: the RAM, the garbage collector, the branch predictor, the VM that reorders instructions without asking permission. The repository has been archived since November 2025 as a testament that the distance between the map and the territory cannot always be traversed.

The Difference Between the Map and the Territory

The research on Kyber implementation documents precisely the optimizations needed to make code constant-time: using Montgomery reduction instead of division, avoiding if in sampling functions, sequential access to NTT tables without dependence on secret data.

Layer      What it promises            What can fail
─────      ────────────────            ────────────────
Mathematical LWE security              No failure (correct)
Algorithm  CCA-secure FO transform     No failure (correct)
Language   Expressiveness              No constant-time guarantees
CPU        Execution                   Cache, predictors, pipeline

Each layer introduces new risks. A mathematically perfect algorithm, implemented in a language that does not guarantee constant time, executed on a CPU that dynamically optimizes: the security promised by the equations leaks through every crack in the hardware.

Sartre wrote that "hell is other people." In cryptography, hell is the other layers: the compiler that reorders operations, the cache that leaks access patterns, the garbage collector that introduces measurable pauses.

// Constant-time implementation (C) — each branch takes the same time
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 early exit
    }
    return result;  // 0 if equal, != 0 if different
}

In C, this function is constant-time (assuming the compiler does not optimize it). In Dart, there is no way to guarantee that the VM will compile this as a loop without data-dependent branching. The same logic produces different security outcomes depending on the language.

What Is Not in the Paper

The Kyber paper (Bos et al., 2018) describes the algorithm. It does not describe how to implement it securely on every platform. It does not warn that a naive ciphertext comparison in Dart can leak the key. It does not mention that the Dart VM can introduce timing variations that nullify the security of the FO transform.

This distance — between the paper and the product — is where most real cryptographic vulnerabilities occur. Not in the equations. In the implementation decisions.

Layer Where is the risk documented?
Algorithm Paper, FIPS 203
Parameters Paper, FIPS 203
Secure implementation in C pq-crystals reference
Secure implementation in Rust Bindings, wrappers
Secure implementation in Dart No official documentation exists
Implementation in Flutter Does not exist

It is not that Kyber cannot be implemented securely in Dart. It is that doing so requires a level of care — and knowledge about the Dart VM — that is documented nowhere. Every pure-Dart implementation is, as of today, a gamble.


In III: The Limit of Error, we will explore the physical frontier of cryptography: the energy required to break a scheme, the Landauer limit, and why even Kyber — like everything else — is destined to fall, though perhaps not for the reasons we expect.


Cross-references with the research: