【问题标题】:Document has been altered or corrupted since it was signed文件自签署以来已被更改或损坏
【发布时间】:2021-02-22 12:17:18
【问题描述】:

我已经部分阅读了itextpdf's paper 关于数字签名的内容,并从中开始开发我的代码:

public class ServerSignature implements ExternalSignature {
    public String getHashAlgorithm() {
      return DigestAlgorithms.SHA256;
    }
    public String getEncryptionAlgorithm() {
      return "RSA";
    }
    public byte[] sign(byte[] message) throws GeneralSecurityException {
      byte[] s = Base64.getDecoder().decode(SPMService.this.signature);
      return s;
    }
  }

public void sign(Certificate[] chain, CryptoStandard subfilter, String reason,
      String location) throws GeneralSecurityException, DocumentException, IOException {
    // Creating the reader and the stamper
    System.out.println("SRC: " + this.SRC);
    PdfReader reader = new PdfReader(this.SRC);
    FileOutputStream os = new FileOutputStream(this.DEST);
    PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0', null, true);
    // Creating the appearance
    PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
    appearance.setReason(reason);
    appearance.setLocation(location);

    appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "sig");

    // Creating the signature
    ExternalDigest digest = new BouncyCastleDigest();
    ExternalSignature signature = new ServerSignature();
    MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, subfilter);
    this.verifySignatures(this.DEST);
  } 

    private void verifySignature(AcroFields fields, String name) throws GeneralSecurityException, IOException {
    // Returns true
    System.out.println("Signature covers whole document: " + fields.signatureCoversWholeDocument(name));
    // Returns the right revisions, e.g. 1 of 1
    System.out.println("Document revision: " + fields.getRevision(name) + " of " + fields.getTotalRevisions());
    PdfPKCS7 pkcs7 = fields.verifySignature(name);
    // Returns SHA256
    System.out.println("Digest algorithm: " + pkcs7.getHashAlgorithm());
    //Returns RSA
    System.out.println("Encryption algorithm: " + pkcs7.getEncryptionAlgorithm());
    // Return /adbe.pkcs7.detached
    System.out.println("Filter subtype: " + pkcs7.getFilterSubtype());
    // Get the signing certificate to find out the name of the signer.
    X509Certificate cert = (X509Certificate) pkcs7.getSigningCertificate();
    System.out.println("Name of the signer: " + CertificateInfo.getSubjectFields(cert).getField("CN"));
    if (pkcs7.getSignName() != null) {
      System.out.println("Alternative name of the signer: " + pkcs7.getSignName());
    }
    // The following log is returning false always. Why?
    System.out.println("Integrity check OK? " + pkcs7.verify());
  }
  private void verifySignatures(String src) throws IOException, GeneralSecurityException {
    Security.addProvider(new BouncyCastleProvider());
    PdfReader reader = new PdfReader(src);
    AcroFields fields = reader.getAcroFields();
    ArrayList<String> names = fields.getSignatureNames();
    for (String name : names) {
      System.out.println("===== " + name + " =====");
      verifySignature(fields, name);
    }
    System.out.println();
  }
  public void start() throws GeneralSecurityException, DocumentException, IOException {
    CertificateFactory factory = CertificateFactory.getInstance("X.509");
    // certificate I received in the Controller
    InputStream targetStream = new ByteArrayInputStream(this.certificate.getBytes());
    Certificate[] chain = new Certificate[1];
    chain[0] = factory.generateCertificate(targetStream);
    this.sign(chain, CryptoStandard.CMS, "Reason", "Location");
  }

使用此代码,当我使用 Adob​​e Acrobat Reader 打开已签名的文档时,签名可见并且嵌入在文档中。问题是它带有一个红色标记,表明“文档自签署以来已被更改或损坏”。

有人可以向我解释缺少什么或者我的逻辑是否错误?

【问题讨论】:

  • 请分享一个由您的代码签名的示例 PDF 以供分析。
  • ServerSignature 中似乎有一个错误:在嵌入式签名容器中,签名字节签署了错误的哈希值(不是签名属性的哈希值,而是其他一些值)。
  • 你能给我举个例子吗?
  • “你能给我举个例子吗” - 一个什么例子?我刚刚写了一个答案,其中包含有关您的示例 PDF 分析的更多详细信息。

标签: java itext digital-signature


【解决方案1】:

问题不在于您显示的代码。因此,这要么是您使用的ServerSignature 的问题,要么是它处理的服务器端代码的问题。

详细介绍

分析你分享的example PDF可以看到:

  • PDF 中带符号字节范围的 SHA256 摘要为

    37B415ACC3E5A146073E8C95D5DEE7E93190CF082210A9CE53AD78F7C5D58002
    

    事实上,嵌入式签名容器SignerInfo 包含签名属性

    . . . . . . SEQUENCE {
    . . . . . . . OBJECT IDENTIFIER messageDigest (1 2 840 113549 1 9 4)
    . . . . . . . . (PKCS #9)
    . . . . . . . SET {
    . . . . . . . . OCTET STRING    
    . . . . . . . . . 37 B4 15 AC C3 E5 A1 46    7......F
    . . . . . . . . . 07 3E 8C 95 D5 DE E7 E9    .>......
    . . . . . . . . . 31 90 CF 08 22 10 A9 CE    1..."...
    . . . . . . . . . 53 AD 78 F7 C5 D5 80 02                            
    . . . . . . . . }
    . . . . . . . }
    

    因此,签名字节范围的摘要计算、其在签名容器创建中的使用以及签名容器的嵌入工作正常。

  • 签名字节可以成功解密为带有 PKCS#1 1.5 填充的DigestInfo 对象。因此,用于加密的私钥与所谓的签名者证书中的公钥相匹配。

  • 签名容器中签名属性的 SHA256 摘要是

    0083D5FA72C3D81EA038BF62D4AFFC949F23E120DA59E59C5CB8EFA5214BA975
    

    但解密后的签名字节(PKCS#1 1.5 填充被移除)是

    3031300D0609608648016503040201050004208DECC8571946D4CD70A024949E033A2A2A54377FE9F1C1B944C20F9EE11A9E51
    

    所以实际签名的 SHA256 摘要是

    8DECC8571946D4CD70A024949E033A2A2A54377FE9F1C1B944C20F9EE11A9E51
    

    显然存在不匹配。因此,显然在您使用的ExternalSignature 实现ServerSignature 或在它地址的后端中没有正确处理签名的属性字节。

不幸的是,您既没有显示您使用的ServerSignature 实现代码,也没有显示相关服务器上的任何代码。因此,无法进一步分析该问题;很明显,您显示的 iText 签名代码工作正常。

您的代码

同时您分享了您使用的ServerSignature 实现代码:

public class ServerSignature implements ExternalSignature {
    public String getHashAlgorithm() {
        return DigestAlgorithms.SHA256;
    }
    public String getEncryptionAlgorithm() {
        return "RSA";
    }
    public byte[] sign(byte[] message) throws GeneralSecurityException {
        byte[] s = Base64.getDecoder().decode(SPMService.this.signature);
        return s;
    }
}

这里可以看到您完全忽略了byte[] message,但返回了一些SPMService.this.signature。当您忽略 message 进行签名时,生成的 PDF 签名在统计上一定是无效的。

因此,请在此处更改您的代码,以使您的 ServerSignature.sign 方法返回签名字节,这些字节对给定的 byte[] message 进行签名。

【讨论】:

  • 我已经用 ServerSignature 更新了代码示例
  • “我已经使用 ServerSignature 更新了代码示例” - 您的 ServerSignature 代码完成忽略了它被调用签名的 byte[] message。所以难怪返回的签名值签署了错误的哈希。 SPMService.this.signature 是什么符号?
  • SPMService.this.signature 表示我们要添加到 PDF 中的签名
  • 但这没有任何意义,您必须添加签名byte[] message,而不是其他签名。
  • 作为来自消息的签名,我是否必须在某处添加我的外部签名,或者签名是否来自证书?我找不到适用于符号的代码(字节 [] 消息),我使用此示例:github.com/itext/i7js-signatures/blob/develop/src/test/java/com/…
【解决方案2】:

脑海中浮现出三种可能性:

  1. 您使用了错误的文档区域进行验证。这是我多次犯过的一个非常容易的错误,尽管我从未尝试过使用 PDF,所以我无能为力。显然,如果你偏离了一个字节,你就会得到一个不匹配的结果。而且,很可能很明显,您不会针对整个文件进行计算;至少您必须排除签名值,因为在计算原始签名时它不存在。很可能其他一些字段也被省略了。

  2. 您使用了错误的算法。我不知道 PDF 的东西,所以我不能发表评论,但你是如何选择你使用的算法的?你确定它是正确的吗?

  3. 文件确实被篡改了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-06-20
    • 1970-01-01
    • 2016-09-16
    • 2018-12-04
    • 2023-04-03
    • 2021-05-11
    • 2021-11-06
    • 1970-01-01
    相关资源
    最近更新 更多