从签名的PDF中提取PKCS1

3

我需要从签名后的PDF文档中提取签名字段,以创建打印版的签名。到目前为止,我已经使用以下iText代码成功地恢复了签名者证书、原因、签名日期和其他字段:

PdfReader reader = new PdfReader(signedPdf);
AcroFields af = reader.getAcroFields();
ArrayList<String> names = af.getSignatureNames();

SimpleDateFormat sdf = new SimpleDateFormat(
                    "dd/MM/yyyy 'a las' HH:mm:ss");

for (int i = 0; i < names.size(); ++i) {

    StringBuilder sb = new StringBuilder();
    String name = names.get(i);
    PdfPKCS7 pk = af.verifySignature(name);

    String firmante = CertificateInfo.getSubjectFields(
            pk.getSigningCertificate()).getField("CN");
    sb.append("Nombre del firmante: " + firmante + "\n");

    Date signDate = pk.getSignDate().getTime();
    String sdate = sdf.format(signDate);
    sb.append("Fecha y hora de la firma: " + sdate + "\n");


    String razon = pk.getReason();
    sb.append("proposito: " + razon + "\n");
}

据我所知,PDF签名是使用iText PdfPkcs7类创建的,使用setExternalDigest方法添加了在外部应用程序中创建的PKCS1字节数组。该文件看起来已经正确签名,并且可以通过外部工具验证。
// Create the signature
PdfPKCS7 sgn = new PdfPKCS7(null, chain, "SHA1", "BC", null, false);

//pkcs1Bytes is a byte array with the signed document hash
sgn.setExternalDigest(pkcs1Bytes, null, "RSA");

然而,打印版本所需的一个必要字段是“签名数字图章”,它是已签署文档哈希或PKCS1的base64字符串。

能够从已签名的PDF文档中提取那些PKCS1字节吗?

修改:我忘了提到,在验证签名后使用PdfPKCS7.getEncodedPKCS1()方法时,会抛出ExceptionConverter: java.security.SignatureException: object not initialized for signing异常。


@pedrofb 抱歉,我忘记提到,在验证后使用该方法会抛出“ExceptionConverter: java.security.SignatureException: object not initialized for signing”的异常。 - Hugo Hernandez
@pedrofb 谢谢,知道这点很好。这改变了我对问题的看法。 - Hugo Hernandez
使用iText简单地提取CMS签名容器,然后使用已经链接的BouncyCastle版本分析该容器,这个方法怎么样? - mkl
只是为了确认一下:您需要哪个摘要值?对于CMS签名,通常涉及多个哈希值,参见 这个答案。您需要签署文档流的摘要值还是签署属性的摘要值? - mkl
1
这听起来像是签名值,也就是已签名哈希。但是这个哈希不是立即针对文档的哈希,而是针对已签名属性的哈希,而文档哈希是其中一个属性的值。 - mkl
显示剩余4条评论
2个回答

3
我已经查看了代码,似乎类PdfPKCS7不允许访问摘要。但是,内容存储在私有成员PdfPKCS7.digest中。因此,使用反射可以提取它。 我在这里这里找到了一个类似的示例(基本相同)。
PdfPKCS7 pdfPkcs7 = acroFields.verifySignature(name);
pdfPkcs7.verify();

Field digestField = PdfPKCS7.class.getDeclaredField("digest");
digestField.setAccessible(true);
byte[] digest = (byte[]) digestField.get(pdfPkcs7);

我认为你需要的变量是digest,因为在执行签名时,该值在getEncodedPKCS1中被赋值。

 public byte[] getEncodedPKCS1() {
   try {
        if (externalDigest != null)
            digest = externalDigest;
        else
            digest = sig.sign();

     //skipped content

以下是在verify()中使用的方式:verifyResult = sig.verify(digest);

请注意,digest是一个私有变量,因此名称或内容可能取决于版本。请查看您特定版本的代码。


这对我很有效!这段代码减少了复杂性,简单总是更好,谢谢。 - Hugo Hernandez
1
虽然这个解决方案看起来并不复杂,但在某些框架中可能会出现反射问题(其中使用反射访问私有信息可能被禁止)。因此,最终取决于具体情况的细节。 - mkl

3
考虑到您的代码,我假设您正在使用5.x版本的iText而不是7.x版本。您可以使用反射(参见这个旧答案或pedrofb在此处的答案),或者您可以仅使用iText提取CMS签名容器,然后使用BouncyCastle分析该容器;如果您使用iText的签名相关功能,则通常已经存在BC的某个版本。
正如OP已经观察到的那样,PdfPKCS7.getEncodedPKCS7()会出现“ExceptionConverter:java.security.SignatureException:object not initialized for signing”错误。原因是该方法用于检索由PdfPKCS7实例新构造的签名容器。
要使用iText提取CMS签名容器,您可以改用以下代码:
AcroFields fields = reader.getAcroFields();
PdfDictionary sigDict = fields.getSignatureDictionary(name); 
PdfString contents = sigDict.getAsString(PdfName.CONTENTS);
byte[] contentBytes = contents.getOriginalBytes();

contentBytes现在包含编码的CMS容器(加上一些尾随字节,通常是空字节,因为Contents值通常比签名容器所需的要大)。

使用BouncyCastle分析此容器并不困难,但具体细节可能取决于您使用的确切BouncyCastle版本。


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