CVE-2026-22698

Bit by the Byte: How a Unit Error Nuked SM2 Encryption

Alon Barad
Alon Barad
Software Engineer

Jan 10, 2026·6 min read

Executive Summary (TL;DR)

The developers of the `sm2` Rust crate confused bits with bytes. Instead of generating a 256-bit random nonce, they generated a 32-bit one. This lowers the cryptographic strength to the point where a laptop can break the encryption in milliseconds using the Baby-Step Giant-Step algorithm.

A critical logic error in the Rust `sm2` crate reduced the encryption entropy from a secure 256 bits to a trivial 32 bits, allowing instant message decryption via basic cryptographic attacks.

The Hook: When Secure Code Isn't

Rust is famous for memory safety. It saves us from buffer overflows, use-after-frees, and the usual C++ nightmares. But Rust cannot save us from logic errors, and it certainly can't save us from failing 3rd-grade math unit conversions. This vulnerability is a perfect, terrifying example of how a single integer misinterpretation can turn military-grade encryption into a cereal box puzzle.

The target here is the sm2 crate. For those unaware, SM2 is the Chinese National Standard for Elliptic Curve Cryptography (ECC). It's their answer to NIST P-256. If you are interacting with Chinese financial systems, government APIs, or regulated industries, you are likely using SM2. The expectation is 256-bit security—a wall so high that all the computers on Earth couldn't scale it in a billion years.

But in CVE-2026-22698, that wall wasn't 256 feet high. It was 32 feet high. Actually, in cryptographic terms, it's worse: it's like replacing a bank vault door with a beaded curtain. Due to a "bits vs. bytes" confusion, the random number generator used for encryption was told to produce 32 bits of entropy instead of 32 bytes.

The Flaw: The Mars Climate Orbiter of Crypto

Remember the Mars Climate Orbiter? It smashed into the red planet because NASA used metric units and Lockheed Martin used imperial units. This vulnerability is the cryptographic equivalent. Deep in the encryption logic (sm2/src/pke/encrypting.rs), the code needs to generate a random ephemeral nonce, denoted as $k$. In ECC, $k$ must be a cryptographically secure random integer roughly the size of the curve order.

For SM2, the curve order is 256 bits. To get 256 bits, you need 32 bytes of randomness ($32 \times 8 = 256$). The developer correctly calculated the byte length:

// Calculates 256 bits / 8 = 32
const N_BYTES: u32 = (Sm2::ORDER.bits() + 7) / 8;

So far, so good. N_BYTES is 32. But here is the catastrophe: they passed this value to a helper function next_k which seemingly wrapped U256::try_random_bits. As the name suggests, try_random_bits expects... bits. Not bytes.

The code asked for N_BYTES (32) worth of randomness. The function obliged and returned a 256-bit integer where only the bottom 32 bits were random, and the top 224 bits were zeros. We call this "Biased Nonce Generation," but that's too polite. It's an open door.

The Code: Anatomy of a Screw-Up

Let's look at the smoking gun. This is the vulnerable logic introduced in commit 4781762. It looks innocuous if you aren't paying close attention to the variable names versus the function signatures.

The Vulnerable Implementation:

// sm2/src/pke/encrypting.rs
 
// 1. We determine we need 32 bytes
const N_BYTES: u32 = (Sm2::ORDER.bits() + 7) / 8;
 
// 2. We pass '32' to next_k
let k = Scalar::from_uint(&next_k(rng, N_BYTES)?).unwrap();

Inside next_k, the variable N_BYTES (value 32) flows into try_random_bits(rng, 32). This generates a number between $0$ and $4,294,967,295$. In the world of cryptography, $4$ billion is a trivially small number.

The Fix (Commit e4f77): The fix was simple: stop doing manual math. The RustCrypto ecosystem provides traits for this very reason. The patch removes the manual calculation entirely and uses try_generate_from_rng.

// The fixed version relies on the trait to do it right
let k = NonZeroScalar::try_generate_from_rng(rng).map_err(|_| Error)?;

This simple change ensures the full 256-bit space is used. It's a classic lesson: if your crypto library forces you to calculate buffer sizes manually, you're probably about to break something.

The Exploit: Baby-Step Giant-Step

So, we have a nonce $k$ that is only 32 bits long. How do we break it? You could brute-force it. A modern GPU can iterate through $2^{32}$ (approx 4.29 billion) possibilities in under a second. But we can be smarter. We can use the Baby-Step Giant-Step (BSGS) algorithm.

BSGS is a "meet-in-the-middle" attack used to solve the Discrete Logarithm Problem. Usually, for a 256-bit curve, the search space is $2^{128}$ (square root of $2^{256}$), which is impossible. But since our search space is actually $2^{32}$, the complexity drops to $\sqrt{2^{32}} = 2^{16}$.

$2^{16}$ is 65,536. That is not a typo. To break this encryption, we only need to perform roughly 65,000 elliptic curve operations. A Python script running on a toaster could solve this instantly.

The Attack Path:

  1. Capture the Ciphertext ($C_1, C_2, C_3$). $C_1$ is the point $[k]G$.
  2. We know $k$ is small ($0 < k < 2^{32}$).
  3. Build a hash table of precomputed points (Baby Steps) for $i$ from $0$ to $2^{16}$.
  4. Check against the target $C_1$ using Giant Steps.
  5. Match found? You have $k$. Compute the shared secret $[k]P_B$ and XOR it with $C_2$. Message decrypted.

The Impact: Total Exposure

The impact here is catastrophic for confidentiality. Any data encrypted with versions 0.14.0-rc.0 or 0.14.0-pre.0 should be considered public domain. If you sent passwords, financial data, or state secrets using this crate, they are currently readable by anyone who saved the network traffic.

Furthermore, because the attack is so computationally cheap, this can be automated at scale. An attacker could record a terabyte of encrypted traffic and retrospectively decrypt all of it in minutes using a single server. There is no "forward secrecy" that saves you here if the ephemeral key itself is flawed.

While this specific report focuses on Public Key Encryption (PKE), if this next_k logic was reused in digital signatures (DSA), it would be even worse: an attacker could recover the private key from a few signatures, allowing them to impersonate the victim forever. For PKE, the damage is limited to the specific messages sent, but that is usually enough to end a company.

Mitigation: Patch and Rotate

The remediation is straightforward but urgent. You must upgrade the sm2 crate to a version that includes commit e4f77788130d065d760e57fb109370827110a525. The fix is available in the latest releases on crates.io.

However, patching the code doesn't fix the past. Because the encryption was fundamentally broken, you must assume all past communication is compromised. If this library was used to exchange long-term secrets (like API keys or root passwords), those secrets must be rotated immediately.

Developer Lesson: When working with cryptography, never manually handle bit/byte conversions if a higher-level abstraction exists. Use types like SecretKey, NonZeroScalar, or Box<[u8]> with strict typing. If you find yourself writing (bits + 7) / 8, stop and ask yourself if the library provides a generate() function. It usually does.

Fix Analysis (1)

Technical Appendix

CVSS Score
9.8/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

Affected Systems

Rust applications using the `sm2` crateSystems relying on SM2 encryption for data confidentialityFinancial or government systems using `sm2` crate versions 0.14.0-rc.0 / 0.14.0-pre.0

Affected Versions Detail

Product
Affected Versions
Fixed Version
sm2 (Rust Crate)
RustCrypto
= 0.14.0-rc.0Patch e4f777
sm2 (Rust Crate)
RustCrypto
= 0.14.0-pre.0Patch e4f777
AttributeDetail
CWE IDCWE-330 (Use of Insufficiently Random Values)
CVSS v3.19.8 (Critical)
Attack VectorNetwork
ImpactConfidentiality (High)
Key Entropy32 bits (Effective)
Exploit ComplexityLow (BSGS attack)
CWE-330
Use of Insufficiently Random Values

The product uses insufficiently random values that can be predicted by an attacker.

Known Exploits & Detection

Vulnerability Timeline

Vulnerability introduced in commit 4781762
2024-09-05
Discovered by XlabAI Team (Tencent) and Atuin Engine
2026-01-09
Patch released and Advisory Published
2026-01-09

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.