首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >PDF政府文件的数字签名验证失败

PDF政府文件的数字签名验证失败
EN

Stack Overflow用户
提问于 2018-09-12 09:29:04
回答 1查看 1.2K关注 0票数 2

我们试图验证一个荷兰政府机构(UWV Verzekeringsbericht)的数字签名,包括文件的真实性。

能够正确验证此文件。

通过一个概念应用的小证明,我们能够验证各种数字签名PDF的真实性:

代码语言:javascript
复制
import com.itextpdf.text.pdf.AcroFields;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.security.PdfPKCS7;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.Security;
import java.util.ArrayList;

public class Verifier {

    public static void main(String[] args) throws IOException, GeneralSecurityException {

        BouncyCastleProvider provider = new BouncyCastleProvider();
        Security.addProvider(provider);

        new Verifier().run(args[0]);
    }

    private void run(String path) throws IOException, GeneralSecurityException {

        final PdfReader reader = new PdfReader(path);

        final AcroFields fields = reader.getAcroFields();

        final ArrayList<String> signatureNames = fields.getSignatureNames();

        for(String signatureName: signatureNames) {
            System.out.println("Verify signature " + signatureName);
            verifySignature(fields, signatureName);
        }
    }

    private PdfPKCS7 verifySignature(final AcroFields fields, final String name) throws GeneralSecurityException {
        System.out.println("Signature covers whole document: " + fields.signatureCoversWholeDocument(name));
        System.out.println("Document revision: " + fields.getRevision(name) + " of " + fields.getTotalRevisions());
        PdfPKCS7 pkcs7 = fields.verifySignature(name);
        System.out.println("Integrity check OK? " + pkcs7.verify());
        return pkcs7;
    }
}

使用以下(Maven)依赖关系:

代码语言:javascript
复制
<dependencies>
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>itextpdf</artifactId>
        <version>5.5.13</version>
    </dependency>
    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcprov-debug-jdk15on</artifactId>
        <version>1.60</version>
    </dependency>
    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcpkix-jdk15on</artifactId>
        <version>1.60</version>
    </dependency>
</dependencies>

您可以猜到,从这个权威机构验证PDF是行不通的。

运行此应用程序的结果是:

代码语言:javascript
复制
Exception in thread "main" java.lang.IllegalArgumentException: can't decode PKCS7SignedData object
    at com.itextpdf.text.pdf.security.PdfPKCS7.<init>(PdfPKCS7.java:214)

这是在PdfPKCS7类中引起的,该类正在从签名的内容实例化ASN1输入流(第203行):

代码语言:javascript
复制
SN1InputStream din = new ASN1InputStream(new ByteArrayInputStream(contentsKey));

这反过来导致IOException: DER长度超过4个字节: 31,因此签名似乎无效。

AcroFields的verifySignature方法调用尝试创建一个PdfPKCS7实例。此方法的片段:

代码语言:javascript
复制
if(!reader.isEncrypted()){
    pk = new PdfPKCS7(contents.getOriginalBytes(), sub, provider);
}else{
    pk = new PdfPKCS7(contents.getBytes(),sub,provider);
}

出于某种原因,iTextPDF得出结论,PDF是加密的,并使用getBytes变体进行签名验证。但是,PDF没有加密(据我所知),所以它应该使用getOriginalBytes

当我强制使用这个原始内容时,在调试时,验证就成功了!

因此,这似乎是iTextPDF中的一个bug,可能是由pdf中不寻常的因素组合造成的。

PDF证书中的一些细节:

代码语言:javascript
复制
Version: 3
Signature algorithm: SHA256 RSA
Key usage: Digital Signature, Encrypt Keys
Public Key: RSA (2048 bits)

不幸的是,我不能分享有关的PDF,因为它包含个人信息。作为荷兰公民,您可以从UWV下载自己的版本,请参阅这些指示

如有任何帮助或建议,我们将不胜感激。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-09-14 17:02:26

此问题的背景似乎是国际标准化组织32000-1 PDF规范中缺少的信息;iText 5.5同时支持ISO 32000-1逐字解释。

在ISO 32000-2中,这一点已得到澄清.

丢失的信息

在PDF成为ISO标准之前,PDF处理器实现者跟随Acrobat的领导,当PDF的文档不明确,甚至声称不同。

当Acrobat加密并对PDF进行签名时,包含签名容器的二进制字符串不会被加密。因此,本例中的其他PDF工具也没有对签名容器进行加密。

于2008年成为国际标准化组织标准。根据ISO 32000-1,

加密适用于文档PDF文件中的所有字符串和流,但有以下例外:

  • 拖车中ID条目的值。
  • 加密字典中的任何字符串
  • 在流中的任何字符串,如内容流和压缩对象流,它们本身都是加密的。

(ISO 32000-1,第7.6节-加密)

根据这一点,在加密和签名的PDF中,包含的二进制字符串--嵌入式签名容器--也将被加密。

于2017年出版了。在其中,上面的枚举是通过一个新条目扩展的。

  • 表示签名字典中内容键的值的任何十六进制字符串。

(ISO 32000-2,第7.6节-加密)

根据这一点,在加密和签名的PDF中,包含的二进制字符串--嵌入式签名容器--不会被加密。

在iText中检索签名容器的代码

在iText中用于检索签名容器的最早代码中,包含签名容器的二进制字符串被假定从未加密过:

代码语言:javascript
复制
pk = new PdfPKCS7(contents.getOriginalBytes(), provider);

(提交ffc70db日期为2004年11月5日,注释为"paulo版本139")

方法getOriginalBytes检索PDF字符串的字节,就像它们在PDF中一样,从未应用过解密。

后来,代码被移动了两三次,没有改变。

添加PAdES支持时,这里只添加子筛选器,仍然使用原始字节:

代码语言:javascript
复制
pk = new PdfPKCS7(contents.getOriginalBytes(), sub, provider);

(2012年8月31日提交691281c,注释为“验证CAdES签名”)

但在2017年初,它被修改为你发现的代码:

代码语言:javascript
复制
if(!reader.isEncrypted()){
    pk = new PdfPKCS7(contents.getOriginalBytes(), sub, provider);
}else{
    pk = new PdfPKCS7(contents.getBytes(),sub,provider);
}

(提交日期为2017年2月9日的0b852d7,注释为“验证签名SUP-1783时处理加密内容流”)

显然,支持问题SUP-1783触发了对ISO 32000-1逐字解释的切换.

在iText 7中我们有

代码语言:javascript
复制
pk = new PdfPKCS7(PdfEncodings.convertToBytes(contents.getValue(), null), sub, provider);

(2015年10月11日的commit ae73650评论为“添加了支持LTV、Ocsp、CRL和TSA的类”)。

但是这里的contents之前标记为未加密

代码语言:javascript
复制
contents.markAsUnencryptedObject();

(2018年4月24日提交6dfb206,注释为“在SignatureUtil中通过只读文档时避免异常”)

在iText 7中,这使contents.getValue()返回原始字节。因此,iText 7支持PDF2.0的澄清。

应该做些什么?

在我看来,考虑到ISO 32000-1的逐字解释,人们应该接受加密或未加密的签名容器,但根据ISO 32000-2的措辞,应该只生成未加密的容器。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52291869

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档