基于远程签名服务的签名创建CMS签名遇验证错误,如何修复?
CMS签名验证失败问题排查与修复
问题描述
我通过Remote Signing Service生成签名后创建CMS签名,操作流程如下:
- 对需签名的消息进行SHA256哈希
- 将哈希值发送至Remote Signing Service获取签名
- 使用以下Java代码生成包含该签名的p7m文件
public static void signMessage() { String messageToBeSign = "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout."; byte[] dataToSign = messageToBeSign.getBytes(); // Log hashed data to send it to Remote Signing Service // MessageDigest digest = MessageDigest.getInstance("SHA-256"); // byte[] hashedData = digest.digest(dataToSign); // System.out.println(Base64.getEncoder().encodeToString(hashedData)); // Load signature recieved from Remote Signing Service byte[] remoteSignature = Base64.getDecoder().decode(remoteSignatureBase64String); Security.addProvider(new BouncyCastleProvider()); // Load signing certificate byte[] certBytes = Base64.getDecoder().decode(base64CertChain[0]); X509CertificateHolder certHolder = new X509CertificateHolder(certBytes); DigestCalculatorProvider digestCalculatorProvider = new JcaDigestCalculatorProviderBuilder().build(); JcaSignerInfoGeneratorBuilder signerInfoGenBuilder = new JcaSignerInfoGeneratorBuilder(digestCalculatorProvider); ContentSigner contentSigner = new PrecomputedSigner(remoteSignature); SignerInfoGenerator signerInfoGenerator = signerInfoGenBuilder.build(contentSigner, certHolder); // Load full certificate chain List<X509CertificateHolder> certChain = new ArrayList<>(); for (String base64Cert : base64CertChain) { certChain.add(new X509CertificateHolder(Base64.getDecoder().decode(base64Cert))); } Store certStore = new JcaCertStore(certChain); CMSSignedDataGenerator generator = new CMSSignedDataGenerator(); generator.addSignerInfoGenerator(signerInfoGenerator); generator.addCertificates(certStore); CMSProcessableByteArray cmsData = new CMSProcessableByteArray(dataToSign); CMSSignedData signedData = generator.generate(cmsData, true); byte[] p7m = signedData.getEncoded(); try (FileOutputStream fos = new FileOutputStream(signedFileOutput)) { fos.write(p7m); } System.out.println("Successfully create a new signature to the .p7m file!"); } //Helper class to handle the precomputed signature from the Remote Signing Service class PrecomputedSigner implements ContentSigner { private final byte[] signature; public PrecomputedSigner(byte[] signature) { this.signature = signature; } @Override public byte[] getSignature() { return signature; } @Override public AlgorithmIdentifier getAlgorithmIdentifier() { return new AlgorithmIdentifier(PKCSObjectIdentifiers.sha256WithRSAEncryption); } @Override public OutputStream getOutputStream() { return new ByteArrayOutputStream(); // Not used since the signature is precomputed } }
生成p7m文件后,使用DSS Demonstration WebApp验证时出现以下错误:
- The signature is not intact!
- The signed attribute: 'signing-certificate' is absent!
错误原因
- 签名不完整:你错误地对原始消息的哈希进行了远程签名,但CMS签名的实际签名对象是签名属性集合的哈希值。验证时DSS会重新计算签名属性集合的哈希,再和签名解密后的哈希对比,两者不匹配导致验证失败。
- 缺少signing-certificate属性:默认配置下
JcaSignerInfoGeneratorBuilder不会自动添加这个属性,而DSS验证要求该属性必须存在,用于明确标识签名证书。
修复方案
1. 调整远程签名的对象
需要先构造CMS的签名属性集合,对该集合的哈希值进行远程签名,而不是原始消息的哈希。
2. 添加signing-certificate签名属性
通过JcaSignerInfoGeneratorBuilder配置添加该属性,同时修正PrecomputedSigner的逻辑,确保签名对应正确的哈希对象。
修改后的完整代码
import org.bouncycastle.asn1.ASN1Encoding; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.cms.*; import org.bouncycastle.cms.jcajce.JcaCertStore; import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.DigestCalculatorProvider; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import org.bouncycastle.x509.X509CertificateHolder; import java.io.ByteArrayOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.security.MessageDigest; import java.security.Security; import java.util.ArrayList; import java.util.Base64; import java.util.List; public class CMSSignatureFix { private static String remoteSignatureBase64String; // 从Remote Signing Service获取的签名 private static String[] base64CertChain; // 完整证书链的Base64数组 private static String signedFileOutput; // 输出p7m文件路径 public static void signMessage() throws IOException, OperatorCreationException { String messageToBeSign = "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout."; byte[] dataToSign = messageToBeSign.getBytes(); Security.addProvider(new BouncyCastleProvider()); // 加载签名证书 byte[] certBytes = Base64.getDecoder().decode(base64CertChain[0]); X509CertificateHolder certHolder = new X509CertificateHolder(certBytes); // 1. 构建签名属性集合(包含signing-certificate属性) DigestCalculatorProvider digestCalculatorProvider = new JcaDigestCalculatorProviderBuilder().build(); JcaSignerInfoGeneratorBuilder signerInfoGenBuilder = new JcaSignerInfoGeneratorBuilder(digestCalculatorProvider); // 添加signing-certificate签名属性 signerInfoGenBuilder.setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator() { @Override public org.bouncycastle.asn1.cms.AttributeTable getAttributes(org.bouncycastle.asn1.cms.AttributeTable parameters) { org.bouncycastle.asn1.cms.Attribute signingCertAttr = new org.bouncycastle.asn1.cms.Attribute( PKCSObjectIdentifiers.id_aa_signingCertificate, new org.bouncycastle.asn1.DERSet(new org.bouncycastle.asn1.x509.SigningCertificate( new org.bouncycastle.asn1.x509.IssuerSerial( certHolder.getIssuer(), certHolder.getSerialNumber() ) )) ); return super.getAttributes(parameters).add(signingCertAttr); } }); // 2. 预计算签名属性的哈希(实际使用时先执行这段获取哈希,发送到远程服务拿签名) /* ContentSigner dummySigner = new ContentSigner() { @Override public org.bouncycastle.asn1.x509.AlgorithmIdentifier getAlgorithmIdentifier() { return new org.bouncycastle.asn1.x509.AlgorithmIdentifier(PKCSObjectIdentifiers.sha256WithRSAEncryption); } @Override public OutputStream getOutputStream() { return new ByteArrayOutputStream(); } @Override public byte[] getSignature() { return new byte[0]; } }; SignerInfoGenerator tempSignerInfoGen = signerInfoGenBuilder.build(dummySigner, certHolder); CMSSignedDataGenerator tempGenerator = new CMSSignedDataGenerator(); tempGenerator.addSignerInfoGenerator(tempSignerInfoGen); CMSProcessableByteArray tempCmsData = new CMSProcessableByteArray(dataToSign); CMSSignedData tempSignedData = tempGenerator.generate(tempCmsData, false); SignerInformation signerInfo = tempSignedData.getSignerInfos().getSigners().iterator().next(); byte[] signedAttrBytes = signerInfo.getSignedAttributes().getEncoded(ASN1Encoding.DER); MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] hashToSend = digest.digest(signedAttrBytes); System.out.println(Base64.getEncoder().encodeToString(hashToSend)); // 发送此哈希到远程服务 */ // 加载远程签名服务返回的签名 byte[] remoteSignature = Base64.getDecoder().decode(remoteSignatureBase64String); ContentSigner contentSigner = new PrecomputedSigner(remoteSignature); // 构建签名器信息生成器 SignerInfoGenerator signerInfoGenerator = signerInfoGenBuilder.build(contentSigner, certHolder); // 加载完整证书链 List<X509CertificateHolder> certChain = new ArrayList<>(); for (String base64Cert : base64CertChain) { certChain.add(new X509CertificateHolder(Base64.getDecoder().decode(base64Cert))); } JcaCertStore certStore = new JcaCertStore(certChain); // 生成CMS签名数据 CMSSignedDataGenerator generator = new CMSSignedDataGenerator(); generator.addSignerInfoGenerator(signerInfoGenerator); generator.addCertificates(certStore); CMSProcessableByteArray cmsData = new CMSProcessableByteArray(dataToSign); CMSSignedData signedData = generator.generate(cmsData, true); byte[] p7m = signedData.getEncoded(); // 写入文件 try (FileOutputStream fos = new FileOutputStream(signedFileOutput)) { fos.write(p7m); } System.out.println("Successfully created .p7m file!"); } // 修正后的PrecomputedSigner static class PrecomputedSigner implements ContentSigner { private final byte[] signature; public PrecomputedSigner(byte[] signature) { this.signature = signature; } @Override public org.bouncycastle.asn1.x509.AlgorithmIdentifier getAlgorithmIdentifier() { return new org.bouncycastle.asn1.x509.AlgorithmIdentifier(PKCSObjectIdentifiers.sha256WithRSAEncryption); } @Override public OutputStream getOutputStream() { return new ByteArrayOutputStream(); // 未使用,因为签名是预计算的 } @Override public byte[] getSignature() { return signature; } } }
关键修改说明
- 添加signing-certificate属性:通过重写
DefaultSignedAttributeTableGenerator的getAttributes方法,手动添加该签名属性,满足DSS验证的合规要求。 - 调整签名哈希对象:现在需要发送签名属性集合的哈希给远程签名服务,而不是原始消息的哈希。代码中注释部分展示了如何获取这个哈希值,实际使用时先执行该部分获取哈希,发送到远程服务得到签名后,再继续执行后续代码。
内容的提问来源于stack exchange,提问作者Minh Vu
相关产品推荐
相关产品推荐

