【问题标题】:How to generate PKCS#7 signature from digest?如何从摘要生成 PKCS#7 签名?
【发布时间】:2018-10-11 09:46:14
【问题描述】:

我想使用 pdf 摘要签署 pdf。我使用下面的代码创建了哈希,

byte[] buffer = new byte[1024];
int numOfBytesRead =0;
MessageDigest md = null;
md = MessageDigest.getInstance("SHA256","BC");
while((numOfBytesRead = content.read(buffer)) != -1 ){
     md.update(buffer, 0, numOfBytesRead);
}
byte[] digest = md.digest();

最后,我需要将此签名附加到我的 pdf 文件中。我找到了一种解决方案Create pkcs7 signature from file digest,但链接中使用的算法是 SHA256withRSA。我的私钥是使用 EC 算法生成的,我需要使用 SHA256withECDSA。是否可以使用 SHA256withECDSA 对哈希进行签名并将签名附加到pdf 使用 PDFBox ExternalSigning 接口。

【问题讨论】:

  • 您是否尝试过在您引用的问题的答案中使用代码,只是更改签名算法?结果如何?
  • @Mkl-我没试过。由于我是密码学新手,我不明白该代码 sn-p 中发生了什么。你有密码学的参考链接吗?你能告诉我我应该改变什么来使它成为 SHA256withECDSA.?

标签: android digital-signature bouncycastle pdfbox pkcs#7


【解决方案1】:

在几种情况下,即使签名者的证书显然是有效的,Adobe 也会将其称为无效;特别是在手头的情况下:

  • 密钥用法或扩展密钥用法值不合适
  • PAdES 签名缺少 ESS 签名证书 v2 属性

密钥使用或扩展密钥使用值不合适

这是基于 OP 首次发布为 an answer

的信息

我尝试了下面的代码,但 pdf 仍然显示签名无效。你能检查一下代码吗,

[...]

我已附上 pdf 文件。 Pdf file created

确实,Adobe Reader 说签名无效,但仔细看:

它说“自从应用了这个签名后,文档没有被修改过”——这意味着签名在数学上是正确的!

问题是“签名者的证书无效”,在挖掘签名属性对话框时可以看到原因:

因此,问题在于您的签名者证书无法使用

这是由于突出显示的属性,而密钥用法数字签名没问题,“扩展密钥用法”1.3.6.1.5.5.8.2.2 ( IPSEC 保护的 OID)不是!

根据 Adob​​e Digital Signatures Guide for IT,Adobe Acrobat 仅接受

  • 以下一个或多个密钥使用值(如果有)

    • 不可否认
    • signTransaction(仅限 11.0.09)
    • digitalSignature(11.0.10 及更高版本)
  • 以及以下一项或多项扩展密钥使用值(如果有)

    • 电子邮件保护
    • 代码签名
    • 任何ExtendedKeyUsage
    • 1.2.840.113583.1.1.5(Adobe Authentic Documents Trust)

由于其IPSEC 保护扩展了密钥使用价值,因此,您的证书对于签署 PDF 文档无效。

这可能只是传统 ISO 32000-1 签名中的问题,可能不在 PAdES 签名中。

PAdES 签名缺少 ESS 签名证书 v2 属性

这是基于 OP 首次发布为 an answer

的信息

我创建了 2 个 pdf 文件,使用 pdf 内容的摘要和以下代码对 PDFA 进行签名,

[...]

PDFA

PDFB 是使用相同的私钥和证书创建的,但我没有使用摘要,而是直接使用 pdf 文档内容,这给了我有效的签名 pdf,PDFB 代码如下,

[...]

PDFB

我认为 PDFA 的签名部分缺少一些我无法弄清楚的东西。

这里的主要区别不是您是自己显式计算哈希还是允许隐式计算,主要区别在于 PDFB 中的签名包含 ESS 签名证书 v2 属性,而 PDFA 中没有。这个属性是在

之间生成的
//PAdES - PDF Advanced Electronic Signature

//PAdES-end

正如 cmets 已经暗示的那样,这仅对 PAdES 签名是必需的,而不是旧版 ISO 32000-1 签名。 The answer the OP took his original code from 指的是在 OP 创建 PAdES 签名时创建旧版 ISO 32000-1 签名(因此可以正常工作)。

PAdES 规范 ETSI EN 319 142-1 要求存在 ESS 签名证书属性:

e) 生成器应根据 ETSI EN 319 122-1 使用签名证书或签名证书 v2 属性,具体取决于散列函数。

(ETSI EN 319 142-1,第 6.3 节 PAdES 基线签名)

它引用了 CAdES 规范 ETSI EN 319 122-1,而后者又要求

h) SPO 要求:ESS signing-certificate。如果使用 SHA-1 哈希算法,则应使用 ESS signing-certificate 属性。

i) SPO 要求:ESS signing-certificate-v2。当使用除 SHA-1 之外的其他哈希算法时,应使用 ESSsigning-certificate-v2 属性。

(ETSI EN 319 122-1,第 6.3 节对组件和服务的要求)

【讨论】:

  • 什么是“signTransaction”?我找不到它:tools.ietf.org/html/rfc5280#section-4.2.1.3
  • 我得查一下。我只是从 Adob​​e 网站引用了上述内容。 ;)
  • 最新的PDF有“1.3.6.1.4.1.311.10.3.12”,看来Adobe也能容忍。
  • 是的。同时我假设这里的主要问题实际上是在非工作情况下没有ESSCertIDv2。示例毕竟是 PAdES 签名的。
  • 是的,问题是缺少 ESSCertIDv2。这也将回答stackoverflow.com/questions/52588527/…。感谢 mkl 和 Tilman 的支持。
【解决方案2】:

我尝试了下面的代码,但 pdf 仍然显示签名无效。你能检查一下代码吗,

System.out.println("Hash Signing started");
    List<Certificate> certList = getFormatCertificate(strCertificate);
    PrivateKey privateKey;
    CMSSignedData s = null;
    Security.addProvider(new BouncyCastleProvider());
    byte[] signature = null;
    try {
        privateKey = loadPrivateKey(strPrivatekey);
        byte[] buffer = new byte[1024];
        int numOfBytesRead =0;
        MessageDigest md = null;
        md = MessageDigest.getInstance("SHA256","BC");
        while((numOfBytesRead = content.read(buffer)) != -1 ){
            md.update(buffer, 0, numOfBytesRead);
        }
        byte[] digest = md.digest();

        // Separate signature container creation step
        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 sha256withECDSA = new DefaultSignatureAlgorithmIdentifierFinder().find(signerAlgorithm);

        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
        InputStream in = new ByteArrayInputStream(certList.get(certList.size()-1).getEncoded());
        X509Certificate cert = (X509Certificate) certFactory.generateCertificate(in);

        gen.addSignerInfoGenerator(builder.build(
                new JcaContentSignerBuilder(signerAlgorithm).build(privateKey),
                new JcaX509CertificateHolder(cert)));
        //DErse
//      gen.addSignerInfoGenerator(builder.build(
//              new JcaContentSignerBuilder(sha256withRSA,
//                      new DefaultDigestAlgorithmIdentifierFinder().find(sha256withRSA))
//                              .build(PrivateKeyFactory.createKey(privateKey.getEncoded())),
//              new JcaX509CertificateHolder(cert)));

        gen.addCertificates(certs);

        s = gen.generate(new CMSAbsentContent(), false);
        System.out.println("Hash sign completed");
        signature = s.getEncoded();// ConstructEcdsaSigValue(s.getEncoded());
    } catch (GeneralSecurityException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        System.out.println("GeneralSecurityException ::"+e.toString());
    } catch (OperatorCreationException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        System.out.println("OperatorCreationException ::"+e.toString());
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        System.out.println("IOException ::"+e.toString());
    } catch (CMSException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        System.out.println("CMSException ::"+e.toString());
    }finally{
        return signature;
    }

我已附上 pdf 文件。 Pdf file created

@Mkl/Tilman : 我创建了 2 个 pdf 文件,PDFA 使用 pdf 内容的摘要和以下代码进行签名,

System.out.println("Hash Signing started");
    List<Certificate> certList = getFormatCertificate(strCertificate);
    PrivateKey privateKey;
    CMSSignedData s = null;
    Security.addProvider(new BouncyCastleProvider());
    byte[] signature = null;
    try {
        privateKey = loadPrivateKey(strPrivatekey);

        /*byte[] buffer = new byte[1024];
        int numOfBytesRead =0;
        MessageDigest md = null;
        //md = MessageDigest.getInstance("SHA256","BC");
        md = MessageDigest.getInstance("SHA-256");
        while((numOfBytesRead = content.read(buffer)) != -1 ){
            md.update(buffer, 0, numOfBytesRead);
        }
        byte[] digest = md.digest();*/
        MessageDigest md = MessageDigest.getInstance("SHA256", "BC");
        byte[] digest = md.digest(IOUtils.toByteArray(content));

        // Separate signature container creation step
        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 sha256withECDSA = new DefaultSignatureAlgorithmIdentifierFinder().find(signerAlgorithm);

        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
        InputStream in = new ByteArrayInputStream(certList.get(certList.size()-1).getEncoded());
        X509Certificate cert = (X509Certificate) certFactory.generateCertificate(in);

        gen.addSignerInfoGenerator(builder.build(
                new JcaContentSignerBuilder(signerAlgorithm).build(privateKey),
                new JcaX509CertificateHolder(cert)));
        //DErse
//      gen.addSignerInfoGenerator(builder.build(
//              new JcaContentSignerBuilder(sha256withRSA,
//                      new DefaultDigestAlgorithmIdentifierFinder().find(sha256withRSA))
//                              .build(PrivateKeyFactory.createKey(privateKey.getEncoded())),
//              new JcaX509CertificateHolder(cert)));

        gen.addCertificates(certs);

        s = gen.generate(new CMSAbsentContent(), false);
        System.out.println("Hash sign completed");
        signature = s.getEncoded();// ConstructEcdsaSigValue(s.getEncoded());
    } catch (GeneralSecurityException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        System.out.println("GeneralSecurityException ::"+e.toString());
    } catch (OperatorCreationException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        System.out.println("OperatorCreationException ::"+e.toString());
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        System.out.println("IOException ::"+e.toString());
    } catch (CMSException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        System.out.println("CMSException ::"+e.toString());
    }finally{
        return signature;
    }

PDFA

PDFB 是使用相同的私钥和证书创建的,但我没有使用摘要,而是直接使用 pdf 文档内容,这给了我有效的签名 pdf,PDFB 代码如下,

SignatureInterface signatureInterface = new SignatureInterface() {
                    @SuppressWarnings("rawtypes")
                    @Override
                    public byte[] sign(InputStream content) throws IOException {
                        try {
                            byte[] certificateByte = null;

                            Store certs = new JcaCertStore(certificates);

                            //PAdES - PDF Advanced Electronic Signature
                            //ESS - Enhanced Security Services
                            //ASN1 - Abstract Syntax Notation One-standard interface description language for defining data structures that can be serialized and deserialized in a cross-platform way
                            // Generating certificate hash
                            MessageDigest md = MessageDigest.getInstance("SHA-256");
                            md.update(certificates.get(certificates.size()-1).getEncoded());
                            byte[] certHash = md.digest();
                            // Generating certificate hash ends
                            System.out.println("Cert hash generated");
                            //ESSCertIDv2 identifies the certificate from the hash
                            ESSCertIDv2 essCert1 =
                                    new ESSCertIDv2(new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256), certHash);
                            ESSCertIDv2[] essCert1Arr =
                                    {
                                            essCert1
                                    };
                            SigningCertificateV2 scv2 = new SigningCertificateV2(essCert1Arr);
                            Attribute certHAttribute =
                                    new Attribute(PKCSObjectIdentifiers.id_aa_signingCertificateV2, new DERSet(scv2));
                            ASN1EncodableVector v = new ASN1EncodableVector();
                            v.add(certHAttribute);

                            AttributeTable at = new AttributeTable(v);

                            //Create a standard attribute table from the passed in parameters - certhash
                            CMSAttributeTableGenerator attrGen = new DefaultSignedAttributeTableGenerator(at){
                                protected Hashtable createStandardAttributeTable(Map parameters)
                                {
                                    Hashtable result = super.createStandardAttributeTable(parameters);
                                    result.remove(CMSAttributes.signingTime);
                                    return result;
                                }
                            };
                            //PAdES-end
                            System.out.println("CMSAttributeTableGenerator generated");
                            SignerInfoGeneratorBuilder genBuild =
                                    new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider());
                            genBuild.setSignedAttributeGenerator(attrGen);
                            //Get single certificate
                            org.spongycastle.asn1.x509.Certificate certas1 = org.spongycastle.asn1.x509.Certificate.getInstance(ASN1Primitive.fromByteArray(certificates.get(certificates.size()-1).getEncoded()));
                            // ContentSigner interface creates SHA256withECDSA signer using PvtKey
                            ContentSigner sha1Signer = new JcaContentSignerBuilder(signerAlgorithm).build(privateKey);
                            //Creates SignerInfoGenerator using X.509 cert and ContentSigner
                            SignerInfoGenerator sifGen = genBuild.build(sha1Signer, new X509CertificateHolder(certas1));

                            // CMSSignedDataGenerator generates a pkcs7-signature message 
                            CMSSignedDataGenerator gen = new CMSSignedDataGenerator();

                            gen.addCertificates(certs);
                            gen.addSignerInfoGenerator(sifGen);
                            //Creates CMS message from PDF
                            CMSProcessableInputStream msg = new CMSProcessableInputStream(content);
                            //Generate a CMS Signed Data object which can be carrying a detached CMS signature
                            //msg - content to be signed
                            CMSSignedData signedData = gen.generate(msg, false);
                            System.out.println("CMSSignedData is done");
                            return signedData.getEncoded();
                        } catch (GeneralSecurityException e) {
                            throw new IOException(e);
                        } catch (CMSException e) {
                            throw new IOException(e);
                        } catch (OperatorCreationException e) {
                            throw new IOException(e);
                        }
                }

            };
            System.out.println("CMSSignedData is done2");
            PDDocument pdDocument =  PDDocument.load(inputfile);

            System.out.println("pdDocument loaded");
            pdDocument.addSignature(signature, signatureInterface);

PDFB

我认为 PDFA 的签名部分缺少一些我无法弄清楚的东西。

【讨论】:

  • 感谢您提供更多信息,但请将其添加到您的问题中,而不是放入答案中。在您的问题下方有一个edit 链接。
  • “pdf 仍然显示签名无效” - 是的,但请更仔细地查看输出:它显示“自从应用此签名后,该文档尚未被修改。 "这意味着签名在数学上是正确的!问题是“签名者的身份无效。”
  • 请说明你从哪里得到signerAlgorithm
  • signerAlgorithm = SHA256wihECDSA
  • 关于您的编辑:“但我没有使用摘要,而是直接使用 pdf 文档内容,这给了我有效的签名 pdf” - 还有其他差异,以及您的差异提及与此处无关。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-20
  • 2013-09-22
  • 2011-10-29
  • 2021-03-28
  • 1970-01-01
相关资源
最近更新 更多