Skip to content

MLKEM-768 with AWS-LC#14598

Open
DarkaMaul wants to merge 21 commits intopyca:mainfrom
trail-of-forks:dm/mlkem-768
Open

MLKEM-768 with AWS-LC#14598
DarkaMaul wants to merge 21 commits intopyca:mainfrom
trail-of-forks:dm/mlkem-768

Conversation

@DarkaMaul
Copy link
Copy Markdown
Contributor

Add the support for ML-KEM using AWS-LC as the backend.

The PR is massive (sorry), about 1000 lines, but I could not reduce it more.

It follows closely the pattern of MLDSA, with the latest changes (class cases, variant enum)

A following PR with the documentation will be opened once this one get merged.

(Note: until aws/aws-lc#3140 is fixed, we have to store the ML-KEM seed on our side)

return rust_openssl.mlkem.from_mlkem768_public_bytes(data)

@abc.abstractmethod
def encapsulate(self) -> tuple[bytes, bytes]:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@reaperhulk WDYT?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This matches the go API

Comment on lines +143 to +145
// ML-KEM returns the seed rather than a PKey because AWS-LC's
// kem_priv_encode writes the expanded key (2,400 bytes) into PKCS#8,
// not the seed.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated it to clarify it.
I wanted to say that we can't use OpenSSL PKey construct here, so we need a second element in our ParsedPrivateKey enum.

}

pub fn serialize_private_key(key: &ParsedPrivateKey) -> crate::KeySerializationResult<Vec<u8>> {
let ParsedPrivateKey::Pkey(pkey) = key;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you refactor this out into its own PR for readability

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open as #14607

}

extern "C" {
fn EVP_PKEY_kem_new_raw_public_key(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need to declare these, they should just be available in ffi::

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, only EVP_PKEY_keygen_deterministic is needed because it is still in experimental/ in aws-lc.

IIUC, this is Algorithm 16 in FIPS 203.

Using this function allows us to use the seed form of the private key.

format: crate::serialization::PrivateFormat,
encryption_algorithm: &pyo3::Bound<'p, pyo3::PyAny>,
) -> CryptographyResult<pyo3::Bound<'p, pyo3::types::PyBytes>> {
if !encryption_algorithm.is_instance(&types::KEY_SERIALIZATION_ENCRYPTION.get(py)?)? {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we end up needing to re-implement a ton of the utility function whereas that wasn't required for MLDSA?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem here is that we can't represent the private key as an openssl PKey (AWS-LC's kem_priv_encode writes the expanded key into PKCS#8, not the seed), so we can't use pkey_private_bytes here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have another solution by refactoring pkey_private_bytes to:

  • accept the new enum ParsedPrivateKey as argument
  // Replace this line with a match {}
        let raw_bytes = pkey.raw_private_key()?;
        return Ok(pyo3::types::PyBytes::new(py, &raw_bytes));
  • Revert
        let parsed = cryptography_key_parsing::ParsedPrivateKey::Pkey(pkey.to_owned());
  • Gate before if format == PrivateFormat::TraditionalOpenSSL { to ensure we're not reaching this for MLKem keys

The main downside is that the refactor modifies every caller of the function from &slf.borrow().pkey, to ParsedPrivateKey::Pkey(slf.borrow().pkey.to_owned())

That would avoid the deduplication here (and the need for extensive tests for each variant of the encryption part)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the aws-lc change to store the seed on the pkey lands, we'll need neither of these changes, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct! Once we can represent MLKEM's key as PKey, all this complexity goes away

This was referenced Apr 8, 2026
pub enum ParsedPrivateKey {
Pkey(openssl::pkey::PKey<openssl::pkey::Private>),
#[cfg(CRYPTOGRAPHY_IS_AWSLC)]
MlKem(cryptography_openssl::mlkem::MlKemVariant, [u8; 64]),
Copy link
Copy Markdown
Contributor Author

@DarkaMaul DarkaMaul Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flag: this has changed from MlKem768 to MlKem to not have an overcomplex enum when we'll add the other variants.

Comment on lines +142 to +147
|| (encryption_algorithm.is_instance(&types::ENCRYPTION_BUILDER.get(py)?)?
&& encryption_algorithm
.getattr(pyo3::intern!(py, "_format"))?
.extract::<crate::serialization::PrivateFormat>()?
== format)
{
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like this branch currently isn't covered

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants