从文件摘要创建pkcs7签名

从文件摘要创建pkcs7签名

本文介绍了从文件摘要创建pkcs7签名的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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


我有一个签名处理程序,这是我的外部签名支持(其中内容是PDF文件)

  public byte [] sign(InputStream content)抛出IOException {
try {
System.out.println(生成CMS签名数据);
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);

返回signedData.getEncoded();
} catch(GeneralSecurityException e){
抛出新的IOException(e);
} catch(CMSException e){
抛出新的IOException(e);
} catch(OperatorCreationException e){
抛出新的IOException(e);
}
}

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

更新:



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

  @Override 
public byte [] sign(InputStream content)抛出IOException {
// testSHA1WithRSAAndAttributeTable
try {
MessageDigest md = MessageDigest.getInstance(SHA1,BC);
列表<证书> certList = new ArrayList< Certificate>();
CMSTypedData msg = new CMSProcessableByteArray(IOUtils.toByteArray(content));

certList.add(证书);

商店certs = new JcaCertStore(certList);

CMSSignedDataGenerator gen = new CMSSignedDataGenerator();

属性attr = new属性(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);
返回新的CMSSignedData(msg,s.getEncoded())。getEncoded();

} catch(例外e){
// TODO自动生成的catch块
e.printStackTrace();
抛出新的IOException(e);
}

}

我正在合并签名带有pdfbox的PDF

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

问题是Adobe说签名无效,因为文档已被更改或损坏,因为它签了。
任何人都可以帮忙吗?

解决方案

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




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

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

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


  • 他以复杂的方式创建最终的CMS容器:

     返回新的CMSSignedData(msg,s.getEncoded())。getEncoded(); 




将后者减少到实际需要的数量,事实证明,不再需要 CMSTypedData msg 。因此,前者是隐式解决的。



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

  //摘要生成步骤
MessageDigest md = MessageDigest.getInstance(SHA256,BC );
byte [] digest = md.digest(IOUtils.toByteArray(content));

//单独的签名容器创建步骤
List< Certificate> certList = Arrays.asList(chain);
JcaCertStore certs = new JcaCertStore(certList);

CMSSignedDataGenerator gen = new CMSSignedDataGenerator();

属性attr = new属性(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);
返回s.getEncoded();

(方法 signWithSeparatedHashing



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

  void sign(PDDocument文档,OutputStream输出,SignatureInterface signatureInterface)抛出IOException 
{
PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signature.setName(示例用户);
signature.setLocation(洛杉矶,加州);
signature.setReason(Testing);
signature.setSignDate(Calendar.getInstance());
document.addSignature(签名);
ExternalSigningSupport externalSigning =
document.saveIncrementalForExternalSigning(output);
byte [] cmsSignature = signatureInterface.sign(externalSigning.getContent());
externalSigning.setSignature(cmsSignature);
}

(方法签署



喜欢这个

  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));
}

(测试方法 testSignWithSeparatedHashing



产生正确签名的PDF,至少与所涉及的证书和私钥相同对于手头的任务。






一句话:



OP使用 IOUtils.toByteArray(内容))(我也在上面的代码中使用)。但考虑到OP的开始评论

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


Currently i have a client-server application that, given a PDF file, signs it (with the server certificate), attachs the signature with the original file and returns the output back to the client (all of this is achieved with PDFBox).
I have a Signature handler, which is my External Signing Support (where content is the PDF file)

    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);
    }
}

It works fine, but i was thinking - what if the PDF file is too big to be uploaded? ex: 100mb... it would take forever!Given that, i am trying to figure out, if instead of signing the PDF file, is it possible to just sign the Hash (ex SHA1) of that file and than the client puts it all together in the end?

Update:

I have been trying to figure this out, and now my signing method is:

    @Override
public byte[] sign(InputStream content) throws IOException {
    // testSHA1WithRSAAndAttributeTable
    try {
        MessageDigest md = MessageDigest.getInstance("SHA1", "BC");
        List<Certificate> certList = new ArrayList<Certificate>();
        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);
    }

}

And i am merging the signature with the PDF with pdfbox

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

The problem is that Adobe says the signature is invalid because the "document has been altered or corrupted since it was signed".Can anyone help?

解决方案

In his update the OP nearly has it right, there merely are two errors:

  • He tries to read the InputStream parameter content twice:

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

    Thus, all data had already been read from the stream before the second attempt which consequently returned an empty byte[]. So the message digest attribute contained a wrong hash value.

  • He creates the final CMS container in a convoluted way:

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

Reducing the latter to what is actually needed, it turns out that there is no need for the CMSTypedData msg anymore. Thus, the former is implicitly resolved.

After re-arranging the digest calculation to the top of the method and additionally switching to SHA256 (as SHA1 is deprecated in many contexts, I prefer to use a different hash algorithm) and allowing for a certificate chain instead of a single certificate, the method looks like this:

// Digest generation step
MessageDigest md = MessageDigest.getInstance("SHA256", "BC");
byte[] digest = md.digest(IOUtils.toByteArray(content));

// Separate signature container creation step
List<Certificate> 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 method signWithSeparatedHashing)

Used in a fairly minimal signing code frame

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 method sign)

like this

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 test method testSignWithSeparatedHashing)

results in properly signed PDFs, as proper at least as the certificates and private key in question are for the task at hand.


One remark:

The OP used IOUtils.toByteArray(content)) (and so do I in the code above). But considering the OP's starting remark

doing so is not such a great idea as it loads a big file into memory at once only for hashing. If one really wants to consider the resource footprint of one's application, one should read the stream a few KB at a time and consecutively digest the data using MessageDigest.update and only use MessageDigest.digest at the end to get the result hash value.

这篇关于从文件摘要创建pkcs7签名的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-21 00:03