使用Apache PDFBOX无法验证数字签名。

7

我是一名数字签名的新手。在我们的一个项目中,我们使用Apache PdfBox处理数字签名pdf文件。虽然我们可以测试所有功能,但验证已签名的pdf文件是我们无法解决的问题。我们使用BouncyCastle作为提供者。以下是代码:

从pdf文件中获取数字签名和签名内容:

byte[] signatureAsBytes = pdsignature.getContents(new FileInputStream(this.INPUT_FILE));
byte[] signedContentAsBytes = pdsignature.getSignedContent(new FileInputStream(this.INPUT_FILE));

数字签名验证:
Security.addProvider(new BouncyCastleProvider());
Signature signer = Signature.getInstance("RSA","BC");

//Get PublicKey from p7b file
X509Certificate cert509=null;
File file = new File("C:\\certificate_file.p7b");
FileInputStream fis = new FileInputStream(file);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Collection c = cf.generateCertificates(fis);
Iterator it = c.iterator();
PublicKey pubkey;

while (it.hasNext()) 
{
   cert509 = (X509Certificate) it.next();
   pubkey = cert509.getPublicKey();
}

boolean VERIFIED=false;
Security.addProvider(new BouncyCastleProvider());
Signature signer = Signature.getInstance("RSA","BC");
PublicKey key=this.getPublicKey(false);
signer.initVerify(key);

List<PDSignature> allsigs = this.PDFDOC.getSignatureDictionaries();
Iterator<PDSignature> i = allsigs.iterator();
    
while(i.hasNext())
{
        PDSignature sig = (PDSignature) i.next();
        byte[] signatureAsBytes = sig.getContents(new FileInputStream(this.INPUT_FILE));
        byte[] signedContentAsBytes = sig.getSignedContent(new FileInputStream(this.INPUT_FILE));
        signer.update(signedContentAsBytes);
        VERIFIED=signer.verify(signatureAsBytes);
}
    
System.out.println("Verified="+VERIFIED);

以下是以p7b格式呈现的证书相关摘录 - 我正在使用BouncyCastle作为安全提供程序。
  Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11
  Key:  Sun RSA public key, 2048 bits
  Validity: [From: Tue Aug 06 12:26:47 IST 2013,
  To: Wed Aug 05 12:26:47 IST 2015]
  Algorithm: [SHA256withRSA]

使用上述代码时,我的响应始终为“false”。我不知道该如何解决这个问题。请帮助我。


据我了解,需要一份“未签名”版本的 PDF 文件进行比较。如何从已签名的副本中提取原始的 PDF 文件?任何帮助将不胜感激。 - Ranjan
如何从签名副本中提取pdf文件的原始副本?这就是“pdsignature.getSignedContent”的作用。 - mkl
pdsignature.getSignedContent 返回原始内容 - 这也是我的理解。然而,验证从未返回 true。正如我之前所说,我是数字签名的新手,因此无法解决这个问题。 - Ranjan
短暂的查看你的代码发现它完全忽略了所集成PDF签名的类型,包括adbe.x509.rsa_sha1adbe.pkcs7.detachedadbe.pkcs7.sha1ETSI.CAdESETSI.RFC3161。你只需要检查其中一种吗?如果是这样,是哪一种? - mkl
请指出正确的选择。输入文件是一个带签名的PDF,使用的算法是SHA256withRSA。 - Ranjan
显示剩余2条评论
2个回答

10
你的主要问题是,PDF签名有多种类型,签名容器的格式和实际签名字节也不同。而你的BC代码只能验证裸签名字节序列,这些字节序列包含在前述的签名容器中。
可互操作的签名类型
正如标题所示,以下列表包含“可互操作的签名类型”,它们或多或少地被严格定义。PDF规范指定了一种方法,可以包括完全自定义的签名方案。但让我们假设我们处于一个可互操作的情况下。签名类型的集合缩小为:
  • adbe.x509.rsa_sha1ISO 32000-1第12.8.3.2节中定义为PKCS#1签名;签名值Contents包含一个DER编码的PKCS#1二进制数据对象;该数据对象是一个相当裸露的签名,在RSA的情况下,它包含了填充文档哈希和哈希算法的加密结构。

  • adbe.pkcs7.sha1ISO 32000-1第12.8.3.3节中定义为PKCS#7签名;签名值Contents包含一个DER编码的PKCS#7二进制数据对象;该数据对象是一个大容器对象,也可以包含元信息,例如可以包含用于构建证书链的证书、用于证书吊销检查的吊销信息、数字时间戳以固定签名时间等。文档字节范围的SHA1摘要应封装在类型为Data的PKCS#7 SignedData字段中。该SignedData的摘要应作为普通PKCS#7摘要纳入其中。

  • adbe.pkcs7.detachedISO 32000-1第12.8.3.3节中定义为PKCS#7签名;签名值Contents包含一个DER编码的PKCS#7二进制数据对象,见上文。 文档字节范围上的原始签名消息摘要应作为普通PKCS#7 SignedData字段纳入其中。PKCS#7 SignedData字段不应封装任何数据。

  • ETSI.CAdES.detachedETSI TS 102 778-3中定义,并将成为ISO 32000-2的一部分;签名值Contents包含一个CMS中指定的DER编码SignedData对象;CMS签名容器与PKCS#7签名容器密切相关,参见上文。这本质上是adbe.pkcs7.detached的不同配置和更严格定义的变体。

  • ETSI.RFC3161ETSI TS 102 778-4中定义,并将成为ISO 32000-2的一部分;签名值Contents包含一个RFC 3161中指定的TimeStampToken;时间戳令牌再次是PKCS#7签名容器的近亲,参见上文,但它们包含一个特殊的数据子结构,其中包含文档哈希、时间戳创建时间以及发行时间服务器的信息。

我建议您学习我提到的规范和引用的文档,大多数是RFC。有了这些知识,您可以轻松找到适当的BouncyCastle类来分析不同的签名内容。
PS(2021年):与此同时,ISO 32000-2已经发布,并确实包含了ETSI.CAdES.detached和ETSI.RFC3161的规范。此外,ETSI技术规范TS 102 778- * for PAdES已被一项实际标准替代,即ETSI EN 319 142- *。

你应该将这个答案发布到其他问题中,对于那些对PDF数字签名内部感兴趣的人来说非常有帮助。 - Le Duc Duy

5

以下是使用Apache PDFBox 1.8.16和Bouncy Castle 1.44验证最常见的PDF签名adbe.pkcs7.detached的示例:

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.bouncycastle.cms.CMSProcessable;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;

import java.io.File;
import java.io.FileInputStream;
import java.security.cert.CertStore;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

public class PDFBoxValidateSignature {
    public static void main(String[] args) throws Exception {
        File signedFile = new File("sample-signed.pdf");
        // We load the signed document.
        PDDocument document = PDDocument.load(signedFile);
        List<PDSignature> signatureDictionaries = document.getSignatureDictionaries();
        // Then we validate signatures one at the time.
        for (PDSignature signatureDictionary : signatureDictionaries) {
            // NOTE that this code currently supports only "adbe.pkcs7.detached", the most common signature /SubFilter anyway.
            byte[] signatureContent = signatureDictionary.getContents(new FileInputStream(signedFile));
            byte[] signedContent = signatureDictionary.getSignedContent(new FileInputStream(signedFile));
            // Now we construct a PKCS #7 or CMS.
            CMSProcessable cmsProcessableInputStream = new CMSProcessableByteArray(signedContent);
            CMSSignedData cmsSignedData = new CMSSignedData(cmsProcessableInputStream, signatureContent);
            SignerInformationStore signerInformationStore = cmsSignedData.getSignerInfos();
            Collection signers = signerInformationStore.getSigners();
            CertStore certs = cmsSignedData.getCertificatesAndCRLs("Collection", (String) null);
            Iterator signersIterator = signers.iterator();
            while (signersIterator.hasNext()) {
                SignerInformation signerInformation = (SignerInformation) signersIterator.next();
                Collection certificates = certs.getCertificates(signerInformation.getSID());
                Iterator certIt = certificates.iterator();
                X509Certificate signerCertificate = (X509Certificate) certIt.next();
                // And here we validate the document signature.
                if (signerInformation.verify(signerCertificate.getPublicKey(), (String) null)) {
                    System.out.println("PDF signature verification is correct.");
                    // IMPORTANT: Note that you should usually validate the signing certificate in this phase, e.g. trust, validity, revocation, etc. See http://www.nakov.com/blog/2009/12/01/x509-certificate-validation-in-java-build-and-verify-chain-and-verify-clr-with-bouncy-castle/.
                } else {
                    System.out.println("PDF signature verification failed.");
                }
            }
        }
    }
}

我不确定是否有官方的示例,我已经在PDFBox 1.8.4的官方示例中查找,但没有找到任何内容。


请问您能否将非标准库引用的所有类的导入内容都包括进来呢?谢谢。 - Carles Barrobés

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接