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

使用iText+SM3WITHSM2签名PDF验证失败,求问题排查方案

SM3WITHSM2算法PDF签名验证失败问题排查

问题描述

使用iText对PDF文件进行数字签名,采用SM3WITHSM2算法,但签名后的PDF无法通过验证,相关实现代码如下:

实现代码

签名主方法

public DeferredSignResult machineSign(MachineSignParam machineSignParam) throws Exception {
    CipherMachineService provider = getProvider();
    URL pdfUrl = machineSignParam.getPdfUrl();
    URL sealUrl = machineSignParam.getSealUrl();
    Rectangle rectangle = machineSignParam.getRectangle();
    int page = machineSignParam.getPage();
    int containerNum = machineSignParam.getContainerNum();
    Certificate[] certificates = machineSignParam.getCertificates();

    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    PdfReader pdfReader = new PdfReader(pdfUrl);
    PdfStamper pdfSigner = PdfStamper.createSignature(pdfReader, byteArrayOutputStream, '\0', null, true);
    PdfSignatureAppearance pdfSignatureAppearance = pdfSigner.getSignatureAppearance();
    Image imageData = Image.getInstance(sealUrl);
    pdfSignatureAppearance.setReason("");
    pdfSignatureAppearance.setLocation("");
    pdfSignatureAppearance.setContact("");
    pdfSignatureAppearance.setLayer2Text("");
    pdfSignatureAppearance.setLayer4Text("");
    pdfSignatureAppearance.setSignatureCreator("");
    pdfSignatureAppearance.setOpacity(1F);
    pdfSignatureAppearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);
    pdfSignatureAppearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.DESCRIPTION);
    pdfSignatureAppearance.setImage(imageData);
    pdfSignatureAppearance.setVisibleSignature(rectangle, page, IdUtil.fastSimpleUUID());

    ExternalSignatureContainer externalSignatureContainer;
    if (provider instanceof Fisherman) {
        externalSignatureContainer = new FisherManSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED,
                containerNum, certificates);
    } else if (provider instanceof Sinocipher) {
        externalSignatureContainer = new SinoChipherSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED,
                containerNum, certificates);
    } else {
        throw new SystemException(1000, "未知的签章容器");
    }
    MakeSignature.signExternalContainer(pdfSignatureAppearance, externalSignatureContainer, 8192);
    byte[] buffer = byteArrayOutputStream.toByteArray();

    // 计算hash
    InputStream inputStreamHash = pdfSignatureAppearance.getRangeStream();
    BouncyCastleDigest bouncyCastleDigest = new BouncyCastleDigest();
    byte[] hash = DigestAlgorithms.digest(inputStreamHash, bouncyCastleDigest.getMessageDigest(HASH_ALGORITHM));
    PdfPKCS7 pkcs7 = new PdfPKCS7(null, certificates, HASH_ALGORITHM, null, bouncyCastleDigest, false);
    pkcs7.setExternalDigest(hash, null, ENCRYPTION_ALGORITHM);


    byte[] sh = pkcs7.getEncodedPKCS7(hash, pdfSignatureAppearance.getSignDate(), null, null, null, null, MakeSignature.CryptoStandard.CMS);
    pdfReader.close();
    DeferredSignResult deferredSignResult = new DeferredSignResult();
    deferredSignResult.setDeferredPdfInputStream(new ByteArrayInputStream(buffer));
    deferredSignResult.setPkcs1(hash);
    deferredSignResult.setPkcs7(sh);
    pdfReader.close();
    return deferredSignResult;
}

签名容器实现

public class FisherManSignatureContainer implements ExternalSignatureContainer {

/**
 * 加密机容器号
 */
private final int containerNum;
private final Fisherman fisherman;
private final Certificate[] certChain;
/**
 * hash算法
 */
private final String hashAlgorithm;
/**
 * 加密算法
 */
private final String encryptAlgorithm;

private Calendar signingTime = Calendar.getInstance();


public FisherManSignatureContainer(PdfName filter, PdfName subFilter, int containerNum, Certificate[] certChain) {
    PdfDictionary pdfDictionary = new PdfDictionary();
    pdfDictionary.put(PdfName.FILTER, filter);
    pdfDictionary.put(PdfName.SUBFILTER, subFilter);
    this.containerNum = containerNum;
    fisherman = new Fisherman();
    this.certChain = certChain;
    String algorithmGroup = CertUtils.getCertificateAlgorithmGroup(certChain[0]);
    String[] parsedAlgorithmGroup = CertUtils.parseAlgorithmGroup(algorithmGroup);
    hashAlgorithm = parsedAlgorithmGroup[0];
    encryptAlgorithm = parsedAlgorithmGroup[1];
}

@Override
public byte[] sign(InputStream inputStream) throws GeneralSecurityException {
    try {
        BouncyCastleDigest digest = new BouncyCastleDigest();
        byte[] hash = DigestAlgorithms.digest(inputStream, digest.getMessageDigest(hashAlgorithm));
        PdfPKCS7 pkcs7 = new PdfPKCS7(null, certChain, hashAlgorithm, null, new BouncyCastleDigest(), false);
        byte[] sh = pkcs7.getAuthenticatedAttributeBytes(hash, signingTime, null, null, MakeSignature.CryptoStandard.CMS);
        byte[] sign;
        if ("SM3".equalsIgnoreCase(hashAlgorithm)) {
            sign = fisherman.sign(sh, containerNum);
        } else {
            sign = fisherman.signRSA(sh, containerNum);
        }
        return getP7(hash,sign);
    } catch (Exception e) {
        log.error("渔翁加密机签章失败:{}", e.getMessage());
        throw new SystemException(1000, "渔翁加密机签章失败!");
    }
}

@Override
public void modifySigningDictionary(PdfDictionary pdfDictionary) {
    pdfDictionary.putAll(pdfDictionary);
}

byte[] getP7(byte[] plaintext,byte[] signValue) throws Exception {
    BouncyCastleDigest bouncyCastleDigest = new BouncyCastleDigest();
    PdfPKCS7 pdfPKCS7 = new PdfPKCS7(null, certChain, hashAlgorithm, null, bouncyCastleDigest, false);
    pdfPKCS7.setExternalDigest(signValue, null, encryptAlgorithm);
    return pdfPKCS7.getEncodedPKCS7(plaintext, signingTime,null,"sinoCipher",null,null, MakeSignature.CryptoStandard.CMS);
}
}

可能的问题点

  • 冗余签名流程干扰:调用MakeSignature.signExternalContainer后,又重复计算哈希并构造PKCS7,这会覆盖或干扰原本由FisherManSignatureContainer完成的正确签名流程,属于无效且错误的操作。
  • 签名字典无意义操作:modifySigningDictionary方法中pdfDictionary.putAll(pdfDictionary)未对签名字典做任何有效修改,需确保签名字典的算法标识、过滤器等参数正确配置。
  • SM2签名格式不兼容:渔翁加密机返回的SM2签名可能是r+s拼接的裸字节数组,而PKCS#7要求的是ASN.1 DER编码的签名结构(包含r和s的序列),需转换格式后再传入PKCS7构造方法。
  • 算法标识配置错误:需确认HASH_ALGORITHM和ENCRYPTION_ALGORITHM的取值为iText与BouncyCastle认可的SM3/SM2标准标识,比如SM3对应"SM3"或OID"1.2.156.10197.1.401",SM2对应"SM2"或OID"1.2.156.10197.1.301"。
  • PKCS7构造参数错误:getP7方法中调用getEncodedPKCS7时传入的plaintext是原始文档哈希,但SM2实际签名的是认证属性字节数组sh,此处参数传递错误,需调整为对应上下文数据。

内容的提问来源于stack exchange,提问作者chiao kang

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 21:47:03