如何在Java中从PKCS#11安全令牌删除公钥?
问题描述
我正在修改一款Java软件,要通过Sun PKCS#11 Provider在TPM2 HSM上实现密钥对和证书的创建、删除操作。目前已完成密钥对及证书的存储,也能删除私钥和证书,但公钥会残留,无法通过Java代码删除。
密钥创建与存储代码
KeyStore keyStore = KeyStore.getInstance("PKCS11", "SunPKCS11-libtpm2"); keyStore.load(null, "userpin".toCharArray()); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "SunPKCS11-libtpm2"); keyPairGenerator.initialize(2048); KeyPair keyPair = keyPairGenerator.generateKeyPair(); keyStore.setKeyEntry("WorkstationAuthentication", keyPair.getPrivate(), null, getCertificates());
执行后pkcs11-tool的输出
# pkcs11-tool --module /usr/lib/x86_64-linux-gnu/pkcs11/libtpm2_pkcs11.so --slot-index=0 --list-objects --login Using slot with index 0 (0x1) Logging in to "myfirsttoken". Please enter User PIN: Public Key Object; RSA 2048 bits label: Usage: encrypt, verify Access: local Private Key Object; RSA label: ID: 534b5332303038576f726b73746174696f6e41757468656e7469636174696f6e Usage: decrypt, sign Access: always sensitive, never extractable, local Allowed mechanisms: RSA-X-509,RSA-PKCS-OAEP,RSA-PKCS,SHA1-RSA-PKCS,SHA256-RSA-PKCS,SHA384-RSA-PKCS,SHA512-RSA-PKCS,RSA-PKCS-PSS,SHA1-RSA-PKCS-PSS,SHA256-RSA-PKCS-PSS,SHA384-RSA-PKCS-PSS,SHA512-RSA-PKCS-PSS Certificate Object; type = X.509 cert label: WorkstationAuthentication subject: DN: ID: 534b5332303038576f726b73746174696f6e41757468656e7469636174696f6e Certificate Object; type = X.509 cert label: subject: DN: DC=com, DC=example, CN=example-CA ID: 636e3d736173636572742d63612c64633d233136303737333631373336333635373237342c64633d2331363032366636392f636e3d736173636572742d63612c64633d233136303737333631373336333635373237342c64633d2331363032366636392f3332333331303437363031363137343435323138303332383239393236343439353035313332
删除操作的代码及结果
用以下代码删除私钥和证书:
if (keyStore.containsAlias("WorkstationAuthentication")) { keyStore.deleteEntry("WorkstationAuthentication"); }
执行后pkcs11-tool显示私钥和证书已删除,但公钥残留:
# pkcs11-tool --module /usr/lib/x86_64-linux-gnu/pkcs11/libtpm2_pkcs11.so --slot-index=0 --list-objects --login Using slot with index 0 (0x1) Logging in to "myfirsttoken". Please enter User PIN: Public Key Object; RSA 2048 bits label: Usage: encrypt, verify Access: local
当前核心问题:
- Java的
KeyStoreAPI无法直接定位并删除无label、无ID的残留公钥 - 希望要么在Java中实现公钥删除,要么阻止公钥被存储到令牌(证书已包含公钥,无需单独存储)
解决方案
一、阻止公钥被存储到令牌
问题根源是Sun PKCS#11 Provider默认会将生成的公钥同步存储到HSM。要避免这个行为,可通过以下方式处理:
仅存储私钥和证书
生成密钥对后,不依赖KeyPairGenerator自动存储公钥,仅调用setKeyEntry存储私钥和证书(证书已包含公钥)。不过部分PKCS11模块(如libtpm2_pkcs11)可能强制存储公钥,这种方式不一定生效。使用底层PKCS11 API生成私钥
绕过KeyPairGenerator的自动存储逻辑,直接调用PKCS11底层API生成私钥,再将证书关联到私钥。这种方式可以完全控制是否存储公钥。
二、在Java中删除残留公钥
针对无label、无ID的残留公钥,可通过遍历HSM上的所有对象,匹配特征后删除:
import sun.security.pkcs11.PKCS11; import sun.security.pkcs11.SunPKCS11; import java.security.Security; public class PKCS11PublicKeyCleanup { public static void main(String[] args) throws Exception { // 获取Sun PKCS11 Provider实例 SunPKCS11 provider = (SunPKCS11) Security.getProvider("SunPKCS11-libtpm2"); PKCS11 pkcs11 = provider.getPKCS11(); long slotId = 0; // 对应slot-index=0 char[] pin = "userpin".toCharArray(); // 打开读写会话并登录 long session = pkcs11.C_OpenSession(slotId, PKCS11.CKF_SERIAL_SESSION | PKCS11.CKF_RW_SESSION, null, null); pkcs11.C_Login(session, PKCS11.CKU_USER, pin); try { // 设置搜索模板:查找2048位RSA公钥 long[] searchTemplate = new long[]{ PKCS11.CKA_CLASS, PKCS11.CKO_PUBLIC_KEY, PKCS11.CKA_KEY_TYPE, PKCS11.CKK_RSA, PKCS11.CKA_MODULUS_BITS, 2048L }; // 初始化对象搜索 pkcs11.C_FindObjectsInit(session, searchTemplate); long[] objectHandles = new long[1]; int count; while ((count = pkcs11.C_FindObjects(session, objectHandles, 1)) > 0) { long pubKeyHandle = objectHandles[0]; // 获取公钥的label和ID属性 byte[] label = (byte[]) pkcs11.C_GetAttributeValue(session, pubKeyHandle, new long[]{PKCS11.CKA_LABEL})[0]; byte[] id = (byte[]) pkcs11.C_GetAttributeValue(session, pubKeyHandle, new long[]{PKCS11.CKA_ID})[0]; // 匹配无label、无ID的目标公钥 if ((label == null || label.length == 0) && (id == null || id.length == 0)) { pkcs11.C_DestroyObject(session, pubKeyHandle); break; // 仅删除目标公钥,避免误删 } } // 结束搜索 pkcs11.C_FindObjectsFinal(session); } finally { // 登出并关闭会话 pkcs11.C_Logout(session); pkcs11.C_CloseSession(session); } } }
注意事项:
- 该代码依赖Sun/Oracle JDK的内部
sun.security.pkcs11包,OpenJDK通常也支持,但需注意版本兼容性 - 需处理
PKCS11Exception等异常,避免会话泄漏 - 遍历对象时需精准匹配特征,防止误删其他有效公钥
内容的提问来源于stack exchange,提问作者Bill
相关产品推荐
相关产品推荐

