Skip to content

ml-kem: PKCS#8 support#135

Merged
tarcieri merged 12 commits intomasterfrom
ml-kem/pkcs8
Oct 25, 2025
Merged

ml-kem: PKCS#8 support#135
tarcieri merged 12 commits intomasterfrom
ml-kem/pkcs8

Conversation

@tarcieri
Copy link
Copy Markdown
Member

@tarcieri tarcieri commented Oct 25, 2025

This is #124, rebased on master, and modified to remove support for expanded decapsulation keys and the option to encode both the seed and the expanded decapsulation key, to discourage proliferation of these options which were added to sate HSM vendors who shipped expanded keys prior to the final FIPS 203 draft. We should not encourage the proliferation of their mistakes.

Additionally, the encoding of PKCS#8 private keys now includes only the seed, making them significantly smaller.

Closes #54

meisterluk and others added 12 commits September 15, 2025 11:52
• add dependency to pkcs8 and const-oid crate and define features pkcs8 & alloc
• implement AssociatedOid and AssociatedAlgorithmIdentifier
  for ML-KEM parameter sets {MlKem512Params,MlKem768Params,MlKem1024Params}
• implement AssociatedAlgorithmIdentifier
  for EncapsulationKey & DecapsulationKey
• serialization:
  implement EncodePublicKey for EncapsulationKey
  implement EncodePrivateKey for DecapsulationKey
• deserialization:
  implement TryFrom<pkcs8::SubjectPublicKeyInfoRef> for EncapsulationKey
  implement TryFrom<pkcs8::PrivateKeyInfoRef> for DecapsulationKey
• add testcase with DER serialization roundtrip

REMARK:
The encapsulation key is serialized into a Subject Public Key Info
as defined in RFC 5280 § 4.1.2.7 or more specifically for ML-KEM,
draft-ietf-lamps-kyber-certificates-latest from 2025-05-27 § 4:
https://lamps-wg.github.io/kyber-certificates/draft-ietf-lamps-kyber-certificates.html#name-subject-public-key-fields

The decapsulation key is serialized into a Private Key Info
as defined in RFC 5958 § 2 or more specifically for ML-KEM,
draft-ietf-lamps-kyber-certificates-latest from 2025-05-27 § 6:
https://lamps-wg.github.io/kyber-certificates/draft-ietf-lamps-kyber-certificates.html#name-private-key-format
• previously, the associated type Error was defined to be
  pkcs8::spki::Error instead of pkcs8::Error
• As a result, the blanket implementation
  """
  impl<T> DecodePrivateKey for T
  where
    T: for<'a> TryFrom<PrivateKeyInfoRef<'a>, Error = Error>,
  """
  https://docs.rs/pkcs8/0.11.0-rc.4/pkcs8/trait.DecodePrivateKey.html#impl-DecodePrivateKey-for-T
  was not effective and DecodePrivateKey was not implemented for
  DecapsulationKey
…PublicKey

• these traits are supported through blanket implementations
  and shall be tested as well
Let us consider the test vectors from this repository:

  https://github.com/lamps-wg/kyber-certificates/tree/624bcaa4bd9ea9e72de5b51d81ce2d90cbd7e54a

This implementation uses a seed of values [0, 1, …, 63].
Now let us generate the public and private keys.

My previous implementation generated an OCTET STRING of length 1632
for ML-KEM 512:

  $ openssl asn1parse -in ml-kem-512-private-key-generated.pem -dump | head -n6 | tail -n2
   20:d=1  hl=4 l=1632 prim: OCTET STRING
      0000 - 70 55 4f d4 36 34 4f 27-85 b1 b3 b1 ba c1 84 b6   pUO.64O'........

The IETF draft specification suggests a structure involving ASN.1
CHOICE to distinguish between seed, expanded, and both formats:

  https://lamps-wg.github.io/kyber-certificates/draft-ietf-lamps-kyber-certificates.html#name-private-key-format

This leads to four additional bytes:

  $ openssl asn1parse -in ML-KEM-512-expanded.priv -dump | head -n6 | tail -n2
   20:d=1  hl=4 l=1636 prim: OCTET STRING
      0000 - 04 82 06 60 70 55 4f d4-36 34 4f 27 85 b1 b3 b1   ...`pUO.64O'....

This implementation now introduces the CHOICE discriminator.

Recognize that the draft suggests serialization to the seed format per
default. Sadly we cannot implement this. The data stored in
EncapsulationKey does not store the seed which lead to this
encapsulation key. As a result, the expanded format is always used.

The previous text talked about changes in the serialization.
In terms of deserialization all three formats must be supported which
also I implemented.
We can re-add support for expanded decapsulation keys and "both" the
former and seeds if it ends up shipping in the final document.

For now though, we should discourage use of anything but seeds as
wasteful and needlessly complex (it was added to the draft to sate HSM
vendors who shipped expanded keys before the final FIPS 203 document and
who refuse to fix their mistakes).
@tarcieri
Copy link
Copy Markdown
Member Author

cc @meisterluk

@tarcieri tarcieri merged commit 14d311c into master Oct 25, 2025
39 checks passed
@tarcieri tarcieri deleted the ml-kem/pkcs8 branch October 25, 2025 14:48
@meisterluk
Copy link
Copy Markdown

LGTM. Cheers!

@tarcieri tarcieri mentioned this pull request Apr 28, 2026
tarcieri added a commit that referenced this pull request Apr 28, 2026
## Added
- `Seed` support e.g. `DecapsulationKey::from_seed` (#133, #138)
- PKCS#8 support (#135)
- `KeyInit`, `KeySizeUser`, and `KeyExport` impls for decapsulation keys
  (#156, #228)
- Parameter set modules: `ml_kem_512`, `mk_kem_768`, `mk_kem_1024`
  (#162)
- `DecapsulationKey::from_expanded` deprecated compatibility support
  (#163)
- `TryKeyInit` and `KeyExport` impls for encapsulation keys (#188)
- Validations against Wycheproof test vectors (#213, #214, #215,
  #217)
- Implement `kem::Kem` trait (#223)
- Support for `kem::FromSeed` trait (#255)

## Changed
- Edition changed to 2024 and MSRV bumped to 1.85 (#118)
- Relax MSRV policy and allow MSRV bumps in patch releases
- Upgrade `hybrid-array` dependency to 0.4 (#129)
- Extract `module-lattice` crate (#199, #202, #204, #209,
  #210, #211, #212, #218, #219, #220)
- Replace `EncodedSizeUser` with `ExpandedKeyEncoding` (#226)
- Bump `getrandom` to v0.4 (#245)
- Bump `rand_core` to v0.10 (#245)
- Migrate from `subtle` to `ctutils` (#277)
- Bump `sha3` dependency to v0.11 (#282)
- Bump `kem` dependency to v0.3 (#283)
- Bump `pkcs8` dependency to v0.11 (#291)

## Fixed
- Validate encryption/encapsulation keys (#179)
- Validate expanded decapsulation key hash (#207)

## Removed
- `Kem` struct and `KemCore` trait - replaced by `kem::Kem` (#223)
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.

ml-kem: PKCS#8 support

2 participants