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.