从文件摘要创建pkcs7签名

目前我有一个客户端 – 服务器应用程序,给定一个PDF文件,签名(使用服务器证书),将签名附加到原始文件并将输出返回给客户端(所有这些都是通过PDFBox实现的)。
我有一个签名处理程序,这是我的外部签名支持(其中内容是PDF文件)

public byte[] sign(InputStream content) throws IOException { try { System.out.println("Generating CMS signed data"); CMSSignedDataGenerator generator = new CMSSignedDataGenerator(); ContentSigner sha1Signer = new JcaContentSignerBuilder("Sha1WithRSA").build(privateKey); generator.addSignerInfoGenerator( new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()) .build(sha1Signer, new X509CertificateHolder(certificate.getEncoded()))); CMSTypedData cmsData = new CMSProcessableByteArray(IOUtils.toByteArray(content)); CMSSignedData signedData = generator.generate(cmsData, false); return signedData.getEncoded(); } catch (GeneralSecurityException e) { throw new IOException(e); } catch (CMSException e) { throw new IOException(e); } catch (OperatorCreationException e) { throw new IOException(e); } } 

它工作正常,但我在想 – 如果PDF文件太大而无法上传怎么办? 例如:100mb ……这需要永远! 鉴于此,我想弄清楚,如果不是签署PDF文件,是否可以只签署该文件的哈希(前SHA1),而不是客户端最终将它们全部放在一起?

更新:

我一直试图解决这个问题,现在我的签名方法是:

  @Override public byte[] sign(InputStream content) throws IOException { // testSHA1WithRSAAndAttributeTable try { MessageDigest md = MessageDigest.getInstance("SHA1", "BC"); List certList = new ArrayList(); CMSTypedData msg = new CMSProcessableByteArray(IOUtils.toByteArray(content)); certList.add(certificate); Store certs = new JcaCertStore(certList); CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); Attribute attr = new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(md.digest(IOUtils.toByteArray(content))))); ASN1EncodableVector v = new ASN1EncodableVector(); v.add(attr); SignerInfoGeneratorBuilder builder = new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider()) .setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v))); AlgorithmIdentifier sha1withRSA = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA"); CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); InputStream in = new ByteArrayInputStream(certificate.getEncoded()); X509Certificate cert = (X509Certificate) certFactory.generateCertificate(in); gen.addSignerInfoGenerator(builder.build( new BcRSAContentSignerBuilder(sha1withRSA, new DefaultDigestAlgorithmIdentifierFinder().find(sha1withRSA)) .build(PrivateKeyFactory.createKey(privateKey.getEncoded())), new JcaX509CertificateHolder(cert))); gen.addCertificates(certs); CMSSignedData s = gen.generate(new CMSAbsentContent(), false); return new CMSSignedData(msg, s.getEncoded()).getEncoded(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); throw new IOException(e); } } 

我正在使用pdfbox将PDF签名与PDF合并

  ExternalSigningSupport externalSigning = document.saveIncrementalForExternalSigning(output); byte[] cmsSignature = sign(externalSigning.getContent()); externalSigning.setSignature(cmsSignature); 

问题是Adobe表示签名无效,因为“文档自签名后已被更改或损坏”。 有人可以帮忙吗?

在他的更新中,OP几乎是正确的,只有两个错误:

  • 他尝试两次读取InputStream参数内容:

     CMSTypedData msg = new CMSProcessableByteArray(IOUtils.toByteArray(content)); [...] Attribute attr = new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(md.digest(IOUtils.toByteArray(content))))); 

    因此,在第二次尝试之前已经从流中读取了所有数据,因此返回空byte[] 。 因此,消息摘要属性包含错误的哈希值。

  • 他以一种错综复杂的方式创建了最终的CMS容器:

     return new CMSSignedData(msg, s.getEncoded()).getEncoded(); 

将后者减少到实际需要,结果certificate不再需要CMSTypedData msg 。 因此,前者被隐含地解决了。

在将摘要计算重新安排到方法的顶部并且另外切换到SHA256之后(因为在许多上下文中不推荐使用SHA1,我更喜欢使用不同的哈希算法)并且允许使用证书chain而不是单个certificate ,看起来像这样:

 // Digest generation step MessageDigest md = MessageDigest.getInstance("SHA256", "BC"); byte[] digest = md.digest(IOUtils.toByteArray(content)); // Separate signature container creation step List certList = Arrays.asList(chain); JcaCertStore certs = new JcaCertStore(certList); CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); Attribute attr = new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(digest))); ASN1EncodableVector v = new ASN1EncodableVector(); v.add(attr); SignerInfoGeneratorBuilder builder = new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider()) .setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v))); AlgorithmIdentifier sha256withRSA = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256withRSA"); CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); InputStream in = new ByteArrayInputStream(chain[0].getEncoded()); X509Certificate cert = (X509Certificate) certFactory.generateCertificate(in); gen.addSignerInfoGenerator(builder.build( new BcRSAContentSignerBuilder(sha256withRSA, new DefaultDigestAlgorithmIdentifierFinder().find(sha256withRSA)) .build(PrivateKeyFactory.createKey(pk.getEncoded())), new JcaX509CertificateHolder(cert))); gen.addCertificates(certs); CMSSignedData s = gen.generate(new CMSAbsentContent(), false); return s.getEncoded(); 

( CreateSignature方法signWithSeparatedHashing

用于相当小的签名代码框架

 void sign(PDDocument document, OutputStream output, SignatureInterface signatureInterface) throws IOException { PDSignature signature = new PDSignature(); signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED); signature.setName("Example User"); signature.setLocation("Los Angeles, CA"); signature.setReason("Testing"); signature.setSignDate(Calendar.getInstance()); document.addSignature(signature); ExternalSigningSupport externalSigning = document.saveIncrementalForExternalSigning(output); byte[] cmsSignature = signatureInterface.sign(externalSigning.getContent()); externalSigning.setSignature(cmsSignature); } 

( CreateSignature方法sign

喜欢这个

 try ( InputStream resource = getClass().getResourceAsStream("test.pdf"); OutputStream result = new FileOutputStream(new File(RESULT_FOLDER, "testSignedWithSeparatedHashing.pdf")); PDDocument pdDocument = PDDocument.load(resource) ) { sign(pdDocument, result, data -> signWithSeparatedHashing(data)); } 

( CreateSignature测试方法testSignWithSeparatedHashing

导致正确签名的PDF,至少正如所讨论的证书和私钥适用于手头的任务。


一句话:

OP使用了IOUtils.toByteArray(content)) (我在上面的代码中也是如此)。 但考虑到OP的首发言论

如果PDF文件太大而无法上传怎么办? 例如:100mb

这样做并不是一个好主意,因为它只会立即将一个大文件加载到内存中进行散列。 如果一个人真的想要考虑一个应用程序的资源占用空间,那么应该一次读取几KB的流并使用MessageDigest.update连续地消化数据,并且仅在末尾使用MessageDigest.digest来获取结果散列值。