Private Key Wrapping of ML-KEM and ML-DSA
Capabilities and Policies
The container configuration flags CONTAINER_CONFIG_PRIVATE_KEY_UNWRAPPING and CONTAINER_CONFIG_PRIVATE_KEY_WRAPPING are enabled as appropriate for wrapping or unwrapping of ML-KEM and ML-DSA private keys.
The container configuration flags CONTAINER_CONFIG_SECRET_KEY_UNWRAPPING and CONTAINER_CONFIG_SECRET_KEY_WRAPPING are enabled as appropriate for decapsulation or encapsulating operations.
Private Key Format PKCS#8
The PKCS#8/RFC 5958 encoding scheme provides a way to encode private keys in an agreed format.
RFC 5958 is backwards compatible with the earlier RFC-5208 format but allows the (optional) addition of the Public Key value.
RFC 5208 defines Private-Key Information as PrivateKeyInfo
PrivateKeyInfo::= SEQUENCE { version Version, // v1 = 0 privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, privateKey PrivateKey, attributes [0] IMPLICIT Attributes OPTIONAL } }
RFC 5958 defines Private-Key Information as OneAsymmetricKey
OneAsymmetricKey::= SEQUENCE { version Version, // v2 = 1 privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, privateKey PrivateKey, attributes [0] Attributes OPTIONAL } [[2: publicKey [1] BIT STRING (CONTAINING PUBLIC-KEY.&Params({PublicKeySet} {@privateKeyAlgorithm.algorithm}) OPTIONAL, . . . }
So OneAsymmetricKey is the same as PrivateKeyInfo except for the version number and the option to add a publicKey.
The fields have the following meanings:
version is the syntax version number.
It may be v1 (= 0) or v2 (= 1).
It shall be 0 for PrivateKeyInfo layout and 1 for OneAsymmetricKey layout.
privateKeyAlgorithm identifies the private-key algorithm as an OID encoding.
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameter ANY DEFINED BY algorithm OPTIONAL } // absent for ML keys
privateKey is an octet string whose contents are the value of the private key.
The interpretation of the contents is defined in the registration of the private-key algorithm.
ML-KEM and ML-DSA private keys are described below.
attributes is a SET of attribute. These are the extended key Usage information that is encrypted along with the private-key information. Because it is an IMPLICIT CONSTRUCTED tag the tag value is 0xA0
publicKey is bit string whose contents are the value of the public key. This field is optional and only present if the Version = v2 (that is, set to 1). Because it is an IMPLICIT tag the tag value is 0x81
Handling OPTIONAL Attributes field
The Attributes field is optional so it might be present or not.
Because it is an |IMPLICIT [0] SET type the field is tagged with the tag value ‘A0’.
The contents of the field (if present) will hold a SET of ATTRIBUTE types (each a SEQ in their own right).
If the Key Usage Attribute bit map is included it is recommended that there should be at least one of the following bits set for ML-DSA keys:
digitalSignature
nonrepudiation
keyCertSign
cRLSign
for ML-KEM keys this is the only allowed bit:
keyAgreement
PKCS#11 has no means to indicate that a private key can be used for keyCertSigning as compared to digitalSignature. We use CKA_SIGN to indicate signature generation capability but otherwise make no more distinction.
Therefore, the Key Usage bits are not relevant when importing and so, the Attributes field are ignored during unwrapping operations.
PKCS#12 - Encrypted private-key information
The EncryptedPrivateKeyInfo ASN.1 type combines an encryption algorithm specifier and an encrypted PKCS#8 private key encoding.
This type is mainly used inside PKCS#12 types (called PKCS8ShroudedKeyBag) to hold the private key encrypted value.
It may also be used by OpenSSL or other enterprise workflows as a PEM file.
This type is not directly supported by the HSM firmware. Cryptoki requires an unwrap key to be previously created so its handle can be passed to C_UnwrapKey or C_UnwrapKeyWithScheme functions.
Encrypted private-key information shall have ASN.1 type
EncryptedPrivateKeyInfo ::= SEQUENCE {
encryptionAlgorithm EncryptionAlgorithmIdentifier,
encryptedData EncryptedData }
EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
{ CONTENT-ENCRYPTION,
{ KeyEncryptionAlgorithms } }
EncryptedData ::= OCTET STRING
The EncryptedData content is the encrypted private key (blob) that may be passed to the C_UnwrapKey or C_UnwrapKeyWithScheme function. After the EncryptedData is decrypted the plain text recovered is the PKCS#8 DER encoding of the Private-Key Information type.
EncryptedPrivateKeyInf may be held stand-alone in PEM encoded format by: 1/ applying Base64 encoding and 2/ sandwiching the result in-between:
-----BEGIN ENCRYPTED PRIVATE KEY-----
-----END ENCRYPTED PRIVATE KEY-----
CKA_PUBLIC_KEY_INFO attribute
ML-DSA and ML-KEM private key objects support the CKA_PUBLIC_KEY_INFO attribute. If the private key object holds a public key value, then that value can be read from the object by getting the CKA_PUBLIC_KEY_INFO attribute.
The CKA_PUBLIC_KEY_INFO attribute holds the public key values as a DER encoded SubjectPublicKeyInfo (as used in X509 certs).
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING }
The ‘algorithm’ is a SEQUENCE with an OID and optional params.
Private Key Raw Value Representations
Both ML-KEM and ML-DSA offer options on how to represent the raw value of a private key.
ML-KEM Private keys may be represented in two forms.
1.Expanded: A collection of: PKE private key, PKE Public key, a hash and a random value = dkPKE∥ek∥H(ek)∥z
NOTE The public key (ek) is included as part of the private key.
2.Seed: The Seeds that will allow the Expanded representation to be re-generated i.e. two 32-byte random values
ML-DSA Private Keys may be represented in two forms.
1.Expanded: A byte array holding a collection of values used to make a signature. The public key is not included but may be calculated from values in the private key.
2.Seed: The Seed that will allow the Expanded representation to be re-generated i.e. one 32-byte random valueAn HSM may internally store either (or both) form depending on performance/storage trade off.
The Expanded form is always required to generate signatures, but an HSM can, when needed, always create the Expanded form from the seeds. Therefore, the HSM can choose to store the seed or the Expanded form or both. When exporting the key, the seed or expanded or both may be wrapped. When importing the key either the seed or the Expanded form or both may be provided.
The Luna HSM trades storage for performance and stores both Expanded and Seed forms. The Expanded form is always used for signing and the Seed is held to support export/wrapping in the Seed form.
Options for Choosing Form of Private Keys When Wrapping
Currently, IETF specifications for ML-DSA and ML-KEM allow more than one option on how to represent a private key.
However, the PKCS#11 C_WrapKey mechanisms do not specify a way for an application to indicate how to encode the private key during the wrapping operation.
To support compatibility between key exporter and Importer, we support more than one export scheme with extensions to PKCS#11.
A new extended Key Wrapping function is defined, which accepts additional input parameters (used to specify the encoding scheme desired and Key Usage).
ML-DSA and ML-KEM use the same layout and so are treated the same.
Cryptoki CK_KEY_ENCODING_SCHEME type
typedef CK_ULONG CK_KEY_ENCODING_SCHEME;
The meaning of the value for this attribute will depend on the key type being wrapped.
Encode Default 0
Encode Seed 1
Encode Exp 2
Encode Both 3
The encoding scheme selected may also specify if the encoding includes the public Key by OR’ing this value:
ENCODE_WITH_PK 4
Private Key encoding format |
---|
Default i.e. HSM chooses format Note 1 |
Seed [0] OCTET STRING |
expandedKey OCTET STRING |
both SEQUENCE { seed OCTET STRING, expandedKey OCTET STRING } |
Note 1 Luna HSM defaults to output format 1 if CKA_SEED is present, otherwise format 2.
Proprietary Function CA_WrapKeyWithScheme
CK_DECLARE_FUNCTION(CK_RV, CA_WrapKeyWithScheme)(
CK_SESSION_HANDLE hSession,
CK_MECHANISM_PTR pMechanism,
CK_OBJECT_HANDLE hWrappingKey,
CK_OBJECT_HANDLE hKey,
CK_KEY_ENCODING_SCHEME keyEncodingScheme,
CK_BYTE_PTR pUsageInfo,
CK_ULONG ulUsageInfoLen,
CK_BYTE_PTR pWrappedKey,
CK_ULONG_PTR pulWrappedKeyLen
);
This proprietary function behaves the same as the pre-existing standard C_WrapKey function except that the keyEncodingScheme value and pUsageInfo values are passed down to the key encoding operation of the mechanism to guide the selection of the encoding scheme to use and add an optional ‘Attributes’ field.
Attributes
The PrivateKeyInfo and OneAsymmetricKey encodings allow for an optional Attributes field.
The pUsageInfo and ulUsageInfoLen parameters point to a DER encoding of a SET OF Attributes.
If ulUsageInfoLen is zero, no Attributes field is added to the PKCS#8 encoding.
If ulUsageInfoLen is not zero then the pUsageInfo must point to a valid ‘Attributes’ encoding,that is, a SET OF ‘Attribute’ where ‘Attribute’ is a SEQ containing an OID followed by one or more values.
The maximum supported length is 1024 bytes.
Attribute ::= SEQUENCE {
attrType OBJECT IDENTIFIER,
attrValues SET OF ANY
}
Attributes ::= SET OF Attribute
Here is a sample showing a keyUsage Attribute for a ML-KEM Private key where bit 2 (keyEncipherment) is set.
31 0E ; SET of Attributes 14 bytes long
30 0C ; 1st Attribute SEQUENCE 12 bytes long
06 03 55 1D 0F ; OID keyUsage (2.5.29.15)
31 05 ; SEQUENCE 5 bytes long
03 03 00 20 00 ; BIT STR 3 bytes: 0 unused bits, val 0x2000
For illustration: here is a BIT STR with digitalSignature + nonrepudiation set:
03 03 00 C0 00 ; BIT STR 3 bytes: 0 unused bits, val 0xC000
KeyUsage ::= BIT STRING {
digitalSignature (0),
nonRepudiation (1), -- recent editions of X.509 have
-- renamed this bit to contentCommitment
keyEncipherment (2),
dataEncipherment (3),
keyAgreement (4),
keyCertSign (5),
cRLSign (6),
encipherOnly (7),
decipherOnly (8) }
TokenAPI Level Changes
The C_WrapKey function resolves down to the PcmciaProtocol-level function: WrapKey(). This command encodes the parameters into a WrapKeyCommand_t.
Token API is extended to support the CA_WrapKeyWithScheme() to accommodate the WrapKeySchemeCommand_t structure.
The C_WrapKey function uses the default values for keyEncodingScheme. If the default behavior is not desired the application may use the CA_WrapKeyWithScheme() function and specify values other than the default.
Chrystoki.conf changes
However in some scenarios the application may not be able to change the function being used to export (that is, C_WrapKey()). In order to allow legacy applications to choose a non-default wrapped key format, Chrysoki.conf values can be used to override the defaults.
In Misc Section:
KeyEncodingScheme=2
If this field is present then C_WrapKey command is redirected to the C_WrapKeyWithScheme handler.
NOTE NOTE This allows only a single choice of KeyEncodingScheme for any single application ‘environment’. The application cannot pick different schemes based on key type or other system needs. It must be configured for just one.
A possibility may be to configure another instance of the application to request an alternative scheme.
TIP Older implementations calling newer firmware will not support the C_WrapKeyWithScheme function and will only ever call the WRAP_KEY command in the firmware.
Newer firmware is backward compatible with older clients.
New implementations calling older firmware will get an Unknown command error from the firmware
•if the C_WrapKeyWithScheme function is called or
•if the C_WrapKey function is called and the Chrystoki.conf file has key wrapping scheme overrides defined.
No support for Attributes field is included in this override method. Most PKCS#8 encoding does not use the Attributes field.
FW Changes
Firmware version 7.9.1 onward extends the KM_WrapKeyCommand to accept a flag telling it to expect a WrapKeyCommand_t structure or a WrapKeySchemeCommand_t structure. .
If a WrapKeyCommand_t structure is received and decoded the value of CK_KEY_ENCODING_SCHEME defaults to zero and the KeyInfo field will be empty.
Details for ML-KEM
version
version is the syntax version number.
When unwrapping it may be v1 (= 0) or v2 (= 1).
When wrapping it is v2 if the encoding scheme asks for the public key, otherwise v1.
privateKeyAlgorithm AlgorithmIdentifier
privateKeyAlgorithm AlgorithmIdentifier identifies the private-key type/size.
AlgorithmIdentifier ::= SEQUENCE { algorithm OBJECT IDENTIFIER, parameter ANY DEFINED BY algorithm OPTIONAL }
A different OID is used for each key size (does not use the algorithm parameter field).
kems OBJECT IDENTIFIER ::= { joint-iso-ccitt(2) country(16) us(840) organization(1) gov(101) csor(3) nistAlgorithm(4) kems(4) } id-alg-ml-kem-512 OBJECT IDENTIFIER ::= { kems 1 } id-alg-ml-kem-768 OBJECT IDENTIFIER ::= { kems 2 } id-alg-ml-kem-1024 OBJECT IDENTIFIER ::= { kems 3 }
2.16.840.1.101.3.4.4.1 (ML-KEM-512)
2.16.840.1.101.3.4.4.2 (ML-KEM-768)
2.16.840.1.101.3.4.4.3 (ML-KEM-1024)
DER Encoding for id-alg-ml-kem-512: 06 -- OID Tag 09 -- Length 60 86 48 01 65 03 04 04 01 -- OID ML-KEM-512
The parameter field in the AlgorithmIdentifier is empty (missing, not a NULL entry).
privateKey
In accordance with version 10 of the IETF spec for ML-KEM private keys this is an ML-KEM 512 layout.
ML-KEM-512-PrivateKey ::= CHOICE { seed [0] OCTET STRING (SIZE (64)), expandedKey OCTET STRING (SIZE (1632)), both SEQUENCE { seed OCTET STRING (SIZE (64)), expandedKey OCTET STRING (SIZE (1632)) } }
Key Parameter Set | Private Key Length | Seed Length |
---|---|---|
ML-KEM-512 | 1632 | 64 |
ML-KEM-768 | 2400 | 64 |
ML-KEM-1025 | 3168 | 64 |
Import
The format of the private-Key Information encoding allows for the selection of 'seed', or of 'expandedKey', or 'both', with DER tags helping the HSM identify which option is being used.
If the IETF-recommended 'seed' option only is used, then if this format is unwrapped, the HSM creates an expanded private/public key pair and saves those three values (seed, private key, public key) in the new object.
If the 'expandedKey' option is unwrapped, the HSM derives the public key from the provided private key and stores these two values (private key and public key, but not including the seed) in the new object.
When both 'seed' and 'expandedKey' are provided, the HSM generates an expanded private/public key pair from the seed. The generated private key is then compared with the supplied private key to ensure they match.
If a publicKey field is included in the encoding, its value is compared to the recovered public key.
In all scenarios, the expanded key and the recovered public key are validated before the new object is created.
Export
Key encoding scheme can be controlled by CK_ML_ENCODING value and UsageInfo, if provided. Otherwise it defaults to Seed if possible, or else expandedKey and no public key or UsageInfo fields.
attributes
These are the application-provided extended key usage information that is encrypted along with the private-key information.
During unwrapping these are ignored (discarded).
During wrapping this field may be built from the UsageInfo.
publicKey
It is an error if a v1 version encoding holds a publicKey.
Any optional publicKey found at the end of the Private-Key Information encoding is compared to the public key computed from the seed (if seed is available), or verified using Encapsulation Inputs Validation that implements the FIPS 203 Section 7.2 Encapsulation key check.
The publicKey field is a bit string that holds the public key value. For ML-KEM this is an octet array of size 800 or 1184 or 1568
NOTE Bit strings have a leading ‘00’ to indicate no bit padding).
Details for ML-DSA
version
version is the syntax version number of the type.
When unwrapping, it may be v1 (= 0) or v2 (= 1).
When wrapping, it will be v2 if the encoding scheme asks for the public key, otherwise v1.
privateKeyAlgorithm
The OIDs are defined for:
•for ‘pure’ signing algorithms based on each key size and
•for Pre-Hash signatures using SHA512 for each key size.
‘pure’ algorithm OIDs are used to indicate the ML-DSA key type.
Note: no OIDS are defined for pre hash algorithms that use a hash other than SHA512.
sigAlgs ::= joint-iso-itu-t(2) country(16) us(840) organization(1) gov(101) csor(3) nistAlgorithm(4) sigAlgs(3)
‘Pure’ algorithms: id-ML-DSA-44 OBJECT IDENTIFIER ::= { sigAlgs 17 } id-ML-DSA-65 OBJECT IDENTIFIER ::= { sigAlgs 18 } id-ML-DSA-87 OBJECT IDENTIFIER ::= { sigAlgs 19 }
Pre-Hash algorithms: id-Hash-ML-DSA-44-with-sha512 OBJECT IDENTIFIER ::= { sigAlgs 32 } id-Hash-ML-DSA-65-with-sha512 OBJECT IDENTIFIER ::= { sigAlgs 33 } id-Hash-ML-DSA-87-with-sha512 OBJECT IDENTIFIER ::= { sigAlgs 34 }
privateKey
Here is the ML-DSA=44 layout, the 65 and 87 layouts are identical except for the length of the expandedKey field.
ML-DSA-44-PrivateKey ::= CHOICE { seed [0] OCTET STRING (SIZE (32)), expandedKey OCTET STRING (SIZE (2560)), both SEQUENCE { seed OCTET STRING (SIZE (32)), expandedKey OCTET STRING (SIZE (2560)) } }
Key Parameter Set | Private Key Length | Seed Length |
---|---|---|
ML-DSA=44 | 2560 | 32 |
ML-DSA=65 | 4032 | 32 |
ML-DSA=87 | 4896 | 32 |
Import
The format of the private-Key Information encoding allows for the selection of 'seed', or of 'expandedKey', or 'both', with DER tags helping the HSM identify which option is being used.
If the IETF-recommended 'seed' option only is used, then if this format is unwrapped, the HSM creates an expanded private/public key pair and saves those three values (seed, private key, public key) in the new object.
If the 'expandedKey' option is unwrapped, the HSM derives the public key from the provided private key, performs a pairwise consistency test, and stores these two values (private key and public key, but not including the seed) in the new object.
When both 'seed' and 'expandedKey' are provided, the HSM generates an expanded private/public key pair from the seed. The generated private key is then compared with the supplied private key to ensure they match.
In all scenarios, the expanded key and the recovered public key are validated before the new object is created.
Export
Key encoding scheme can be controlled by CK_ML_ENCODING value and UsageInfo value, if If those are supplied. In the absence of those values Seed is used if possible, or expandedKey otherwise, with no public key field or key Usage.
attributes
These are the application-provided extended information that is encrypted along with the private-key information.
During unwrapping these are ignored (discarded).
During wrapping this field may be built from the UsageInfo.
publicKey
The ‘publicKey’ field is a bit string that holds the public key value. For ML-DSA this is an octet array of size 1312 or 1952 or 2592 bytes.
NOTE Bit strings have a leading ‘00’ to indicate no bit padding).
It is an error if a v1 encoding holds a publicKey.
Here is a sample public key encoding for ML-DSA-44
SEQUENCE { SEQUENCE { OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.3.17 } } BIT_STRING { `00` `d7b2b47254aae0db45e7930d4a98d2c97d8f1397d17 89dafa17024b316e9bec94fc9946d42f19b79a7413bbaa33e7149cb42ed51156 5ba5925e8edefa679369a2202766151f16a965f9f81ece76cc070b55869e4db9 . . . . . 784cf05c830b3242c8312` } }
During unwrap any optional publicKey found at the end of the Private-Key Information encoding is compared to the public key computed from the seed (if any) and/or verified using the Public Key Validations method.
Tools
ML key wrapping support is added to our standard tools.
Ckdemo
Supports wrapping with new C_WrapKeyWithScheme function and ensures both ML private key types can be unwrapped.
Multitoken
Supports wrapping and unwrapping of these ML key types.