bc-java icon indicating copy to clipboard operation
bc-java copied to clipboard

ML-KEM and ML-DSA private key ASN.1 encoding format does not comply with IETF draft

Open bukodi opened this issue 1 year ago • 3 comments

Description: The ASN.1 encoding of the ML-DSA and ML-KEM private keys generated by the Bouncy Castle library does not comply with the IETF draft specifications.

According to Chapter 6 of the Dilithium IETF draft [1], the 'privateKey' component of the OneAsymmetricKey sequence should contain the raw octet string encoding of the 32-octet seed. However, the BC library generates an OCTET STRING that contains another OCTET STRING, which in turn contains the 32-octet seed. This additional OCTET STRING tag affects the import and export operations of both ML-KEM and ML-DSA private keys for all key sizes.

(Used Bouncy Castle version :1.79)

Reproduction steps for ML-DSA-44 key export:

  1. The IETF draft [1] Appendix C.1 provides an example ML-DSA-44 private key with seed 000102...1e1f:

    -----BEGIN PRIVATE KEY-----
    MDICAQAwCwYJYIZIAWUDBAMRBCAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRob
    HB0eHw==
    -----END PRIVATE KEY-----
    

    Decoded by online tool: https://lapo.it/asn1js/#MDICAQAwCwYJYIZIAWUDBAMRBCAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHw

  2. Generate the same key pair with the BC library and export the private key in PKCS#8 format:

        KeyPairGenerator kpg = KeyPairGenerator.getInstance(MLDSAParameterSpec.ml_dsa_44.getName(), "BC");
        kpg.initialize(MLDSAParameterSpec.ml_dsa_44, new FixedSecureRandom( new byte[] {
                0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
                0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
        }));
        KeyPair kp = kpg.generateKeyPair();
    
        StringWriter sw = new StringWriter();
        try( JcaPEMWriter pemWriter = new JcaPEMWriter(sw) ) {
            pemWriter.writeObject( new JcaMiscPEMGenerator(kp.getPrivate()));;
        };
        System.out.println("Private key PEM: \n" + sw);
    

The output is:

 -----BEGIN PRIVATE KEY-----
 MDQCAQAwCwYJYIZIAWUDBAMRBCIEIAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZ
 GhscHR4f
 -----END PRIVATE KEY-----

Decoded by online tool: https://lapo.it/asn1js/#MDQCAQAwCwYJYIZIAWUDBAMRBCIEIAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4f

References: [1] https://datatracker.ietf.org/doc/draft-ietf-lamps-dilithium-certificates/06/ [2] https://datatracker.ietf.org/doc/draft-ietf-lamps-kyber-certificates/07/

bukodi avatar Jan 16 '25 13:01 bukodi

This is kind of fixed in 1.80 (it would have been difficult in 1.79 as it came out a month before the decision about raw octets was made) but there's still a discussion going on about this.

Unfortunately seed only keys for ML-DSA and ML-KEM create an issue for FIPS modules where the ASN.1 parsing is done outside the cryptographic boundary. For these modules you need the long form keys as Section 6 of both FIPS PUB 203 and FIPS PUB 204 do not allow the internal key pair generation function to be exposed via an API except for testing, effectively ruling out the use of seed only in this case. This being the case (OpenSSL is an example of a module like this, I gather the issue also trips up PKCS#11 as well) it is unlikely we will be able to produce the IETF proposal, although I think we will be able to read it. In our case it appears we'll need to make use of the OASIS PKCS#11 proposal, which is a based around a SEQUENCE with 2 optional fields.

dghgit avatar Jan 22 '25 05:01 dghgit

Is it correct to say that based on the thread : https://mailarchive.ietf.org/arch/msg/spasm/thm2hfIoA6MLz8aOxkvDLt643CM/ that IETF will be using CHOICE (seed, expandedKey, both) - distinguished by IMPLICIT tag ([0], OCTET STRING, SEQUENCE) ?

That means that BC will be compliant with the future RFC and this issue can be closed?

Update: fixed the mailing list thread link " What priv key format are we hacking in 2 weeks?"

space88man avatar Apr 21 '25 00:04 space88man

Yes, if you try the current beta at https://download.bouncycastle.org/betas you'll find it's now following the final IETF standard. I'll close this when 1.81 comes out.

And yes, it was quite a saga...

dghgit avatar Apr 21 '25 02:04 dghgit

1.81 now released.

dghgit avatar Aug 03 '25 04:08 dghgit