【发布时间】:2018-12-02 10:48:05
【问题描述】:
我正在尝试以 CreateSignature 示例为起点,并对其进行更改以使其与我们基于外部网络的 HSM 系统一起使用。
生成的 PDF 文档总是抱怨“文档已被更改”。我对应该使用什么签名缺乏洞察力。
下面是我的CreateSignatureBase.java中sign()的实现:
@Override
public byte[] sign(InputStream content) throws IOException {
// cannot be done private (interface)
try {
// Certificate chain is acquired at initialization
List<Certificate> certList = new ArrayList<>();
certList.addAll(Arrays.asList(certificateChain));
Store<?> certs = new JcaCertStore(certList);
org.bouncycastle.asn1.x509.Certificate cert = org.bouncycastle.asn1.x509.Certificate.getInstance(certificateChain[0].getEncoded());
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
//HSMSigner is the class that interacts with the network HSM to get the signature.
HSMSigner signer = new HSMSigner();
byte[] input = IOUtil.toByteArray(content);
//SignedHash is a base64-encoded PKCS1 block. see HSMSigner.getSignature() below
final String signedHash = signer.getSignature(input);
ContentSigner sha1Signer = new ContentSigner() {
@Override
public byte[] getSignature() {
return Base64.getDecoder().decode(signedHash);
}
@Override
public OutputStream getOutputStream() {
return new ByteArrayOutputStream();
}
@Override
public AlgorithmIdentifier getAlgorithmIdentifier() {
return new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256WITHRSAENCRYPTION");
}
};
gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(sha1Signer, new X509CertificateHolder(cert)));
gen.addCertificates(certs);
CMSProcessableInputStream msg = new CMSProcessableInputStream(content);
CMSSignedData cmsSignedData = gen.generate(msg, true);
byte[] result = cmsSignedData.getEncoded();
return result;
} catch (GeneralSecurityException | CMSException | OperatorCreationException e) {
throw new IOException(e);
}
}
以下是HSMSigner().getSignature()的实现
public String getSignature(byte [] bytes) {
String host = "hsmvip.corp.com";
int port = 9000;
SignClient signClient;
try {
//initialize the sign client
signClient = ///..;
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(bytes);
byte[] outputDigest = messageDigest.digest();
// signature returned by the sign method is a base64-encoded PKCS1 block.
String signature = signClient.sign(Base16.encodeAsString(outputDigest));
signClient.close();
return signature;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
非常感谢任何帮助找出我做错了什么。
可在https://file.io/2tVvYO找到已签名的 pdf 副本
提前致谢!
【问题讨论】:
-
以上文件链接失效。请使用gofile.io/?c=XvridR
-
在您的代码中,您使用
InputStream content,创建其字节的 PKCS#1 签名,然后尝试基于此签名构建 CMS 签名容器。这是错误的做法,InputStream content中的数据不能直接签名,而是必须进行散列,那么它们的散列只是一种键值对映射中的一个值,而这个映射(称为“签名属性") 将由您的 HSM 签名。我无法判断至少您的HSMSigner().getSignature()是否正确,这取决于signClient的行为。 -
嗨@mlk,感谢您的指导,我能够根据您的建议更新我的代码并使其正常工作。我已经更新了下面的代码。
标签: java pdf pdfbox bouncycastle