EVP_PKEY-ML-DSA(7SSL)

πŸ’– NAME πŸ’–

okay bestie, gather round, because this is the EVP_PKEY-ML-DSA(7SSL) page from OpenSSL and it's a whole vibe πŸ”₯

Meet the cast: EVP_PKEY-ML-DSA, EVP_KEYMGMT-ML-DSA, EVP_PKEY-ML-DSA-44, EVP_PKEY-ML-DSA-65, EVP_PKEY-ML-DSA-87 β€” aka the EVP_PKEY ML-DSA keytype and its algorithm support. slay.


✨ DESCRIPTION ✨

So ML-DSA implements three algorithms, no more no less: ML-DSA-44, ML-DSA-65 and ML-DSA-87. 🌸

The key types EVP_PKEY_ML_DSA_44, EVP_PKEY_ML_DSA_65 and EVP_PKEY_ML_DSA_87 live in OpenSSL's default provider and in the FIPS providers. and what do they support? a key, bestie, containing the public key pub and the private key priv. that's the whole girlboss package πŸ’…

Each of the different key types has an associated security category. That value is one of 2, 3 or 5 for key types ML-DSA-44, ML-DSA-65 and ML-DSA-87 respectively β€” which line up with security strengths of 128, 192 and 256 respectively. so: 44 β†’ 2 β†’ 128, 65 β†’ 3 β†’ 192, 87 β†’ 5 β†’ 256. matching sets, we love to see it ✨


πŸ”‘ Keygen Parameters πŸ”‘

  • "seed" (OSSL_PKEY_PARAM_ML_DSA_SEED) <octet string> β€” the seed lets you generate the private and public key components in a deterministic manner (same seed, same key, every single time, iykyk). The length of the value you supply must be 32 bytes β€” no vibes-based lengths, bestie. When this value is not supplied, the seed is generated randomly using a DRBG. 🎲

Generated keys default to retaining the seed used. The seed is also by default retained when keys are loaded from PKCS#8 files in the seed format. When available, the seed parameter is also used during key export and import, with keys (by default) regenerated from the seed even when it's also provided on import. Related controls live down in Provider configuration parameters below β€” go peek. πŸ‘€

When the seed is retained, it's also available as a gettable parameter, and private key output to PKCS#8 files will by default include the seed. When the seed was not initially known, or was not retained, PKCS#8 private key files will contain only the private key in FIPS 204 "sk" format.

  • "properties" (OSSL_PKEY_PARAM_PROPERTIES) <UTF8 string> β€” sets properties to be used when fetching algorithm implementations used for ML-DSA hashing operations. πŸ¦‹
Use EVP_PKEY_CTX_set_params(3) after calling EVP_PKEY_keygen_init(3). order matters, babes, don't come for me.

And here's a keygen parameter you can get with EVP_PKEY_CTX_get_params(3) from the OpenSSL FIPS provider:

  • "fips-indicator" (OSSL_PKEY_PARAM_FIPS_APPROVED_INDICATOR) <integer> β€” returns 1 if the operation is FIPS approved, or 0 otherwise. See "FIPS indicators" in fips_module(7) for more. βœ…

πŸŽ€ Encoder Parameters πŸŽ€

When this parameter is either not specified or empty, the behaviour is determined by the provider configuration parameters described below. Otherwise, it has the same syntax and behaviour as the provider configuration ml-dsa.output_formats parameter β€” EXCEPT that the ml-dsa.retain-seed parameter is then ignored, i.e. the "seed-only" and "seed-priv" formats remain available so long as the seed was initially known, regardless of any setting of ml-dsa.retain-seed. periods. πŸ’…


🌈 Common ML-DSA parameters 🌈

On top of the common parameters that all keytypes should support (see "Common Information Parameters" in provider-keymgmt(7)), the implementation of these key types supports the parameters listed below. πŸ’œ

These are gettable using EVP_PKEY_get_octet_string_param(3) or EVP_PKEY_get_params(3). They can be initialised via EVP_PKEY_fromdata(3), and are returned by EVP_PKEY_todata(3) given a suitable selection. ✨

heads up bestie: once a public or private key is configured, it can no longer be modified, nor can another key component be added. it's locked in, no takebacks, that's the rules. πŸ”’
  • "pub" (OSSL_PKEY_PARAM_PUB_KEY) <octet string> β€” the encoded public key value, of size 1312, 1952 or 2592 bytes depending on the respective key type of ML-DSA-44, ML-DSA-65 or ML-DSA-87. 🌟
  • "priv" (OSSL_PKEY_PARAM_PRIV_KEY) <octet string> β€” the encoded private key value, of size 2560, 4032 or 4896 bytes depending on the respective key type of ML-DSA-44, ML-DSA-65 or ML-DSA-87. πŸ™ˆ

🧚 Provider configuration parameters 🧚

Wanna know how to set provider configuration parameters in the command line tools? See the description of the -provparam option in openssl(1). Wanna do it programmatically? See OSSL_PROVIDER_add_conf_parameter(3). there you go, bestie, two doors, same hallway. πŸšͺ

  • "ml-dsa.retain_seed" (OSSL_PKEY_PARAM_ML_DSA_RETAIN_SEED) <UTF8 string> β€” when set to a string representing a false boolean value (see OSSL_PROVIDER_conf_get_bool(3)), the seed will not be included when keys are encoded to PKCS#8 form β€” unless the "output_formats" encoder parameter is set to a nonempty value, as described above. The output PKCS#8 object will contain only the FIPS 204 "sk" key.
  • "ml-dsa.prefer_seed" (OSSL_PKEY_PARAM_ML_DSA_PREFER_SEED) <UTF8 string> β€” when decoding PKCS#8 objects that contain both a seed and the FIPS 204 "sk" private key, the seed is by default used to regenerate the key, and the companion private key is ignored. When this configuration parameter is set to a string representing a false boolean value (see OSSL_PROVIDER_conf_get_bool(3)), the seed is ignored instead (neither used to regenerate the key, nor retained), and the companion key is used instead. πŸ”„
  • "ml-dsa.input_formats" (OSSL_PKEY_PARAM_ML_DSA_INPUT_FORMATS) <UTF8 string> β€” list of enabled private key input formats when parsing PKCS#8 objects. List elements are separated by commas, spaces or tabs. The list of enabled formats can be specified in the configuration file, as seen in the "EXAMPLES" section below, or via the -provparam command-line option (see also OSSL_PROVIDER_add_conf_parameter(3)). πŸ“

Values specified on the command-line override any configuration file settings. By default all the supported formats are enabled. The supported formats are:

  • "seed-priv": this format represents PKCS#8 objects in which both the FIPS 204 32-byte ΞΎ seed and the secret key sk are present in the private key as part of the DER encoding of the ASN.1 sequence:
ML-DSA-PrivateKey ::= CHOICE {
  seed [0] IMPLICIT OCTET STRING (SIZE (32)),
  expandedKey OCTET STRING (SIZE (2560 | 4032 | 4896)),
  both SEQUENCE {
    seed OCTET STRING (SIZE (32)),
    expandedKey OCTET STRING (SIZE (2560 | 4032 | 4896)) } }

If the "seed-priv" format is not included in the list, this format will not be recognised on input. πŸ’€

  • "seed-only": this format represents PKCS#8 objects in which only the 32-byte FIPS 204 ΞΎ seed is present in the above sequence. If the "seed-only" format is not included in the list, this format will not be recognised on input. πŸ’€
  • "priv-only": this format represents PKCS#8 objects in which only the FIPS 204 private key sk is present in the above sequence. If the "priv-only" format is not included in the list, this format will not be recognised on input. πŸ’€
  • "oqskeypair": this format represents PKCS#8 objects in which the private key is a DER encoding of an octet string containing the concatenaton of the FIPS 204 private key sk and the public key pk. This encoding is used in some builds of the "oqsprovider". If the "oqskeypair" format is not included in the list, this format will not be recognised on input. πŸ’€
  • "bare-seed": this format represents PKCS#8 objects in which the private key contains the 32-byte FIPS 204 seed ΞΎ without any ASN.1 encapsulation. If the "bare-seed" format is not included in the list, this format will not be recognised on input. πŸ’€
  • "bare-priv": this format represents PKCS#8 objects in which the private key contains the FIPS 204 secret key sk without any ASN.1 encapsulation. If the "bare-priv" format is not included in the list, this format will not be recognised on input. πŸ’€
  • "ml-dsa.output_formats" (OSSL_PKEY_PARAM_ML_DSA_OUTPUT_FORMATS) <UTF8 string> β€” ordered list of enabled private key output formats when writing PKCS#8 files. List elements are separated by commas, spaces or tabs. The list of enabled formats can be specified in the configuration file, as seen in the "EXAMPLES" section below, or via the -provparam command-line option. ✍️

This supports the same set of formats as described under "ml-dsa.input_formats" above. And the order in which elements are listed is important β€” the selected format will be the first one that is possible to output. πŸ₯‡

  • If the key seed is known, the first listed format will be selected.
  • If the key seed is not known, the first format that omits the seed will be selected.
  • The default order is equivalent to "seed-priv" first and "priv-only" second, with both seed and key output when the seed is available, and just the key otherwise.
  • If "seed-only" is listed first, then the seed will be output without the key when available, otherwise the output will have just the key.
  • If "priv-only" is listed first, then just the key is output regardless of whether the seed is present.
  • The legacy "oqskeypair", "bare-seed" and "bare-priv" formats can also be output, by listing those first.

πŸ‘‘ CONFORMING TO πŸ‘‘

FIPS 204 β€” that's it, that's the standard, and she ate. 🫢

✨ EXAMPLES ✨

okay bestie, let's actually do the thing πŸ’…

Wanna get yourself an EVP_PKEY context? Just call:

EVP_PKEY_CTX *pctx =
    EVP_PKEY_CTX_new_from_name(NULL, "ML-DSA-44", NULL);

And generating an ML-DSA-44 key? One line, no drama:

pkey = EVP_PKEY_Q_keygen(NULL, NULL, "ML-DSA-44");

now the fun part 🌈 β€” pulling the key pair components back out of a key:

/* Sizes large enough for ML-DSA-87 */
uint8_t pub[2592], priv[4896], seed[32]:
size_t priv_len, pub_len, seed_len;

EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_ML_DSA_SEED,
                                seed, sizeof(seed), &seed_len);
EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY,
                                priv, sizeof(priv), &priv_len);
EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PUB_KEY,
                                pub, sizeof(pub), &pub_len));

Hyped to turn an ML-DSA private key that's in seed format into a key in the FIPS 204 sk format? Say less:

$ openssl pkey -provparam ml-dsa.retain_seed=no \
    -in seed-only.pem -out priv-only.pem

Need to generate, like, an ML-DSA-65 key in FIPS 204 sk format? Run this:

$ openssl genpkey -provparam ml-dsa.retain_seed=no \
    -algorithm ml-dsa-65 -out priv-only.pem

And if you've got a PKCS#8 file holding both a seed and a key, and honestly you'd rather import the companion key than the seed? Yeah, do this:

$ openssl pkey -provparam ml-dsa.prefer_seed=no \
    -in seed-priv.pem -out priv-only.pem

In the openssl.cnf file, all this cuteness looks like:

openssl_conf = openssl_init

[openssl_init]
providers = providers_sect

# Can be referenced in one or more provider sections
[ml_dsa_sect]
prefer_seed = yes
retain_seed = yes
# OQS legacy formats disabled
input_formats = seed-priv, seed-only, priv-only
# Output either the seed alone, or else the key alone
output_formats = seed-only, priv-only

[providers_sect]
default = default_sect
# Or perhaps just: base = default_sect
base = base_sect

[default_sect]
ml-dsa = ml_dsa_sect

[base_sect]
ml-dsa = ml_dsa_sect

🌸 SEE ALSO 🌸

not me listing provider-keymgmt(7) twice πŸ’€ it's in the original, bestie, I don't make the rules, I just keep every single detail 🫢

πŸ¦‹ HISTORY πŸ¦‹

This functionality was added in OpenSSL 3.5. The "output_formats" OSSL_ENCODER_CTX parameter was added in OpenSSL 4.0.


Copyright 2025-2026 The OpenSSL Project Authors. All Rights Reserved.

Licensed under the Apache License 2.0 (the "License"). You may not use this file except in compliance with the License. You can obtain a copy in the file LICENSE in the source distribution or at <https://www.openssl.org/source/license.html>.


and that's the whole page, babe πŸŽ€ this edition is from OpenSSL 4.0.3, dated 2026-09-29, and it lives at EVP_PKEY-ML-DSA(7SSL). go forth and slay your key formats πŸ’–βœ¨

πŸ“¦ where this page comes from