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

使用Apache POI结合HSM签名Office文档遇异常求助

解决Apache POI结合Utimaco HSM签名Office文档的私钥敏感字段异常问题

问题原因

Apache POI默认的Office签名实现(如XAdESSignatureConfig相关逻辑)在处理私钥时,会尝试读取私钥的敏感内部字段(比如privateExponent),而HSM中的私钥是受硬件保护、不可导出的,因此触发java.lang.UnsupportedOperationException: Private Exponent value is sensitive异常。而iText和XML签名的实现仅调用私钥标准的sign()方法,不会访问这些敏感属性,所以能正常工作。

解决方案:自定义签名逻辑绕过敏感字段读取

通过替换POI默认的签名实现,直接使用HSM提供的签名能力,避免POI直接操作私钥内部属性。

1. 加载HSM与BouncyCastle Provider

首先确保正确加载Utimaco HSM的Provider和BouncyCastle(用于兼容POI的签名流程):

import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
// 替换为Utimaco实际的Provider类
import com.utimaco.crypto.provider.UtimacoProvider;

// 加载Provider
Security.addProvider(new UtimacoProvider());
Security.addProvider(new BouncyCastleProvider());

2. 获取HSM中的私钥与证书链

通过PKCS#11 KeyStore获取HSM中的私钥和对应证书:

import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Collections;

// 初始化HSM KeyStore
KeyStore hsmKeyStore = KeyStore.getInstance("PKCS11");
hsmKeyStore.load(null, "你的HSM PIN".toCharArray());

// 获取私钥与证书
String keyAlias = "HSM中密钥的别名";
PrivateKey hsmPrivateKey = (PrivateKey) hsmKeyStore.getKey(keyAlias, "你的HSM PIN".toCharArray());
X509Certificate signingCert = (X509Certificate) hsmKeyStore.getCertificate(keyAlias);

3. 自定义HSM签名实现

重写POI的Signer类,直接调用HSM的签名API生成签名值,避免POI访问私钥敏感字段:

import org.apache.poi.poifs.crypt.dsig.Signer;
import java.security.Signature;
import java.security.SignatureException;

public class HSMSigner extends Signer {
    private final PrivateKey privateKey;
    private final String hsmProviderName;
    private final String signatureAlgorithm;

    public HSMSigner(PrivateKey privateKey, String hsmProviderName, String signatureAlgorithm) {
        this.privateKey = privateKey;
        this.hsmProviderName = hsmProviderName;
        this.signatureAlgorithm = signatureAlgorithm;
    }

    @Override
    public byte[] sign(byte[] data) throws SignatureException {
        try {
            // 使用HSM Provider创建签名实例
            Signature signature = Signature.getInstance(signatureAlgorithm, hsmProviderName);
            signature.initSign(privateKey);
            signature.update(data);
            return signature.sign();
        } catch (Exception e) {
            throw new SignatureException("HSM签名失败", e);
        }
    }
}

4. 配置POI签名流程并执行签名

将自定义签名器注入POI的签名配置,完成Office文档签名:

import org.apache.poi.poifs.crypt.dsig.SignatureConfig;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.poifs.crypt.dsig.XAdESSignature;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class OfficeHSMSigner {
    public static void main(String[] args) throws Exception {
        // 加载待签名文档
        XWPFDocument doc = new XWPFDocument(new FileInputStream("待签名文档.docx"));

        // 配置签名参数
        SignatureConfig signatureConfig = new SignatureConfig();
        signatureConfig.setSigningKey(hsmPrivateKey);
        signatureConfig.setSigningCertificateChain(Collections.singletonList(signingCert));
        // 注入自定义HSM签名器,指定算法与Provider
        signatureConfig.setSigner(new HSMSigner(hsmPrivateKey, "Utimaco", "SHA256withRSA"));

        // 执行签名
        XAdESSignature signature = new XAdESSignature(doc);
        signature.setSignatureConfig(signatureConfig);
        signature.sign(new FileOutputStream("已签名文档.docx"));
    }
}

关键注意事项

  • 确保使用POI 4.1.2及以上版本,旧版本对不可导出私钥的兼容较差。
  • 签名算法需与HSM支持的算法匹配(如SHA256withRSA、SHA384withECDSA等)。
  • 验证HSM Provider的配置正确,私钥对象确实是HSM返回的不可导出实例。

内容的提问来源于stack exchange,提问作者Dat Nguyen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 20:49:57