使用iTextSharp库提取PDF文件中包含的签名图像

4

我有一个签名的PDF文件。使用iTextSharp库的此功能,我可以找到证书p7m签名:

        private void GetSignature(string FileName)
    {
        AcroFields acroFields = new PdfReader(FileName).AcroFields;
        List<string> names = acroFields.GetSignatureNames();

        foreach (var name in names)
        {
            PdfDictionary dict = acroFields.GetSignatureDictionary(name);
            PdfString contents = (PdfString)PdfReader.GetPdfObject(dict.Get(PdfName.CONTENTS));

            byte[] PKCS7 = contents.GetOriginalBytes();
            ByteArrayToFile(@"c:\signature\" + name + ".p7m", PKCS7);

        }
    }

现在...我怎样才能提取与签名相关的图像(位图)?这可能吗? 谢谢,Luigi

如果您确定它只是位图,那么提取它并不太困难。但在实际情况中,非位图的图形和文本也经常是可视化的一部分。这将要求将PDF部件呈现为图像,而这在itext中尚未实现。 - mkl
请提供一个样本文件。在数字签名中不需要生物识别数据,因为这里的身份验证使用证书和非对称加密。 - mkl
您可以在此处找到文件:https://dl.dropboxusercontent.com/u/2303720/signSample.pdf - Gigi
1
啊,使用xyzmo软件创建的签名... 他们曾经使用自己完全独立的PDF对象进行签名。根据您提供的示例,他们现在至少已经开始使用标准签名作为其签名技术的基础。尽管如此,他们仍然在一个被引用为**/Sig字典中的附加对象中包含自己的信息,该对象被引用为/xyzmo_signature**。我认为您将不得不询问xyzmo人如何解释添加到签名中的自定义数据,特别是因为它的大部分内容都在XML EncryptedSignatureDataContainer片段中... - mkl
我已经作为答案添加了对样本文档中包含的数据的分析。如果您对提取其中任何数据的详细信息感兴趣,请告诉我。我会相应地扩展答案。我将使用Java代码,因为我很少使用c#。但翻译应该很容易。 - mkl
显示剩余2条评论
1个回答

5
在您的示例文档中,“签名”一词有三重含义:
  1. 它包含符合PDF规范ISO 32000-1:2008的数字签名。
  2. 相应的可视化内容包含手写签名的位图图像。
  3. 相应的签名字典包含软件添加到PDF中的所有签名数据的专有信息。最有可能这些专有信息包含OP评论中提到的生物识别数据。
根据创建这些多级签名的软件制造商,手写签名似乎是主要的身份证明。数字签名仅用于保护文件免受更改;它并不一定反映手动签署人的身份,而是反映手动签名所在设备的所有者的身份(“请在此处签名以确认您已收到包裹”)。

函数

手写签名捕获 - 在签名板、支付终端、iPad或Android设备上收集可法医鉴定的签名。

签名验证 - 将手写签名与预先录入的个人资料进行比对。

控制签名过程中的所有步骤 - 包括定位签名字段、填写表格、添加注释、添加附件等等。

保护文档完整性 - 通过数字签名将其密封。

(xyzmo英文网站首页)

关于使用iText提取所有这些信息的问题...

  1. 可以使用AcroFields类中的签名相关方法轻松提取和验证数字签名的属性,正如OP已经观察到的那样。
  2. 手写签名的位图显像也可以相当容易地提取。签名表单字典的外观流仅绘制作为资源附加到流中的位图。
  3. 包含专有信息的数据容器也可以提取,因为它只是签名字典中另一个关键字的值。
  4. 不幸的是,该数据容器的内容被打包成称为EncryptedSignatureDataContainer的XML片段。该XML片段的有效负载数据是否可以正确解密以及如何进行解释,需要向xyzmo公司本身请求信息,我不知道他们是否认为这些信息是公开的。

因此,最相关的信息是最难访问的。

PS 关于加密生物识别有效负载的解密,我在制造商的网站上找到了以下内容:

该文档包含加密的捕获签名(RSA 4096 + AES256)。一个人的签名被立即加密,当它被签名板捕获时,使用一个特殊证书的私钥。这个特殊证书是由使用xyzmo套件的公司选择的,并且通常存储在公司之外的安全环境中(银行保险柜、外部公证处等)。因此,xyzmo本身无法访问这个证书。对于签名的加密,xyzmo套件只需要证书的公钥。只有在解密和从文档中提取签名时,才需要使用私钥。只有公司授权访问这个证书的特定人员,才能使用PenAnalyst工具解密配置文件,这是套件的一部分。(来自xyzmo英文网站数字签名捕获FAQ)
因此,为了解密生物识别数据,您必须可以访问相应的私钥,这个私钥通常存储在公司之外的安全环境中(银行保险柜、外部公证处等)。如果您有这种访问权限,我们可以继续讨论这些已解密数据的格式...;)

顺便说一下,如果有人可以从签署的文件中简单地检索生物识别数据,那么它们就可以轻松地复制到其他文件中以伪造签名。

提取手写签名的位图图像

由于对提取手写签名的位图图像有特别的兴趣,这里提供一个快速而简单的帮助程序来提取签名的图像。如前所述,我在Java中执行此操作,因为我更熟悉该语言:

public class XyzmoSignatureDataExtractor
{
    public XyzmoSignatureDataExtractor(PdfReader reader)
    {
        this.reader = reader;
    }

    public PdfImageObject extractImage(String signatureName) throws IOException
    {
        MyImageRenderListener listener = new MyImageRenderListener();

        PdfDictionary sigFieldDic = reader.getAcroFields().getFieldItem(signatureName).getMerged(0);
        PdfDictionary appearancesDic = sigFieldDic.getAsDict(PdfName.AP);
        PdfStream normalAppearance = appearancesDic.getAsStream(PdfName.N);

        PdfDictionary resourcesDic = normalAppearance.getAsDict(PdfName.RESOURCES);
    
        PdfContentStreamProcessor processor = new PdfContentStreamProcessor(listener);
        processor.processContent(ContentByteUtils.getContentBytesFromContentObject(normalAppearance), resourcesDic);        

        return listener.image;
    }

    class MyImageRenderListener implements RenderListener
    {
        public void beginTextBlock() { }

        public void endTextBlock() { }

        public void renderImage(ImageRenderInfo renderInfo)
        {
            try
            {
                image = renderInfo.getImage();
            }
            catch (IOException e)
            {
                throw new RuntimeException("Failure retrieving image", e);
            }
        }

        public void renderText(TextRenderInfo renderInfo) { }

        PdfImageObject image = null;
    }

    final PdfReader reader;
}

您可以像这样使用它:
PdfReader reader = new PdfReader(resourceStream);
XyzmoSignatureDataExtractor extractor = new XyzmoSignatureDataExtractor(reader);
AcroFields acroFields = reader.getAcroFields();

for (String name: acroFields.getSignatureNames())
{
    System.out.printf("\nTesting signature '%s'.\n", name);
    PdfImageObject image = extractor.extractImage(name);

    OutputStream os = new FileOutputStream("target/test-outputs/SampleXyzmoSignature-image-" + name + "." + image.getFileType());
    os.write(image.getImageAsBytes());
    os.close();

    PdfDictionary imageDictionary = image.getDictionary();
    PRStream maskStream = (PRStream) imageDictionary.getAsStream(PdfName.SMASK);
    if (maskStream != null)
    {
        PdfImageObject maskImage = new PdfImageObject(maskStream);

        os = new FileOutputStream("target/test-outputs/SampleXyzmoSignature-image-" + name + "-mask." + maskImage.getFileType());
        os.write(maskImage.getImageAsBytes());
        os.close();
    }
}

警告:XyzmoSignatureDataExtractor确实是一个快速而粗糙的hack。许多假设被做出,null检查被省略,...


非常感谢您的回答。 关于第二点,当您提到: “手写签名的位图图像也可以相当容易地提取。” 您能否提供提取单个签名位图的代码? 我尝试使用此ExtractImages(filename)方法从pdf中提取图像,但它不起作用。谢谢。 - Gigi
您的 PdfImageExtractor 可以提取页面内容流中包含或引用的图像。然而,手头上的签名图像是从签名注释的正常外观流中引用的。我在答案中添加了一些快速且简单的 Java 代码,以展示如何提取这些图像。 - mkl
非常感谢,它完美地运行了! 您的建议非常有用。 祝一切顺利。 - Gigi
如果您对 .Net/iTextSharp 版本感兴趣,@user3492925 已经将代码移植到 这个答案 中。 - mkl

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