使用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
相关产品推荐
相关产品推荐

