Skip to content

feat(ml-kem): add zeroize support and tests#1332

Open
rainmitch wants to merge 1 commit intocryspen:mainfrom
rainmitch:rainmitch/ml-kem-zeroize
Open

feat(ml-kem): add zeroize support and tests#1332
rainmitch wants to merge 1 commit intocryspen:mainfrom
rainmitch:rainmitch/ml-kem-zeroize

Conversation

@rainmitch
Copy link

@rainmitch rainmitch commented Feb 14, 2026

Description:

This PR introduces memory hardening for ML-KEM secret keys by implementing the Zeroize and ZeroizeOnDrop traits. The goal is to ensure sensitive key material is securely wiped from memory when it goes out of scope. This facilitates FIPS 140-3 compliance (AS05.10) regarding the zeroization of plaintext secret keys.

Scope:

This implementation focuses on the secret-holding structs and their underlying vector types. It does not modify the underlying core logic or seed inputs (which very likely needs more work to fully implement zeroize). I believe this covers all primary secret-holding structs in a non-intrusive way required for memory hardening.

This is implemented as an optional feature that requires being enabled.

Changes:

I have implemented Zeroize for the following types:

  • High-Level Keys: MlKemPrivateKey, MlKemKeyPair
  • Internal Components: PolynomialRingElement
  • Unpacked Variants: MlKemPrivateKeyUnpacked, IndCpaPrivateKeyUnpacked, MlKemKeyPairUnpacked
  • Vector Backends: PortableVector, SIMD128Vector, SIMD256Vector

I have implemented ZeroizeOnDrop for the following types:

  • MlKemPrivateKey, MlKemKeyPair
  • PolynomialRingElement
  • MlKemPrivateKeyUnpacked, IndCpaPrivateKeyUnpacked, MlKemKeyPairUnpacked

Note: Vector types do not implement ZeroizeOnDrop to avoid performance penalties in non-secret contexts; they are zeroed explicitly by their parent structs.

Technical Verification:

To ensure the security features are not optimized away by the compiler, I performed the following checks:

  • Hax Compatibility: All zeroize logic is gated behind #[cfg(not(hax))] to prevent interference with the formal verification pipeline.
  • Dead Store Elimination (DSE): Used core::hint::black_box in SIMD implementations and testing to force the compiler to treat the memory as "used," preventing it from deleting the zeroing instructions.

Assembly Audit:

  • SIMD128Vector test: Verified that stp (Store Pair) instructions are generated for clearing, and vmaxvq is used for zero-verification in tests.
  • SIMD256Vector test: Verified that vmovdqa is generated for clearing, and vptest is used for zero-verification.

Performance:

Benchmarks show a negligible performance impact (performed on a Ryzen 5 7430u):

Operation Key Size Platform API Ratio Zeroize-Enabled Original
Decapsulation 1024 AVX2 Standard 1.00 18.8±0.18µs 18.8±0.17µs
Decapsulation 1024 AVX2 Unpacked 1.01 11.7±0.09µs 11.6±0.11µs
Decapsulation 1024 Portable Standard 1.02 55.7±0.65µs 54.5±0.63µs
Decapsulation 1024 Portable Unpacked 1.00 35.0±0.31µs 35.2±0.39µs
Encapsulation 1024 AVX2 Ext. Random 1.03 17.4±0.30µs 16.9±0.17µs
Encapsulation 1024 AVX2 Unp. (Ext. Rand) 1.00 6.3±0.17µs 6.3±0.20µs
Encapsulation 1024 Portable Ext. Random 1.00 47.9±0.68µs 47.9±0.63µs
Encapsulation 1024 Portable Unp. (Ext. Rand) 1.00 24.5±0.30µs 24.9±0.22µs
Key Generation 1024 AVX2 Ext. Random 1.00 15.1±0.20µs 15.0±0.19µs
Key Generation 1024 AVX2 Unp. (Ext. Rand) 1.02 15.0±0.28µs 14.7±0.22µs
Key Generation 1024 Portable Ext. Random 1.02 42.0±0.52µs 41.3±0.69µs
Key Generation 1024 Portable Unp. (Ext. Rand) 1.01 41.9±0.60µs 41.6±0.71µs
PK Validation 1024 AVX2 Standard 1.00 539.1±8.32ns 543.7±9.46ns
PK Validation 1024 Portable Standard 1.00 1240.0±13.17ns 1263.6±22.09ns
--- --- --- --- --- --- ---
Decapsulation 512 AVX2 Standard 1.00 9.2±0.15µs 9.3±0.08µs
Decapsulation 512 AVX2 Unpacked 1.01 6.4±0.05µs 6.3±0.05µs
Decapsulation 512 Portable Standard 1.00 22.6±0.21µs 22.6±0.25µs
Decapsulation 512 Portable Unpacked 1.00 17.1±0.28µs 17.1±0.28µs
Encapsulation 512 AVX2 Ext. Random 1.10 9.3±0.16µs 8.5±0.11µs
Encapsulation 512 AVX2 Unp. (Ext. Rand) 1.02 3.9±0.06µs 3.8±0.04µs
Encapsulation 512 Portable Ext. Random 1.04 19.0±0.14µs 18.4±0.24µs
Encapsulation 512 Portable Unp. (Ext. Rand) 1.02 11.9±0.09µs 11.7±0.16µs
Key Generation 512 AVX2 Ext. Random 1.02 8.2±0.23µs 8.0±0.09µs
Key Generation 512 AVX2 Unp. (Ext. Rand) 1.02 7.9±0.28µs 7.8±0.11µs
Key Generation 512 Portable Ext. Random 1.00 15.6±0.22µs 15.8±0.18µs
Key Generation 512 Portable Unp. (Ext. Rand) 1.00 15.4±0.19µs 15.6±0.16µs
PK Validation 512 AVX2 Standard 1.00 265.8±4.79ns 273.2±16.21ns
PK Validation 512 Portable Standard 1.00 622.3±14.28ns 632.7±13.59ns
--- --- --- --- --- --- ---
Decapsulation 768 AVX2 Standard 1.02 13.2±0.16µs 12.9±0.13µs
Decapsulation 768 AVX2 Unpacked 1.01 8.2±0.07µs 8.1±0.06µs
Decapsulation 768 Portable Standard 1.01 37.2±0.36µs 36.8±0.51µs
Decapsulation 768 Portable Unpacked 1.00 24.9±0.21µs 25.0±0.18µs
Encapsulation 768 AVX2 Ext. Random 1.01 12.1±0.21µs 12.0±0.13µs
Encapsulation 768 AVX2 Unp. (Ext. Rand) 1.00 4.5±0.04µs 4.6±0.05µs
Encapsulation 768 Portable Ext. Random 1.01 31.8±0.38µs 31.3±0.30µs
Encapsulation 768 Portable Unp. (Ext. Rand) 1.04 18.2±1.81µs 17.5±0.13µs
Key Generation 768 AVX2 Ext. Random 1.01 11.0±0.17µs 11.0±0.13µs
Key Generation 768 AVX2 Unp. (Ext. Rand) 1.01 10.9±0.14µs 10.8±0.09µs
Key Generation 768 Portable Ext. Random 1.03 27.0±0.28µs 26.3±0.33µs
Key Generation 768 Portable Unp. (Ext. Rand) 1.01 26.5±0.33µs 26.2±0.31µs
PK Validation 768 AVX2 Standard 1.01 411.0±28.60ns 405.2±6.72ns
PK Validation 768 Portable Standard 1.01 977.1±14.22ns 970.5±6.81ns

Closing Notes:

Note: I am not a security programmer or researcher. I have tried to implement this to the best of my abilities, verifying the assembly output to ensure the compiler respects the zeroing instructions. I really like libcrux and wanted to use it for a project, but also need zeroize functionality, so I worked to try to implement it. I have tried to do my due diligence in checking and design. I would very much appreciate someone double checking my work, especially as this is my first contribution to a project. But I believe that this works.

I would greatly appreciate feedback and comments on anything that needs to be changed.

@rainmitch rainmitch requested review from a team as code owners February 14, 2026 19:34
@rainmitch rainmitch requested a review from keks February 14, 2026 19:34
@rainmitch rainmitch force-pushed the rainmitch/ml-kem-zeroize branch from d2bb7f8 to b99428c Compare February 14, 2026 21:59
@rainmitch
Copy link
Author

Just a heads-up. I've also successfully applied this same hardening pattern to the ML-DSA module locally. I'll open a separate PR for that one if these changes are wanted and once I have feedback on this PR and on your preferences.

@jschneider-bensch
Copy link
Collaborator

Thank you for the effort on this!
I'll make sure to give it a full review this week. From skimming I think it could be a nice addition, but I'll have to think about whether we want to add another cargo feature to libcrux-ml-kem, as there are quite a few already.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments