Skip to content

[API Proposal] Composite ML-KEM #129633

Description

@PranavSenthilnathan

Background and motivation

Composite ML-KEM is a Post-Quantum Cryptography key encapsulation mechanism that combines ML-KEM with a traditional key agreement algorithm (ECDH or X25519/X448). The latest draft specification is: https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-kem

This follows the same composite design philosophy as Composite ML-DSA (#118320), providing hybrid post-quantum security for key establishment. Both component algorithms must succeed for the combined operation to succeed, ensuring security even if one algorithm is compromised.

Related: #114453 (ML-KEM, api-approved), #118320 (Composite ML-DSA, api-approved)

API Proposal

The API follows the pattern established by CompositeMLDsa (#118320) for the composite type, and MLKem (#114453) for the KEM operations (encapsulate/decapsulate).

namespace System.Security.Cryptography
{
    // System.Security.Cryptography and Microsoft.Bcl.Cryptography
    [Experimental("SYSLIB5006")]
    public abstract class CompositeMLKem : IDisposable
    {
        protected CompositeMLKem(CompositeMLKemAlgorithm algorithm);

        public static bool IsAlgorithmSupported(CompositeMLKemAlgorithm algorithm);

        public CompositeMLKemAlgorithm Algorithm { get; }

        public byte[] Decapsulate(byte[] ciphertext);
        public void Decapsulate(ReadOnlySpan<byte> ciphertext, Span<byte> sharedSecret);
        protected abstract void DecapsulateCore(ReadOnlySpan<byte> ciphertext, Span<byte> sharedSecret);

        public void Encapsulate(out byte[] ciphertext, out byte[] sharedSecret);
        public void Encapsulate(Span<byte> ciphertext, Span<byte> sharedSecret);
        protected abstract void EncapsulateCore(Span<byte> ciphertext, Span<byte> sharedSecret);

        public byte[] ExportDecapsulationKey();
        public int ExportDecapsulationKey(Span<byte> destination);
        public bool TryExportDecapsulationKey(Span<byte> destination, out int bytesWritten);
        protected abstract int ExportDecapsulationKeyCore(Span<byte> destination);

        public byte[] ExportEncapsulationKey();
        public int ExportEncapsulationKey(Span<byte> destination);
        public bool TryExportEncapsulationKey(Span<byte> destination, out int bytesWritten);
        protected abstract int ExportEncapsulationKeyCore(Span<byte> destination);

        public static CompositeMLKem GenerateKey(CompositeMLKemAlgorithm algorithm);

        public static CompositeMLKem ImportEncapsulationKey(CompositeMLKemAlgorithm algorithm, ReadOnlySpan<byte> source);
        public static CompositeMLKem ImportEncapsulationKey(CompositeMLKemAlgorithm algorithm, byte[] source);

        public static CompositeMLKem ImportDecapsulationKey(CompositeMLKemAlgorithm algorithm, ReadOnlySpan<byte> source);
        public static CompositeMLKem ImportDecapsulationKey(CompositeMLKemAlgorithm algorithm, byte[] source);

        public void Dispose();
        protected virtual void Dispose(bool disposing);

        // PKCS#8 / SPKI / PEM import/export
        public bool TryExportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBytes, PbeParameters pbeParameters, Span<byte> destination, out int bytesWritten);
        public bool TryExportEncryptedPkcs8PrivateKey(ReadOnlySpan<char> password, PbeParameters pbeParameters, Span<byte> destination, out int bytesWritten);
        public bool TryExportEncryptedPkcs8PrivateKey(string password, PbeParameters pbeParameters, Span<byte> destination, out int bytesWritten);

        public byte[] ExportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBytes, PbeParameters pbeParameters);
        public byte[] ExportEncryptedPkcs8PrivateKey(ReadOnlySpan<char> password, PbeParameters pbeParameters);
        public byte[] ExportEncryptedPkcs8PrivateKey(string password, PbeParameters pbeParameters);

        public string ExportEncryptedPkcs8PrivateKeyPem(ReadOnlySpan<byte> passwordBytes, PbeParameters pbeParameters);
        public string ExportEncryptedPkcs8PrivateKeyPem(ReadOnlySpan<char> password, PbeParameters pbeParameters);
        public string ExportEncryptedPkcs8PrivateKeyPem(string password, PbeParameters pbeParameters);

        public byte[] ExportPkcs8PrivateKey();
        public string ExportPkcs8PrivateKeyPem();
        public bool TryExportPkcs8PrivateKey(Span<byte> destination, out int bytesWritten);
        protected abstract bool TryExportPkcs8PrivateKeyCore(Span<byte> destination, out int bytesWritten);

        public byte[] ExportSubjectPublicKeyInfo();
        public string ExportSubjectPublicKeyInfoPem();
        public bool TryExportSubjectPublicKeyInfo(Span<byte> destination, out int bytesWritten);

        public static CompositeMLKem ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBytes, ReadOnlySpan<byte> source);
        public static CompositeMLKem ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<char> password, ReadOnlySpan<byte> source);
        public static CompositeMLKem ImportEncryptedPkcs8PrivateKey(string password, byte[] source);

        public static CompositeMLKem ImportFromEncryptedPem(ReadOnlySpan<char> source, ReadOnlySpan<byte> passwordBytes);
        public static CompositeMLKem ImportFromEncryptedPem(ReadOnlySpan<char> source, ReadOnlySpan<char> password);
        public static CompositeMLKem ImportFromEncryptedPem(string source, byte[] passwordBytes);
        public static CompositeMLKem ImportFromEncryptedPem(string source, string password);

        public static CompositeMLKem ImportFromPem(ReadOnlySpan<char> source);
        public static CompositeMLKem ImportFromPem(string source);

        public static CompositeMLKem ImportPkcs8PrivateKey(byte[] source);
        public static CompositeMLKem ImportPkcs8PrivateKey(ReadOnlySpan<byte> source);

        public static CompositeMLKem ImportSubjectPublicKeyInfo(byte[] source);
        public static CompositeMLKem ImportSubjectPublicKeyInfo(ReadOnlySpan<byte> source);
    }
    
    // System.Security.Cryptography and Microsoft.Bcl.Cryptography
    [Experimental("SYSLIB5006")]
    public sealed class CompositeMLKemAlgorithm : IEquatable<CompositeMLKemAlgorithm>
    {
        internal CompositeMLKemAlgorithm();

        public static CompositeMLKemAlgorithm MLKem768WithRsaOaep2048 { get; }
        public static CompositeMLKemAlgorithm MLKem768WithRsaOaep3072 { get; }
        public static CompositeMLKemAlgorithm MLKem768WithRsaOaep4096 { get; }
        public static CompositeMLKemAlgorithm MLKem768WithX25519 { get; }
        public static CompositeMLKemAlgorithm MLKem768WithECDiffieHellmanP256 { get; }
        public static CompositeMLKemAlgorithm MLKem768WithECDiffieHellmanP384 { get; }
        public static CompositeMLKemAlgorithm MLKem768WithECDiffieHellmanBrainpoolP256r1 { get; }

        public static CompositeMLKemAlgorithm MLKem1024WithRsaOaep3072 { get; }
        public static CompositeMLKemAlgorithm MLKem1024WithECDiffieHellmanP384 { get; }
        public static CompositeMLKemAlgorithm MLKem1024WithECDiffieHellmanBrainpoolP384r1 { get; }
        public static CompositeMLKemAlgorithm MLKem1024WithX448 { get; }
        public static CompositeMLKemAlgorithm MLKem1024WithECDiffieHellmanP521 { get; }

        public string Name { get; }

        public int CiphertextSizeInBytes { get; }
        public int SharedSecretSizeInBytes { get; }

        public bool Equals([NotNullWhen(true)] CompositeMLKemAlgorithm? other);
        public override bool Equals([NotNullWhen(true)] object? obj);
        public override int GetHashCode();
        public override string ToString();

        public static bool operator ==(CompositeMLKemAlgorithm? left, CompositeMLKemAlgorithm? right);
        public static bool operator !=(CompositeMLKemAlgorithm? left, CompositeMLKemAlgorithm? right);
    }

    // System.Security.Cryptography and Microsoft.Bcl.Cryptography
    [Experimental("SYSLIB5006")]
    [SupportedOSPlatform("windows")]
    public sealed partial class CompositeMLKemCng : CompositeMLKem
    {
        public CompositeMLKemCng(CngKey key);

        public CngKey GetKey();
    }
}

namespace System.Security.Cryptography.X509Certificates
{
    // System.Security.Cryptography only
    public sealed partial class PublicKey
    {
        [Experimental("SYSLIB5006")]
        public PublicKey(CompositeMLKem key);

        [Experimental("SYSLIB5006")]
        [UnsupportedOSPlatform("browser")]
        public CompositeMLKem? GetCompositeMLKemPublicKey();
    }

    // System.Security.Cryptography only
    public partial class X509Certificate2
    {
        [Experimental("SYSLIB5006")]
        public CompositeMLKem? GetCompositeMLKemPublicKey();

        [Experimental("SYSLIB5006")]
        public CompositeMLKem? GetCompositeMLKemPrivateKey();

        [Experimental("SYSLIB5006")]
        public X509Certificate2 CopyWithPrivateKey(CompositeMLKem privateKey);
    }

    // Microsoft.Bcl.Cryptography only
    public static partial class X509CertificateKeyAccessors
    {
        [Experimental("SYSLIB5006")]
        public static CompositeMLKem? GetCompositeMLKemPublicKey(this X509Certificate2 certificate);

        [Experimental("SYSLIB5006")]
        public static CompositeMLKem? GetCompositeMLKemPrivateKey(this X509Certificate2 certificate);

        [Experimental("SYSLIB5006")]
        public static X509Certificate2 CopyWithPrivateKey(this X509Certificate2 certificate, CompositeMLKem privateKey);
    }
}

Open questions

Key size properties: MLKem exposes exact key sizes as properties (e.g. EncapsulationKeySizeInBytes). CompositeMLDsa has variable, bounded key sizes but doesn't expose these values (yet). The reasoning was that we can add it later if needed. We should decide which approach to take for Composite ML-KEM. The asterisked values below are max sizes instead of exact sizes:

Algorithm Public Key Private Key Ciphertext Shared Secret
MLKem768WithRsaOaep2048 1454* 1258* 1344 32
MLKem768WithRsaOaep3072 1582* 1834* 1472 32
MLKem768WithRsaOaep4096 1710* 2415* 1600 32
MLKem768WithX25519 1216 96 1120 32
MLKem768WithECDiffieHellmanP256 1249 115 1153 32
MLKem768WithECDiffieHellmanP384 1281 128 1185 32
MLKem768WithECDiffieHellmanBrainpoolP256r1 1249 116 1153 32
MLKem1024WithRsaOaep3072 1966* 1834* 1952 32
MLKem1024WithECDiffieHellmanP384 1665 128 1665 32
MLKem1024WithECDiffieHellmanBrainpoolP384r1 1665 132 1665 32
MLKem1024WithX448 1624 120 1624 32
MLKem1024WithECDiffieHellmanP521 1701 146 1701 32

CNG classes: This assumes Windows will support Composite ML-KEM in NCrypt. Composite ML-KEM support in BCrypt has already shipped in a preview, so it seems likely.

Activity

  1. added this to the 11.0.0 milestone on Jun 19, 2026
  2. dotnet-policy-service commented on Jun 19, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @bartonjs, @vcsjones, @dotnet/area-system-security
    See info in area-owners.md if you want to be subscribed.

  3. bartonjs commented on Jun 19, 2026

    @bartonjs
    Member

    public byte[] ExportDecapsulationKey();

    I'm of two minds here. For ML-KEM that's what the spec called the private key, e.g.

    Algorithm 19 ML-KEM.KeyGen()
    Generates an encapsulation key and a corresponding decapsulation key.
    Output: encapsulation key ek ∈ 𝔹384𝑘+32 .
    Output: decapsulation key dk ∈ 𝔹768𝑘+96 .

    For Composite ML-KEM they're just called "public key" and "private key" or "composite public" and (by extension) "composite private"

    Key Generation Process:

    1. Generate component keys

    mlkemSeed = Random(64)
    (mlkemPK, mlkemSK) = ML-KEM.KeyGen_internal(
    mlkemSeed[:32],
    mlkemSeed[32:] )
    (tradPK, tradSK) = Trad.KeyGen()

    1. Check for component key gen failure
      if NOT (mlkemPK, mlkemSK) or NOT (tradPK, tradSK):
      output "Key generation error"

    2. Output the composite public and private keys

    pk = SerializePublicKey(mlkemPK, tradPK)
    sk = SerializePrivateKey(mlkemSeed, tradSK)
    return (pk, sk)

    Likening it to Composite ML-DSA, that might mean the spec-correctest answer is ExportCompositeMLKemPublicKey and ExportCompositeMLKemPrivateKey.

    On the other hand, encapsulation and decapsulation do describe their role, and a sort of... primitiveness.

    public static bool IsSupported { get; } and public static bool IsAlgorithmSupported(CompositeMLKemAlgorithm algorithm);

    For HPKE the current thinking is to get rid of the big "IsSupported" and only have the one with HpkeSuite... but I think that's because so long as we provide a managed fallback on environments that the OS can't offload for us DHKEM_P256_HKDF_SHA256_AES_128_GCM will always work, and thus IsSupported is just "!browser" (okay, so not "always", but "always").

    Here it's less universal, since we need MLKem.IsSupported to be true, which isn't guaranteed. I could go either way.

    [Experimental("SYSLIB5006")]

    I was expecting we'd close 5006 this year, but since we're still at one provider for SLH-DSA, at least that one stays Experimental... and HashML-DSA is A Thing. So, yeah, 5006 is probably reasonable for this. Since it's currently still at draft stage we should expect Experimental.

    MaxEncapsulationKeySizeInBytes

    The RSA keys still mess this up. If the spec were authoritative and said only e=65537 was allowed, then sure. I don't want to add API that calls something "Max" and then it be possible to violate that max, and also don't want to make keys not exportable because they violate the max.

  4. vcsjones commented on Jun 19, 2026

    @vcsjones
    Member

    I'm of two minds here. For ML-KEM that's what the spec called the private key, e.g.

    I think that is largely because this is being driven by IETF and they are sticking with their terms.

    Private and Public are widely just considered bad terms these days - that's precisely why FIPS 203 avoided it.

    In order to reduce confusion, this standard will not use the terms “public key” or “private key.” Instead, keys will be referred to only using the more specific terms, i.e., one of “encapsulation key”, “decapsulation key”, “encryption key”, “decryption key”, and “shared secret key”.

    It's still a KEM, and I think NIST and the broader cryptography community have good reasons for avoiding public and private key as terminology. The SPKI and PKCS#8 are unfortunate, but those terms have existed for many years.

  5. PranavSenthilnathan commented on Jun 19, 2026

    @PranavSenthilnathan
    MemberAuthor

    Removed MaxEncapsulationKeySizeInBytes and changed the core method to ExportEncapsulationKeyCore instead of TryExportEncapsulationKeyCore - like CompositeMLDsa it would give the implementor a sufficiently large buffer. IIRC we wanted to avoid cases where incorrect implementations wrote off the end of an insufficiently large buffer by mistake.

    Removing the property makes the public int ExportDecapsulationKey(Span<byte> destination); overload harder to use since the user won't know the buffer size to provide. We can consider removing that overload since the TryExportDecapsulationKey does a similar operation although it's not as compositional (e.g. can't do span.Slice(0, ExportDecapsulationKey(span))).

    Removed IsSupported based on the managed implementation argument.

    I'd prefer the encapsulation/decapsulation terminology to be internally consistent with ML-KEM, but we can decide this in the API design review meeting.

  6. vcsjones commented on Jun 19, 2026

    @vcsjones
    Member

    namespace System.Security.Cryptography.X509Certificates

    I don't know how much sense it makes to put any of this on PublicKey or X509Certificate2. Remember that hybrids / composites are here mostly to support a transitionary time. Ideally, composites wouldn't even exist. The only reason they do is to hedge against improved cryptanalysis in PQC1.

    So let's play out some scenarios

    1. ML-KEM is sound. Everyone is happy with it, so there is no point in using a Composite when the cryptographic community feels it's no longer needed. Use ML-KEM directly - it will cut down on complexity significantly.
    2. ML-KEM is not sound. Everyone realizes that ML-KEM shouldn't be used. In that case, using the Composites doesn't make sense either and everyone moves on to the next other composite, thing, or whatever.

    Either way - the point is that Composites are not meant to exist for a very long time (indeed, CNSA 2.0 even said don't bother with Composites)

    Combined with the fact that Composite ML-KEM is squints keyAgreement in X.509 sense, and using X.509 with keyAgreement is already a weird scenario - I don't think it will get much use, either.

    All that to say: my sense is that we want to try and minimize the amount of public API surface for something that is - by design - not supposed to exist long.

    Especially in the case of PublicKey - it supports arbitrary SubjectPublicKeyInfo's. The APIs should really only exist for things that are expected to really be core, common, use cases. For example, one could just do

    PublicKey key = PublicKey.CreateFromSubjectPublicKeyInfo(compositeKem.ExportSubjectPublicKeyInfo());

    1There are technically other reasons people like Composites, but that is the primary one

  7. PranavSenthilnathan commented on Jun 20, 2026

    @PranavSenthilnathan
    MemberAuthor

    My understanding was that the main use case of the MLKem class would be for certificates in things like Enveloped CMS. And CompositeMLKem would be an alternative to it (even if hybrids have questionable utility). If we don't support certificates, what would be the use case for CompositeMLKem? Or are you suggesting keeping all of that an internal implementation instead of making it public?

  8. vcsjones commented on Jun 20, 2026

    @vcsjones
    Member

    My understanding was that the main use case of the MLKem class would be for certificates in things like Enveloped CMS.

    Within .NET itself, possibly. A much more common use of ML-KEM, the algorithm, is in protocols that do ephemeral key agreement like SSH or TLS. It can be used for EnvelopedCms.

    If we don't support certificates, what would be the use case for CompositeMLKem?

    Enveloped CMS, as a specification, does not require X.509 certificates. We just designed it in such a way that it did. We haven't added any support for KEMs in EnvelopedCms. When we get around to adding KEMRecipientInfo there is no reason for us to require the KEM keys to be shrouded in a certificate.

    Or are you suggesting keeping all of that an internal implementation instead of making it public?

    I am not exactly sure what I am suggesting, unfortunately. I am just pointing out that this is a lot of new API surface that is by design living on borrowed time. Maybe all of it is necessary now.

    I guess we can keep the certificate stuff. I just want to be very mindful that composites don't need to have as much API surface as possible.

  9. bartonjs commented on Jun 29, 2026

    @bartonjs
    Member

    Seems like conversation has run out, so marking api-ready-for-review. (Feel free to continue discussing)

  10. added
    api-ready-for-reviewAPI is ready for review, it is NOT ready for implementation
    and removed
    api-suggestionEarly API idea and discussion, it is NOT ready for implementation
    on Jun 29, 2026
  11. bartonjs commented on Jun 29, 2026

    @bartonjs
    Member

    (Disclaimer: I am not officially stating a Microsoft policy here, just my understanding of it. Anything officially written replaces anything I say here.)

    FWIW, my current understanding/projection of the Microsoft viewpoint (which may, very well, be unique), is that even in the face of a CRQC, composites are "better" than pure PQC until closer to 2050 than 2030. Mainly it comes down to "a CRQC existing doesn't mean that using it is fast or free, so buttressing new algorithms with a pre-Q algorithm just gives an extra element of defense". When the new algorithms are old enough that implementation gotchas have had a chance to be found out and fixed, and no one has found a fundamental flaw in them, then the addition of the traditional component become an "unnecessary" extra element of defense... or when the time and money for running a break becomes negligible enough...

    At the very least, I took to the MS Crypto Board the idea of giving composites a sort of second-class treatment (e.g. moving them to a different assembly, reducing integration touchpoints)... and of everyone who spoke, I got a unanimous plea to treat them first-class. At least the current ML-DSA and ML-KEM ones.

  12. vcsjones commented on Jul 2, 2026

    @vcsjones
    Member

    Just to formalize some of my thoughts here

    plea to treat them first-class

    There are of course always going to be differing opinions about what someone thinks is important. I don't think I mean treat it as second-class, but every new API we introduce incurs hundreds of lines of code, tests, and documentation that will live forever.

    We offer first-class APIs for developers. We offer public APIs per-algorithm because it gives a much better developer experience than, say, EVP_DigestSign and hoping you passed the right strings and parameters to get the algorithm you want. That however means additional Public API surface.

    Putting aside the CRQC relevance questions in terms of "obsolete" or not:

    public PublicKey(CompositeMLKem key);
    [Experimental("SYSLIB5006")] [UnsupportedOSPlatform("browser")] public CompositeMLKem? GetCompositeMLKemPublicKey();

    Personally I think we should just stop adding things to PublicKey. We have PublicKey.CreateFromSubjectPublicKeyInfo and PublicKey.ExportSubjectPublicKeyInfo to support arbitrary public keys. All those "first class" APIs do is defer to those APIs, anyway:

    public PublicKey(MLKem key) : this(key.ExportSubjectPublicKeyInfo())

    So anyone can do PublicKey.CreateFromSubjectPublicKeyInfo(mlKemCompositeInstance.ExportSubjectPublicKeyInfo()).

    If I had a time machine I would go back and recommend we don't add it for the other PQC types, but they are there now. I don't like using "but we already did it" to justify keep doing things we shouldn't do.

    • GetCompositeMLKemPublicKey
    • GetCompositeMLKemPrivateKey
    • CopyWithPrivateKey

    I'm torn on these because I generally don't expect these APIs to get any reasonable amount of traction. That said if you do actually need these APIs, you can't easily work around not having them except for GetCompositeMLKemPublicKey.

    The most obvious use case for it is EnvelopedCms but I would counter

    1. I think a lot of EnvelopedCms use is because it is simply the only tool available in-the-box that does "asymmetric encryption". That will change with HPKE, and I expect HPKE will be used more commonly in that regard.

    2. EnvelopedCms is a type-forward in .NET Framework configurations of S.S.C.Pkcs. So we can't add APIs to it

      [assembly: TypeForwardedTo(typeof(System.Security.Cryptography.Pkcs.EnvelopedCms))]

      Even if we decide we want these APIs, I don't know that we should have X509CertificateKeyAccessors OOB them. They will only be present for .NET Framework, and, since EnvelopedCms can't add Public API surface (unless we have extension methods do CryptoConfig or some stuff) I'm not really sure who the OOB APIs are for.

  13. 2 remaining items

  14. bartonjs commented on Aug 11, 2026

    @bartonjs
    Member

    Video

    • Looks good as proposed.
    • Leaving out the PublicKey members is fine, as well as the other certificate-attachment API (if there's not currently use for them)
    namespace System.Security.Cryptography
    {
        // System.Security.Cryptography and Microsoft.Bcl.Cryptography
        [Experimental("SYSLIB5006")]
        public abstract class CompositeMLKem : IDisposable
        {
            protected CompositeMLKem(CompositeMLKemAlgorithm algorithm);
    
            public static bool IsAlgorithmSupported(CompositeMLKemAlgorithm algorithm);
    
            public CompositeMLKemAlgorithm Algorithm { get; }
    
            public byte[] Decapsulate(byte[] ciphertext);
            public void Decapsulate(ReadOnlySpan<byte> ciphertext, Span<byte> sharedSecret);
            protected abstract void DecapsulateCore(ReadOnlySpan<byte> ciphertext, Span<byte> sharedSecret);
    
            public void Encapsulate(out byte[] ciphertext, out byte[] sharedSecret);
            public void Encapsulate(Span<byte> ciphertext, Span<byte> sharedSecret);
            protected abstract void EncapsulateCore(Span<byte> ciphertext, Span<byte> sharedSecret);
    
            public byte[] ExportDecapsulationKey();
            public int ExportDecapsulationKey(Span<byte> destination);
            public bool TryExportDecapsulationKey(Span<byte> destination, out int bytesWritten);
            protected abstract int ExportDecapsulationKeyCore(Span<byte> destination);
    
            public byte[] ExportEncapsulationKey();
            public int ExportEncapsulationKey(Span<byte> destination);
            public bool TryExportEncapsulationKey(Span<byte> destination, out int bytesWritten);
            protected abstract int ExportEncapsulationKeyCore(Span<byte> destination);
    
            public static CompositeMLKem GenerateKey(CompositeMLKemAlgorithm algorithm);
    
            public static CompositeMLKem ImportEncapsulationKey(CompositeMLKemAlgorithm algorithm, ReadOnlySpan<byte> source);
            public static CompositeMLKem ImportEncapsulationKey(CompositeMLKemAlgorithm algorithm, byte[] source);
    
            public static CompositeMLKem ImportDecapsulationKey(CompositeMLKemAlgorithm algorithm, ReadOnlySpan<byte> source);
            public static CompositeMLKem ImportDecapsulationKey(CompositeMLKemAlgorithm algorithm, byte[] source);
    
            public void Dispose();
            protected virtual void Dispose(bool disposing);
    
            // PKCS#8 / SPKI / PEM import/export
            public bool TryExportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBytes, PbeParameters pbeParameters, Span<byte> destination, out int bytesWritten);
            public bool TryExportEncryptedPkcs8PrivateKey(ReadOnlySpan<char> password, PbeParameters pbeParameters, Span<byte> destination, out int bytesWritten);
            public bool TryExportEncryptedPkcs8PrivateKey(string password, PbeParameters pbeParameters, Span<byte> destination, out int bytesWritten);
    
            public byte[] ExportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBytes, PbeParameters pbeParameters);
            public byte[] ExportEncryptedPkcs8PrivateKey(ReadOnlySpan<char> password, PbeParameters pbeParameters);
            public byte[] ExportEncryptedPkcs8PrivateKey(string password, PbeParameters pbeParameters);
    
            public string ExportEncryptedPkcs8PrivateKeyPem(ReadOnlySpan<byte> passwordBytes, PbeParameters pbeParameters);
            public string ExportEncryptedPkcs8PrivateKeyPem(ReadOnlySpan<char> password, PbeParameters pbeParameters);
            public string ExportEncryptedPkcs8PrivateKeyPem(string password, PbeParameters pbeParameters);
    
            public byte[] ExportPkcs8PrivateKey();
            public string ExportPkcs8PrivateKeyPem();
            public bool TryExportPkcs8PrivateKey(Span<byte> destination, out int bytesWritten);
            protected abstract bool TryExportPkcs8PrivateKeyCore(Span<byte> destination, out int bytesWritten);
    
            public byte[] ExportSubjectPublicKeyInfo();
            public string ExportSubjectPublicKeyInfoPem();
            public bool TryExportSubjectPublicKeyInfo(Span<byte> destination, out int bytesWritten);
    
            public static CompositeMLKem ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBytes, ReadOnlySpan<byte> source);
            public static CompositeMLKem ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<char> password, ReadOnlySpan<byte> source);
            public static CompositeMLKem ImportEncryptedPkcs8PrivateKey(string password, byte[] source);
    
            public static CompositeMLKem ImportFromEncryptedPem(ReadOnlySpan<char> source, ReadOnlySpan<byte> passwordBytes);
            public static CompositeMLKem ImportFromEncryptedPem(ReadOnlySpan<char> source, ReadOnlySpan<char> password);
            public static CompositeMLKem ImportFromEncryptedPem(string source, byte[] passwordBytes);
            public static CompositeMLKem ImportFromEncryptedPem(string source, string password);
    
            public static CompositeMLKem ImportFromPem(ReadOnlySpan<char> source);
            public static CompositeMLKem ImportFromPem(string source);
    
            public static CompositeMLKem ImportPkcs8PrivateKey(byte[] source);
            public static CompositeMLKem ImportPkcs8PrivateKey(ReadOnlySpan<byte> source);
    
            public static CompositeMLKem ImportSubjectPublicKeyInfo(byte[] source);
            public static CompositeMLKem ImportSubjectPublicKeyInfo(ReadOnlySpan<byte> source);
        }
        
        // System.Security.Cryptography and Microsoft.Bcl.Cryptography
        [Experimental("SYSLIB5006")]
        public sealed class CompositeMLKemAlgorithm : IEquatable<CompositeMLKemAlgorithm>
        {
            internal CompositeMLKemAlgorithm();
    
            public static CompositeMLKemAlgorithm MLKem768WithRsaOaep2048 { get; }
            public static CompositeMLKemAlgorithm MLKem768WithRsaOaep3072 { get; }
            public static CompositeMLKemAlgorithm MLKem768WithRsaOaep4096 { get; }
            public static CompositeMLKemAlgorithm MLKem768WithX25519 { get; }
            public static CompositeMLKemAlgorithm MLKem768WithECDiffieHellmanP256 { get; }
            public static CompositeMLKemAlgorithm MLKem768WithECDiffieHellmanP384 { get; }
            public static CompositeMLKemAlgorithm MLKem768WithECDiffieHellmanBrainpoolP256r1 { get; }
    
            public static CompositeMLKemAlgorithm MLKem1024WithRsaOaep3072 { get; }
            public static CompositeMLKemAlgorithm MLKem1024WithECDiffieHellmanP384 { get; }
            public static CompositeMLKemAlgorithm MLKem1024WithECDiffieHellmanBrainpoolP384r1 { get; }
            public static CompositeMLKemAlgorithm MLKem1024WithX448 { get; }
            public static CompositeMLKemAlgorithm MLKem1024WithECDiffieHellmanP521 { get; }
    
            public string Name { get; }
    
            public int CiphertextSizeInBytes { get; }
            public int SharedSecretSizeInBytes { get; }
    
            public bool Equals([NotNullWhen(true)] CompositeMLKemAlgorithm? other);
            public override bool Equals([NotNullWhen(true)] object? obj);
            public override int GetHashCode();
            public override string ToString();
    
            public static bool operator ==(CompositeMLKemAlgorithm? left, CompositeMLKemAlgorithm? right);
            public static bool operator !=(CompositeMLKemAlgorithm? left, CompositeMLKemAlgorithm? right);
        }
    
        // System.Security.Cryptography and Microsoft.Bcl.Cryptography
        [Experimental("SYSLIB5006")]
        [SupportedOSPlatform("windows")]
        public sealed partial class CompositeMLKemCng : CompositeMLKem
        {
            public CompositeMLKemCng(CngKey key);
    
            public CngKey GetKey();
        }
    }
    
    namespace System.Security.Cryptography.X509Certificates
    {
        // System.Security.Cryptography only
        public sealed partial class PublicKey
        {
            [Experimental("SYSLIB5006")]
            public PublicKey(CompositeMLKem key);
    
            [Experimental("SYSLIB5006")]
            [UnsupportedOSPlatform("browser")]
            public CompositeMLKem? GetCompositeMLKemPublicKey();
        }
    
        // System.Security.Cryptography only
        public partial class X509Certificate2
        {
            [Experimental("SYSLIB5006")]
            public CompositeMLKem? GetCompositeMLKemPublicKey();
    
            [Experimental("SYSLIB5006")]
            public CompositeMLKem? GetCompositeMLKemPrivateKey();
    
            [Experimental("SYSLIB5006")]
            public X509Certificate2 CopyWithPrivateKey(CompositeMLKem privateKey);
        }
    
        // Microsoft.Bcl.Cryptography only
        public static partial class X509CertificateKeyAccessors
        {
            [Experimental("SYSLIB5006")]
            public static CompositeMLKem? GetCompositeMLKemPublicKey(this X509Certificate2 certificate);
    
            [Experimental("SYSLIB5006")]
            public static CompositeMLKem? GetCompositeMLKemPrivateKey(this X509Certificate2 certificate);
    
            [Experimental("SYSLIB5006")]
            public static X509Certificate2 CopyWithPrivateKey(this X509Certificate2 certificate, CompositeMLKem privateKey);
        }
    }
  15. added
    api-approvedAPI was approved in API review, it can be implemented
    and removed
    api-ready-for-reviewAPI is ready for review, it is NOT ready for implementation
    on Aug 11, 2026
  16. PranavSenthilnathan commented on Aug 24, 2026

    @PranavSenthilnathan
    MemberAuthor

    We should move [SupportedOSPlatform("windows")] from CompositeMLKemCng to its constructor, matching the other CNG algorithm types.

    [Experimental("SYSLIB5006")]
    -[SupportedOSPlatform("windows")]
    public sealed partial class CompositeMLKemCng : CompositeMLKem
    {
    +   [SupportedOSPlatform("windows")]
        public CompositeMLKemCng(CngKey key);
    }

    @bartonjs I don't think we need a separate review for this update

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

Metadata

Metadata

Labels

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions