RFR: 8347938: Switch to latest ML-KEM private key encoding [v5]

Sebastian Stenzel duke at openjdk.org
Mon Aug 4 12:53:58 UTC 2025


On Wed, 30 Jul 2025 15:45:50 GMT, Weijun Wang <weijun at openjdk.org> wrote:

>> The private key encoding formats of ML-KEM and ML-DSA are updated to match the latest IETF drafts at: https://datatracker.ietf.org/doc/html/draft-ietf-lamps-dilithium-certificates-11 and https://datatracker.ietf.org/doc/html/draft-ietf-lamps-kyber-certificates-10. New security/system properties are introduced to determine which CHOICE a private key is encoded when a new key pair is generated or when `KeyFactory::translateKey` is called.
>> 
>> By default, the choice is "seed".
>> 
>> Both the encoding and the expanded format are stored inside a `NamedPKCS8Key` now. When loading from a PKCS #8 key, the expanded format is calculated from the input if it's seed only.
>
> Weijun Wang has updated the pull request incrementally with one additional commit since the last revision:
> 
>   combine security properties description; remove one test

If my understanding is correct, we have now up to three byte[] in the NamedPKCS8Key class:
* the encoded format
* the raw expanded format
* the raw seed

With `getRawBytes()` either returning the encoded or the expanded format, depending on whether the encoded format and the expanded key are the same.

`getEncoded()` on the other hand always wraps the raw key in its ASN.1 structure.

Shouldn't `engineTranslateKey()` rely on the latter, then?

src/java.base/share/classes/com/sun/crypto/provider/ML_KEM_Impls.java line 132:

> 130:             if (nk instanceof NamedPKCS8Key npk) {
> 131:                 var type = KeyChoices.getPreferred("mlkem");
> 132:                 if (KeyChoices.typeOfChoice(npk.getRawBytes()) != type) {

> ```
> /// 1. If only `privKeyMaterial` is present, it's also the expanded format.
> /// 2. If both `privKeyMaterial` and `expanded` are available, `privKeyMaterial`
> ///    is the encoding format, and `expanded` is the expanded format.
> ```

This in mind, the result of `getRawBytes()` differs. In the first case, it is in fact raw bytes, as the method name suggests. (See also usage of `privKeyMaterial` in `PKCS8Key.generateEncoding()`).

Therefore, the ASN.1 envelope may be missing, causing 


java.security.InvalidKeyException: Wrong tag: -39
	at java.base/sun.security.util.KeyChoices.typeOfChoice(KeyChoices.java:144)
	at java.base/com.sun.crypto.provider.ML_KEM_Impls$KF.engineTranslateKey(ML_KEM_Impls.java:132)
	at java.base/java.security.KeyFactory.translateKey(KeyFactory.java:475)


Isn't `getEncoded()` the safer bet?

Suggestion:

                if (KeyChoices.typeOfChoice(npk.getEncoded()) != type) {

src/java.base/share/classes/sun/security/provider/ML_DSA_Impls.java line 157:

> 155:             if (nk instanceof NamedPKCS8Key npk) {
> 156:                 var type = KeyChoices.getPreferred("mldsa");
> 157:                 if (KeyChoices.typeOfChoice(npk.getRawBytes()) != type) {

See corresponding comment above for ML-KEM

-------------

Changes requested by overheadhunter at github.com (no known OpenJDK username).

PR Review: https://git.openjdk.org/jdk/pull/24969#pullrequestreview-3083718545
PR Review Comment: https://git.openjdk.org/jdk/pull/24969#discussion_r2251165104
PR Review Comment: https://git.openjdk.org/jdk/pull/24969#discussion_r2251166383


More information about the security-dev mailing list