【问题标题】:External Signature with PDFBox 2.0.12PDFBox 2.0.12 的外部签名
【发布时间】:2020-07-21 15:05:06
【问题描述】:

签名修改问题。

我正在使用 DSC 令牌(外部签名)传递文档哈希和签名哈希。

我收到如下错误:“文档自签署以来已被更改或损坏” 获取文档的哈希:-

public String genrateDigitalCertificateSign() {
        try {
            src = new FileInputStream(inputFilePath);
            OutputStream dest = new FileOutputStream(new File(RESULT_FOLDER, "Test.pdf"));
            pdDocument = PDDocument.load(src);

            PDSignature pds = null;
            String hashdocument = null;
            File imgFile = new File(inputImgPath);
            PDAcroForm acroForm = pdDocument.getDocumentCatalog().getAcroForm();
            if (acroForm == null) {
                pdDocument.getDocumentCatalog().setAcroForm(acroForm = new PDAcroForm(pdDocument));
            }
            acroForm.setSignaturesExist(true);
            acroForm.setAppendOnly(true);
            acroForm.getCOSObject().setDirect(true);

            pds = new PDSignature();
            pds.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
            pds.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
            pds.setSignDate(Calendar.getInstance());

            PDPage pdPage = pdDocument.getPage(0);
            PDImageXObject pdImage = PDImageXObject.createFromFileByContent(imgFile, pdDocument);

            //visible signature rectangle
            rectangle = new PDRectangle(200.00, 200.00,150.00,50.00);
            
            List<PDField> acroFormFields = acroForm.getFields();
            PDSignatureField signatureField = new PDSignatureField(acroForm);
            acroForm.setSignaturesExist(true);
            acroForm.setAppendOnly(true);
            acroForm.getCOSObject().setDirect(true);
            signatureField.setValue(pds);
            acroFormFields.add(signatureField);
            pdDocument.addSignature(pds);
            
            //creating visible stamp
            createVisualSignatureTemplate(pdDocument, signatureField, pdPage, rectangle, pdImage, signDisplayInfo);

            externalSigning = pdDocument.saveIncrementalForExternalSigning(dest);
            InputStream dataToSign = externalSigning.getContent();
            hashdocument = DigestUtils.sha256Hex(dataToSign); // hash is generated
            return hashdocument;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

外部签名代码:-

public byte[] sign(byte[] hash)
            throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        PrivateKey privKey = pk;
        try {
            List<Certificate> certList = new ArrayList<>();
            certList.addAll(Arrays.asList(chain));
            Store certs = new JcaCertStore(certList);
            CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
            org.bouncycastle.asn1.x509.Certificate cert = org.bouncycastle.asn1.x509.Certificate
                    .getInstance(chain[0].getEncoded());
            ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA256WithRSA").build(privKey);
            gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build())
                            .build(sha1Signer, new X509CertificateHolder(cert)));
            gen.addCertificates(certs);
            CMSProcessableInputStream msg = new CMSProcessableInputStream(new ByteArrayInputStream(hash));
            CMSSignedData signedData = gen.generate(msg, 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);
        }
    }

签名附加码:-

public void signedPDF(byte[] hash)
            throws InvalidKeyException, NoSuchAlgorithmException, SignatureException, IOException {
        byte[] signedHash = sign(hash);
        externalSigning.setSignature(signedHash);
        IOUtils.closeQuietly(src);
        pdDocument.close();
    } 

附加此签名后,但在打开已签名的 PDF 时出现如下图所示的错误。

PDF文件链接: https://drive.google.com/file/d/1qRT2CVgET8Ds1fu0b5psii3j8ytPKaLH/view?usp=sharing

[编辑]

public byte[] genrateDigitalCertificateSign() {
        try {
            src = new FileInputStream(inputFilePath);
            OutputStream dest = new FileOutputStream(new File(RESULT_FOLDER, "Test.pdf"));
            pdDocument = PDDocument.load(src);

            PDSignature pds = null;
            String hashdocument = null;
            File imgFile = new File(inputImgPath);
            PDAcroForm acroForm = pdDocument.getDocumentCatalog().getAcroForm();
            if (acroForm == null) {
                pdDocument.getDocumentCatalog().setAcroForm(acroForm = new PDAcroForm(pdDocument));
            }
            acroForm.setSignaturesExist(true);
            acroForm.setAppendOnly(true);
            acroForm.getCOSObject().setDirect(true);

            pds = new PDSignature();
            pds.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
            pds.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
            pds.setSignDate(Calendar.getInstance());

            PDPage pdPage = pdDocument.getPage(0);
            PDImageXObject pdImage = PDImageXObject.createFromFileByContent(imgFile, pdDocument);

            //visible signature rectangle
            rectangle = new PDRectangle(200.00, 200.00,150.00,50.00);
            
            List<PDField> acroFormFields = acroForm.getFields();
            PDSignatureField signatureField = new PDSignatureField(acroForm);
            acroForm.setSignaturesExist(true);
            acroForm.setAppendOnly(true);
            acroForm.getCOSObject().setDirect(true);
            signatureField.setValue(pds);
            acroFormFields.add(signatureField);
            pdDocument.addSignature(pds);
            
            //creating visible stamp
            createVisualSignatureTemplate(pdDocument, signatureField, pdPage, rectangle, pdImage, signDisplayInfo);

            externalSigning = pdDocument.saveIncrementalForExternalSigning(dest);
            InputStream dataToSign = externalSigning.getContent();
            hashdocument = DigestUtils.sha256(dataToSign); // hash is generated
            return hashdocument;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

最近签署的PDF文件链接:- https://drive.google.com/file/d/1mZ8Wqppx3EylI1aLYB9Fl8NzibBXZxzR/view?usp=sharing

[编辑 2] 这是我根据您的建议尝试的流程。
在此您指出的第一个问题是什么?我无法弄清楚,请帮助。

externalSigning = pdDocument.saveIncrementalForExternalSigning(dest);
            InputStream dataToSign = externalSigning.getContent();
            hashdocument = DigestUtils.sha256(dataToSign); // hash is generated 
            byte[] signedhash = sign(hashdocument);
            externalSigning.setSignature(signedhash);
            pdDocument.close();

【问题讨论】:

  • 请分享一个示例 PDF 以供分析。此外,您可能必须提供更多代码,您提供的代码看起来非常不完整。
  • @mkl 文件和初始哈希生成代码也上传了,请检查。

标签: java pdfbox


【解决方案1】:

好的,你的代码有两个问题:

双重哈希

您的代码会创建一个包含散列的散列文档数据的签名,其中只需要文档数据的散列。

您的代码主要是从 PDFBox 示例中借用的。在外部签名的情况下,要签名的内容被检索并转发到sign方法:

byte[] cmsSignature = sign(externalSigning.getContent());

(来自CreateSignature,扩展了CreateSignatureBase

不过,在您的代码中,您首先要对要签名的内容进行哈希处理,然后将该 哈希处理 转发给 sign 方法:

public String genrateDigitalCertificateSign() {
    ...
    InputStream dataToSign = externalSigning.getContent();
    hashdocument = DigestUtils.sha256Hex(dataToSign); // hash is generated
    return hashdocument;
    ...
}

byte[] signedHash = sign(hash);

就创建 CMS 签名容器而言,您的 sign 方法使用与 PDFBox 示例相同的代码。

因此,您的代码过于频繁地对文档数据进行哈希处理。要解决此问题,您必须

  • 要么放弃散列步骤并将文档数据转发给签名
  • 或更改sign 方法以不再对其输入进行哈希处理。

我假设您的意图是计算哈希并将其转发给单独的签名服务,因此您的选择是后者。

对哈希进行十六进制编码

您的哈希生成方法genrateDigitalCertificateSign 返回的哈希值不是实际的byte[],而是对其进行十六进制编码并返回该十六进制字符串:

hashdocument = DigestUtils.sha256Hex(dataToSign); // hash is generated
return hashdocument;

不过,您的其他方法希望获取并在实际的 byte[] 上运行。

要解决这个问题,你必须

  • 不是以十六进制编码开头,即使用DigestUtils.sha256 代替DigestUtils.sha256Hex 并从genrateDigitalCertificateSign 返回byte[] 而不是String
  • 或对哈希值进行十六进制解码,然后将其提供给signedPDF 方法中的sign 方法。

我假设您的意图是以字符串形式传输哈希,所以您的选择是后者。

【讨论】:

  • 感谢您的建议,我尝试了两种方法仍然面临同样的问题。
  • 在您的示例 PDF 中,这两个错误是问题所在。请分享一个新的 PDF 签名,该 PDF 使用您已更正这两个错误的代码。并考虑在对问题文本的编辑中分享您的代码更改。
  • 根据您的第二个附件和您添加的代码,您修复了第二个问题,但还没有解决第一个问题:在签名中没有文档哈希的十六进制编码的哈希,而是哈希的文档哈希。不过,它必须是文档哈希本身。
  • 我没有得到你提到的第一个问题,请简要介绍一下。
  • 根据您的建议,我修改了代码并上传了相同的内容。
猜你喜欢
  • 2019-11-13
  • 1970-01-01
  • 2021-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-15
相关资源
最近更新 更多