CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Dashboard
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-22698
8.70.04%

Lost in Translation: How a Unit Mismatch Nuked SM2 Encryption in RustCrypto

Alon Barad
Alon Barad
Software Engineer

Feb 23, 2026·7 min read·5 visits

PoC Available

Executive Summary (TL;DR)

A 'bits vs. bytes' logic error in the Rust `sm2` crate caused ephemeral keys (nonces) to be generated with only 32 bits of entropy instead of 256. This makes recovering the private key and decrypting traffic trivial using basic algorithms like Baby-Step Giant-Step.

Cryptography is widely regarded as 'hard', but usually for the wrong reasons. We assume the math is the killer, but often, it's simple plumbing—like confusing inches with centimeters. CVE-2026-22698 is the cryptographic equivalent of the Mars Climate Orbiter crash: a catastrophic unit mismatch in the RustCrypto `sm2` library that reduced 256-bit military-grade encryption down to a 32-bit toy puzzle. By passing a byte count into a function expecting a bit count, the random number generator produced ephemeral keys that are trivially solvable on a modern laptop in milliseconds.

The Hook: When 256 Equals 32

In the world of Elliptic Curve Cryptography (ECC), the security of the entire system often hangs by a single thread: the randomness of the ephemeral nonce, usually denoted as $k$. Whether you are signing a transaction or encrypting a secret message, $k$ must be a uniformly random number typically between $1$ and the curve order $n$ (for standard curves, this is roughly $2^{256}$). If an attacker can guess $k$, or if $k$ is biased, the math falls apart. We've seen this before with the Sony PS3 hack and various Bitcoin wallet disasters.

Enter CVE-2026-22698. This vulnerability affects the sm2 crate within the RustCrypto ecosystem. SM2 is the Chinese National Standard for elliptic curve cryptography, functionally similar to ECDSA and ECIES. It is designed to provide 128-bit security levels, backed by a 256-bit curve. Rust is famous for its memory safety, promising to save us from buffer overflows and use-after-free bugs. But Rust's compiler cannot save you from logic errors, especially when the developer confuses the map for the territory—or in this case, the byte for the bit.

The vulnerability is shockingly simple. The implementation intended to generate a 256-bit random number. Instead, due to a semantic confusion in the arguments passed to the random number generator, it requested only 32 bits of randomness. The resulting numbers were technically 256-bit integers (U256), but the top 224 bits were always zero. This is the cryptographic equivalent of buying a bank vault door but leaving the combination set to '1-2-3-4'.

The Flaw: The Mars Climate Orbiter of Crypto

To understand the gravity of this screw-up, we have to look at how the encryption nonce $k$ was generated in sm2/src/pke/encrypting.rs. The developer needed to know how many random bits to fetch. The SM2 curve is 256 bits long, which corresponds to 32 bytes. The code calculated the byte length correctly using a constant named N_BYTES.

However, the helper function responsible for fetching the randomness, next_k, was defined to accept a bit_length as its argument, not a byte length. When the code called next_k(N_BYTES), it was effectively asking the RNG: 'Please give me a random number that is 32 bits long.' The RNG obliged. It returned a number where the first 32 bits were random, and the remaining bits were effectively null.

This is a classic 'Unit Mismatch' error. In engineering, this crashes satellites. In cryptography, it turns 'Computationally Infeasible' into 'Instant'. The entropy of the system, which should have been $2^{256}$, was instantaneously reduced to $2^{32}$. To put that in perspective: $2^{256}$ is roughly the number of atoms in the observable universe. $2^{32}$ is about 4 billion—a number your GPU can count to while you are waiting for your coffee to brew.

The Code: The Smoking Gun

Let's look at the autopsy. The vulnerability resided in the interaction between the encrypt function and the next_k helper. Here is the vulnerable logic that shipped in versions like 0.14.0-rc.0:

// Vulnerable Code Logic
 
// 1. Calculate the number of BYTES in the curve order (256 bits = 32 bytes)
const N_BYTES: u32 = (Sm2::ORDER.bits() + 7) / 8; // Evaluates to 32
 
// 2. Call the helper function passing 32
let k = Scalar::from_uint(next_k(N_BYTES)).unwrap();
 
// ... elsewhere ...
 
fn next_k(bit_length: u32) -> U256 {
    loop {
        // 3. The RNG is asked for 'bit_length' bits. 
        // Since we passed 32, we get 32 bits of randomness.
        let k = U256::random_bits(&mut rand_core::OsRng, bit_length);
        
        // The check passes because k is small and definitely < Sm2::ORDER
        if !bool::from(k.is_zero()) && k < Sm2::ORDER {
            return k;
        }
    }
}

The fix was straightforward: abandon the manual bit/byte gymnastics and use the type-safe, curve-aware generation methods provided by the library traits. This prevents the user from manually specifying lengths altogether.

// Patched Code (Commit e4f7778)
// No more manual bit counting. We just ask the curve for a scalar.
let k = NonZeroScalar::try_generate_from_rng(rng)
    .map_err(|_| Error)?;

This change ensures that k is generated using the full order of the curve, restoring the full 256 bits of entropy.

The Exploit: Cracking it in Milliseconds

So, we have an encryption scheme where the ephemeral key $k$ is chosen from the range $[1, 2^{32}]$. How does an attacker weaponize this? In SM2 Public Key Encryption, the ciphertext consists of three parts: $C1$, $C2$, and $C3$. The critical component is $C1$, which is the ephemeral public key computed as $C1 = [k]G$ (where $G$ is the generator point).

The attacker observes $C1$ on the wire. Their goal is to find $k$. This is the Elliptic Curve Discrete Logarithm Problem (ECDLP). Normally, this is hard. But because we know $k$ is tiny ($< 2^{32}$), we don't need fancy math; brute force would literally work. A single modern CPU core can perform millions of curve operations per second. Iterating through 4 billion possibilities is trivial.

However, a sophisticated attacker would use the Baby-Step Giant-Step (BSGS) algorithm or Pollard's Rho.

The Attack Chain:

  1. Capture $C1$: Extract the point $[k]G$ from the ciphertext.
  2. Solve for $k$:
    • Set $m = \lceil \sqrt{2^{32}} \rceil = 65536$.
    • Compute a table of $i \times G$ for $0 \le i < m$ (Baby Steps).
    • Compute $C1 - j \times m \times G$ (Giant Steps) and check for a collision in the table.
    • This reduces the complexity to $O(\sqrt{N})$, meaning we only need about $65,536$ operations. This is instantaneous.
  3. Recover Shared Secret: Compute $S = [k]P_B$ (where $P_B$ is the victim's public key).
  4. Decrypt: Derive the decryption key from $S$ and decrypt $C2$ to get the plaintext.

This exploit is 100% reliable. There is no noise, no race condition, and no guessing. If the traffic was encrypted with a vulnerable version of this library, it is essentially plaintext to anyone who knows how to write a Python script.

The Impact: Why We Panic

The impact here is total confidentiality loss for any data encrypted using this implementation. SM2 is often used in environments requiring compliance with Chinese standards (GB/T), which can include financial services, automotive controls, and critical infrastructure communication in specific regions.

While this vulnerability is in a specific Rust crate (sm2) and not the standard itself, the elliptic-curves ecosystem in Rust is the bedrock for many modern applications looking to move away from C/C++ implementations like OpenSSL. A vulnerability here shakes trust in the 'Rewrite it in Rust' narrative—not because Rust failed, but because it proves that memory safety is not a silver bullet for logic flaws.

Furthermore, if this randomness generation logic was copy-pasted into signature generation (which shares similar nonce requirements), it would allow for immediate private key recovery from a handful of signatures. Fortunately, this specific CVE seems scoped to Public Key Encryption (PKE), limiting the damage to data decryption rather than identity theft—though in many contexts, those are equally devastating.

The Fix: Remediation

If you are using the sm2 crate, you need to check your Cargo.lock immediately. If you see versions 0.14.0-pre.0 or 0.14.0-rc.0, you are vulnerable. The fix was merged in commit e4f7778 and is available in subsequent releases.

Remediation Steps:

  1. Run cargo update -p sm2.
  2. Verify the version is patched.
  3. Rotate Keys? Technically, this is an encryption flaw, not a key generation flaw. Your long-term private keys aren't compromised by the act of decrypting, but any data you encrypted in the past is compromised. If you used this library to generate your long-term keys (and if the random generation logic was shared), you MUST rotate keys. If it was only used for encryption, the keys are safe, but the data is not.

Developer Lesson: Avoid 'magic numbers' and manual type conversions for critical parameters. Use Strong Types. If the function expected BitLength, a u32 alias or a newtype wrapper could have prevented passing a byte count. Better yet, use high-level traits like TryGenerateFromRng that encapsulate the complexity of curve orders entirely.

Official Patches

GitHubPull Request #1600 fixing the entropy issue

Fix Analysis (1)

Technical Appendix

CVSS Score
8.7/ 10
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N
EPSS Probability
0.04%
Top 87% most exploited

Affected Systems

RustCrypto sm2 crate usersSystems implementing SM2-PKE via RustCrypto

Affected Versions Detail

Product
Affected Versions
Fixed Version
sm2
RustCrypto
= 0.14.0-pre.0Post-0.14.0-rc.0 dev branch
sm2
RustCrypto
= 0.14.0-rc.0Post-0.14.0-rc.0 dev branch
AttributeDetail
CWE IDCWE-331 (Insufficient Entropy)
Attack VectorNetwork (Passive/Active)
CVSS v4.08.7 (High)
Entropy Reduction256 bits -> 32 bits
Exploit ComplexityTrivial (O(2^16) ops)
Patch StatusReleased

MITRE ATT&CK Mapping

T1040Network Sniffing
Credential Access
T1557Adversary-in-the-Middle
Credential Access
CWE-331
Insufficient Entropy

The software uses an algorithm or scheme that produces insufficient entropy, leaving data or keys vulnerable to brute-force attacks.

Known Exploits & Detection

HypotheticalBaby-Step Giant-Step algorithm can solve for k in ~65k operations.
NucleiDetection Template Available

Vulnerability Timeline

Vulnerable code introduced in commit 4781762f
2024-09-05
Fix commit e4f77788 pushed to repository
2026-01-09
GHSA-w3g8-fp6j-wvqw published
2026-01-10
CVE-2026-22698 assigned
2026-01-10

References & Sources

  • [1]NVD Entry
  • [2]GitHub Security Advisory

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.