M9 / Key management and HSMs

PKCS#11 v3.2 post-quantum mechanisms

PractitionerAdvisor

After this lesson you can

  • Recognize, in a code example, the new mechanisms and functions PKCS#11 v3.2 defines for ML-DSA and ML-KEM (including C_EncapsulateKey/C_DecapsulateKey)
  • State, with its date, why PKCS#11 v3.2 is a full OASIS Standard and not just a draft

Before thisM8: Protocols

Mental model

How is the encapsulate/decapsulate structure of ML-KEM, which you saw in M3, called inside an HSM? The answer: PKCS#11 is the industry-standard interface for talking to HSMs, and version 3.2, with FINAL2026-07-15 status, defines the new functions needed for ML-KEM, ML-DSA and SLH-DSA. This lesson makes clear what the sentence “our HSM supports PKCS#11” means in a PQC context (and what it does not).

New functions: why the existing ones are not enough

In earlier PKCS#11 versions, key agreement (like DH) was done with the C_DeriveKey function: two parties combined their existing keys to derive a new key. As you saw in M3, a KEM does not fit this symmetric pattern: one side encapsulates (producing both a secret and a ciphertext), the other side decapsulates (decrypting the ciphertext to reach the secret). This new pattern does not fit into C_DeriveKey; so PKCS#11 v3.2 added two new functions for exactly this need: C_EncapsulateKey and C_DecapsulateKey (specification sections 5.18.8-5.18.9). A concrete way to verify that an application uses a “PQC-ready” PKCS#11 library: check whether these two functions really exist, rather than just looking at the version number (another example of the “the version number alone is not enough” lesson from M0).

Mechanisms: a separate section for each algorithm family

The specification defines a separate mechanism section for each new algorithm family: ML-DSA (section 6.67, for key generation and signing), ML-KEM (section 6.68, for key generation and encapsulate/decapsulate), SLH-DSA (section 6.69). The existing hash-based signature mechanisms (HSS, XMSS/XMSS-MT) were also renamed, to make naming consistent with ML-DSA.

A few recurring concepts in PKCS#11’s C API, before reading the examples below: a session is a session the application opens to the HSM (you can think of it like a database connection); a template is an array listing the attributes of the key to be generated (is it extractable, which operations can it be used for); a handle is a reference number the application holds for an object (such as a key) inside the HSM, not the key itself. These three concepts (session, template, handle) recur in every PKCS#11 call; what is new in the examples below is only the mechanism constants (CKM_...) and the two new functions.

A practical code example of what mechanism selection looks like for generating an ML-DSA-65 key pair (not tested with a real PKCS#11 library, inferred from the specification’s own mechanism naming pattern, so marked [not run]):

CK_MECHANISM mechanism = { CKM_ML_DSA_KEY_PAIR_GEN, NULL_PTR, 0 };
CK_RV rv = C_GenerateKeyPair(session, &mechanism,
                              pub_template, pub_count,
                              priv_template, priv_count,
                              &pub_handle, &priv_handle);

And an ML-KEM encapsulate/decapsulate call pair, showing how C_EncapsulateKey/C_DecapsulateKey work through the same call pattern (mechanism + handles) as the C_GenerateKeyPair you just saw (also [not run], inferred from the specification’s function signature):

CK_MECHANISM mechanism = { CKM_ML_KEM, NULL_PTR, 0 };
// Encapsulating side: encapsulate to the peer's public key (peer_pubkey_handle)
CK_RV rv = C_EncapsulateKey(session, &mechanism, peer_pubkey_handle,
                             secret_template, secret_count,
                             &shared_secret_handle,
                             ciphertext, &ciphertext_len);
// Decapsulating side: decapsulate with its own private key (priv_handle)
CK_RV rv2 = C_DecapsulateKey(session, &mechanism, priv_handle,
                              secret_template, secret_count,
                              ciphertext, ciphertext_len,
                              &shared_secret_handle2);

Names such as peer_pubkey_handle and priv_handle here are also handles; this is the PKCS#11 call-level counterpart of the encapsulate/decapsulate logic you saw in M3 (one side produces a ciphertext and a secret with the public key; the other side decrypts the ciphertext with the private key and reaches the same secret).

Other new mechanisms: not just ML-KEM and ML-DSA

v3.2 does not stop at adding ML-KEM and ML-DSA; it also defines its own mechanism for SLH-DSA (section 6.69, hash-based, the “conservative backup” option you saw in M4). The existing stateful hash-based signature mechanisms (HSS, XMSS/XMSS-MT) were also renamed; names such as CK_XMSS_PARAMETER_SET_TYPE were updated to match ML-DSA’s naming pattern. This is not just cosmetic: integration code written against the old naming in a code base must account for this renaming when moving to v3.2.

A warning: being a standard does not mean every HSM supports it

PKCS#11 v3.2 being a full OASIS Standard (not a draft) does not mean an HSM actually implements these mechanisms. The standard defines what the interface looks like; a vendor’s firmware actually implementing that interface is a separate step, and as you will see in the next lesson (hsm-validation-reality), there is another separate gap between the claim “we implement it” and “independently validated”. Asking about these three levels separately (does the standard exist, does the vendor implement it, has it been independently validated) is the core discipline of the rest of this module.

Numbers to know

  • PKCS#11 v3.2 became a full OASIS Standard on 15 July 2026; it defines the ML-DSA (6.67), ML-KEM (6.68) and SLH-DSA (6.69) mechanisms and the new C_EncapsulateKey/C_DecapsulateKey functions

Lab: Verify the mechanism and function names in the specification

[not run] This is a verification exercise; running it on a real HSM or SoftHSM2 is done in Project 2 (PQC PKI)

Requires: internet access (to open the specification page); no real PKCS#11 library needed. Check your setup

shell
# Open docs.oasis-open.org/pkcs11/pkcs11-spec/v3.2/pkcs11-spec-v3.2.html and find section 6.67
Recorded output
You will see mechanism constants such as CKM_ML_DSA_KEY_PAIR_GEN and the parameters needed for ML-DSA key generation and signing
shell
# Find sections 5.18.8 and 5.18.9 (C_EncapsulateKey, C_DecapsulateKey)
Recorded output
You will see the full signature of these two functions (parameters, return type) and confirm which real specification text the code example in this lesson rests on

At the table

How to say this in a bank meeting.

To an executive
For our HSMs to support PQC, both the hardware firmware and the software libraries we use must implement the current PKCS#11 standard (v3.2); the two must be checked separately.
To an architect
The presence of the C_EncapsulateKey/C_DecapsulateKey functions is concrete, checkable evidence that a library really supports ML-KEM at the PKCS#11 level; do not rely only on a 'we support v3.2' statement.
Objection
“"Our library says PKCS#11 v3.2. Isn't that enough?"”
Answer
The version number alone is not enough (a repeat of the lesson you saw in M0): you need to confirm with a test call whether these two functions are really implemented.

Sources

Checkpoint

Answer first, then compare with the model answer and score yourself against the rubric. Saved in this browser only.

  1. 01Recall

    What are the two new functions PKCS#11 v3.2 defines for KEM operations?

  2. 02Recall

    When was PKCS#11 v3.2 approved, and with what status (draft or full standard)?

  3. 03Scenario

    An application developer is trying to use the existing C_DeriveKey function for ML-KEM. Tell them what is wrong and which function they should use.

  4. 04Hostile

    An architect says 'Our HSM supports PKCS#11, so it automatically supports ML-KEM.' Is that right? Why.

Project linkThe source of the mechanism names used when generating keys through a software HSM (SoftHSM2/PKCS#11) in Project 2 (PQC PKI).