Java 使用网络HSM和PDFBox签署PDF
我试图以CreateSignature示例为起点,对其进行更改,使其与基于外部网络的HSM系统一起工作 生成的PDF文档总是抱怨“文档已被更改”。我对应该用什么来签名缺乏洞察力 以下是my CreateSignatureBase.java中sign()的实现:Java 使用网络HSM和PDFBox签署PDF,java,pdf,pdfbox,bouncycastle,Java,Pdf,Pdfbox,Bouncycastle,我试图以CreateSignature示例为起点,对其进行更改,使其与基于外部网络的HSM系统一起工作 生成的PDF文档总是抱怨“文档已被更改”。我对应该用什么来签名缺乏洞察力 以下是my CreateSignatureBase.java中sign()的实现: @Override public byte[] sign(InputStream content) throws IOException { // cannot be done private (interfac
@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);
}
}
非常感谢您能帮我找出我做错了什么
签名pdf的副本可在以下网址找到:
提前谢谢 感谢您的详细回复,我能够更新CreateSignatureBase.sign()方法,如下所示,以获得所需的结果 CreateSignautreBase.java:sign()
@覆盖
公共字节[]符号(InputStream内容)引发IOException{
//无法完成私有(接口)
试一试{
//初始化时获取证书链
List certList=new ArrayList();
addAll(Arrays.asList(certificateChain));
存储证书=新JcaCertStore(证书列表);
org.bouncycastle.asn1.x509.Certificate cert=org.bouncycastle.asn1.x509.Certificate.getInstance(certificateChain[0].getEncoded());
CMSSignedDataGenerator gen=新的CMSSignedDataGenerator();
//HSMSigner是与网络HSM交互以获取签名的类。
HSMSigner signer=新的HSMSigner();
字节[]输入=IOUtil.toByteArray(内容);
//这是对以前代码的更新。
//创建内容的散列并将其添加到属性映射
MessageDigest=MessageDigest.getInstance(“SHA-256”);
messageDigest.update(输入);
Attribute attr=new属性(CMSAttributes.messageDigest,new DERSet)(new DEROctetString(messageDigest.digest());
ASN1EncodableVector v=新的ASN1EncodableVector();
v、 添加(attr);
ContentSigner sha1Signer=新ContentSigner(){
//这是为了确保使用正确的数据创建签名。
ByteArrayOutputStream=新建ByteArrayOutputStream();
@凌驾
公共字节[]getSignature(){
//在这里调用HSM,流是AttributeMap
返回Base64.getDecoder().decode(signer.getSignature(stream.toByteArray());
}
//可能由BouncyCastle库调用以提供内容
@凌驾
public OutputStream getOutputStream(){
回流;
}
@凌驾
公共算法标识符getAlgorithmIdentifier(){
返回新的DefaultSignatureAlgorithmIdentifierFinder().find(“SHA256WithRSANYPTION”);
}
};
//根据mkl的评论,使用AttributeTable作为输入,其中该表已经具有内容的哈希值。
SignerInfoGeneratorBuilder=new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider())
.setSignedAttributeGenerator(新的DefaultSignedAttributeTableGenerator(新的AttributeTable(v)));
gen.addSignerInfoGenerator(建造商建造(sha1Signer,新X509认证持有人(cert));
一般证书(证书);
CMSProcessableInputStream msg=新的CMSProcessableInputStream(内容);
cmssignedata cmssignedata=gen.generate(msg,true);
byte[]result=cmsSignedData.getEncoded();
返回结果;
}catch(一般安全性异常| CMSException |运算符创建异常e){
抛出新的IOException(e);
}
}
HSMSigner.getSignature()保持不变 上面的文件链接不起作用。请在代码中使用您获取的
InputStream内容
,创建其字节的PKCS#1签名,然后尝试基于此签名构建CMS签名容器。这是错误的方法,InputStream content
中的数据不能直接签名,而是必须对其进行散列,然后它们的散列只是一种键值对映射中的一个值,而该映射(称为“签名属性”)将由HSM签名。我无法判断您的HSMSigner().getSignature()
是否正确,这取决于该signClient
的行为。您好@mlk,感谢您的指导,我能够根据您的建议更新我的代码并使其正常工作。我已经更新了下面的代码。嗨,你能告诉我HSM的工作情况吗。当我们发送摘要时它会返回什么。您在HSM中使用哪个库来签署摘要。我希望在不调用服务的情况下实现相同的逻辑。
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;
}
}
@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);
//This is the update over previous code.
//Create a hash of the content and add it to the attribute map
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(input);
Attribute attr = new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(messageDigest.digest())));
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(attr);
ContentSigner sha1Signer = new ContentSigner() {
//This is to ensure that signature is created using the right data.
ByteArrayOutputStream stream = new ByteArrayOutputStream();
@Override
public byte[] getSignature() {
//Calling HSM here instead, the stream is the AttributeMap
return Base64.getDecoder().decode(signer.getSignature(stream.toByteArray()));
}
//Perhaps called by BouncyCastle library to provide the content
@Override
public OutputStream getOutputStream() {
return stream;
}
@Override
public AlgorithmIdentifier getAlgorithmIdentifier() {
return new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256WITHRSAENCRYPTION");
}
};
//As per mkl's comment, using the AttributeTable as an input where the table already has a Hashed value of the content.
SignerInfoGeneratorBuilder builder = new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider())
.setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v)));
gen.addSignerInfoGenerator(builder.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);
}
}