文档自签名以来已被更改或损坏 使用 PDFBox 时出现问题

问题描述 投票:0回答:1

我正在尝试使用 Hsm 服务对 pdf 进行签名,该服务接收文档哈希并使用签名的哈希进行响应。我遵循了一些示例并检查了 Stack OverFlow 中涉及一些类似问题的帖子,但我没有找到解决方案。这里保留了数据的快速流动:

  • 接收 byte64 格式的 pdf - 创建 PDDocumemt 并添加视觉签名
  • 使用externalSigning对象获取数据哈希并将其发送以进行签名。
  • 使用签名方法添加证书和签名哈希

当在我的消费应用程序上收到 pdf 时,我得到一个签名的 pdf,但签名无效...它显示“文档自签名以来已被更改或损坏”。我已经尝试找到问题的根源,但没有成功,我在签名方法之后没有更改文档,只进行必要的操作以从文件中获取字节以在响应中发送它。

我知道有类似的讨论,但没有一个能解决我的问题。 这是我处理签名过程的大部分代码:

PDDocument doc = PDDocument.load(request.getRequest().getFile());
            
float page_width = doc.getPage(page).getMediaBox().getWidth();
float page_height = doc.getPage(page).getMediaBox().getHeight();

// Calculate position for signature
float sqr_width = page_width / num_cols;
float sqr_height = page_height / num_rows;
float pos_X = page_width - sqr_width - (sqr_width * (num_cols - column));
float pos_Y = page_height - sqr_height - (sqr_height * (num_rows - row));

GetCertificatebyTotpIDOutput certificateBody= getCertificate();     


PDSignature signature = new PDSignature();
signature.setFilter( PDSignature.FILTER_ADOBE_PPKLITE );
signature.setSubFilter( PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signature.setReason( "Test" );
signature.setSignDate(Calendar.getInstance());

Rectangle2D humanRect = new Rectangle2D.Float(); 
humanRect.setFrame(new Point2D.Float(pos_X, pos_Y), new Dimension((int) sqr_width, (int) sqr_height));

PDRectangle rect = createSignatureRectangle(doc, humanRect);

InputStream template = createVisualSignatureTemplate( doc, page, rect, signature, certificateBody ); // Implementation defined below.
SignatureOptions options = new SignatureOptions();
options.setVisualSignature( template );
options.setPage(0);
options.setPreferredSignatureSize(9000);

doc.addSignature( signature, options );


FileOutputStream outputStream = new FileOutputStream(filePath);

ExternalSigningSupport externalSigning = doc.saveIncrementalForExternalSigning(outputStream);

byte[] input = IOUtils.toByteArray(externalSigning.getContent());
byte[] hash = MessageDigest.getInstance("SHA-256").digest(input);
String digest = new String(Base64.getEncoder().encode(hash));

System.out.println("Before signing: " + new String(Base64.getEncoder().encode(hash)));

byte[] signedContent3 = Files.readAllBytes(Paths.get(filePath));
byte[] hash3 = MessageDigest.getInstance("SHA-256").digest(signedContent3);
System.out.println("File OS hash: " + new String(Base64.getEncoder().encode(hash3)));


SigFinalizeOutput signedDoc = signDocument(certificateBody.getCertAlias(), "test_pdf", digest, "test", totpID, totp);
String base64HashSig = signedDoc.getSignedDocsInfo().get(0).getHashSig();

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

externalSigning.setSignature(cmsSignature);

及签署方法:

public byte[] sign(InputStream content, String signedHash) throws IOException {

    // cannot be done private (interface)
    try {
        GetCertificatebyTotpIDOutput certificateBody= getCertificate(); 
        String base64CertificateString = certificateBody.getCert_64();

//        byte[] certificateBytes = Base64.getDecoder().decode(base64CertificateString);
//        CertificateFactory cf = CertificateFactory.getInstance("X.509");
//        X509Certificate certificate = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(certificateBytes));

        byte[] decoded = Base64.getDecoder().decode(base64CertificateString);
        Collection collection = null;
        try {
            collection = CertificateFactory.getInstance("X.509").generateCertificates(new ByteArrayInputStream(decoded));
        } catch (CertificateException e) {

        }
          
        Certificate[] certChain = new Certificate[collection.size()];
        Iterator iterator = collection.iterator();
        int i = 0;
        while (iterator.hasNext()) {
            certChain[i++] = (Certificate) iterator.next();
        }
        
        // Certificate chain is acquired at initialization
        List<Certificate> certList = new ArrayList<>();
        for (Certificate cert : certChain) {
            certList.add(cert);
        }
        Store certStore= new JcaCertStore(certList);
        org.bouncycastle.asn1.x509.Certificate cert = org.bouncycastle.asn1.x509.Certificate.getInstance(certChain[0].getEncoded());
        
        X509Certificate signerCert = (X509Certificate) certChain[0];
        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
        gen.addCertificates(certStore);
      
        byte[] input = IOUtils.toByteArray(content);


        MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
        messageDigest.update(input);
        System.out.println("During signing: " + new String(Base64.getEncoder().encode(messageDigest.digest())));
        Attribute attr = new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(messageDigest.digest())));
        ASN1EncodableVector v = new ASN1EncodableVector();
        v.add(attr);

        ContentSigner contentSigner = new ContentSigner() {
            
            @Override
            public byte[] getSignature() {
                 System.out.println(String.valueOf(Base64.getDecoder().decode(signedHash)));
                return Base64.getDecoder().decode(signedHash);
            }

            @Override
            public OutputStream getOutputStream() {
                return new ByteArrayOutputStream() ;
            }

            @Override
            public AlgorithmIdentifier getAlgorithmIdentifier() {
                return new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256WITHDSA");
            }
        };

        SignerInfoGeneratorBuilder builder = new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider())
                .setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v)));
        gen.addSignerInfoGenerator(builder.build(contentSigner, new X509CertificateHolder(cert)));
        //gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(contentSigner, signerCert));
        
        
        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);
    }
}

我尝试计算字节范围,但后来我看到

externalsigning.getcontent()
已经只从pdf中检索了值,而没有签名对象。 希望有人可以帮助我至少下一步要检查什么。

java pdfbox digital-signature
1个回答
0
投票

在要求提供由您的代码签名的示例 PDF 后,我查看了您的代码并发现了一些问题。但很可能不是全部。

不正确使用
MessageDigest

你愿意

        MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
        messageDigest.update(input);
        System.out.println("During signing: " + new String(Base64.getEncoder().encode(messageDigest.digest())));
        Attribute attr = new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(messageDigest.digest())));

但是如果你阅读

MessageDigest.digest()
方法的 JavaDocs,你会发现:

    /**
     * Completes the hash computation by performing final operations
     * such as padding. The digest is reset after this call is made.
     *
     * @return the array of bytes for the resulting hash value.
     */
    public byte[] digest() {

因此在

System.out.println("During signing: " + new String(Base64.getEncoder().encode(messageDigest.digest())))
中检索哈希值并重置
messageDigest
。因此,在下一行
Attribute attr = new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(messageDigest.digest())))
中,您将检索空输入的摘要!

签署错误的哈希值

您明确设置了签名属性生成器:

        SignerInfoGeneratorBuilder builder = new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider())
                .setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v)));

因此,您知道您使用了带有签名属性的 CMS 签名容器结构。但您不签署这些属性,而是签署文档数据!

因此,您必须更改代码才能签署正确的数据。

© www.soinside.com 2019 - 2024. All rights reserved.