You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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的KeyStore API无法直接定位并删除无label、无ID的残留公钥
  • 希望要么在Java中实现公钥删除,要么阻止公钥被存储到令牌(证书已包含公钥,无需单独存储)

解决方案

一、阻止公钥被存储到令牌

问题根源是Sun PKCS#11 Provider默认会将生成的公钥同步存储到HSM。要避免这个行为,可通过以下方式处理:

  1. 仅存储私钥和证书
    生成密钥对后,不依赖KeyPairGenerator自动存储公钥,仅调用setKeyEntry存储私钥和证书(证书已包含公钥)。不过部分PKCS11模块(如libtpm2_pkcs11)可能强制存储公钥,这种方式不一定生效。

  2. 使用底层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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 12:57:32