附件损坏签名第 2 部分

2024-01-11

我创建了将图像添加到现有 pdf 文档然后对其进行签名的代码,所有这些都使用 PDFBox(请参阅下面的代码)。

该代码很好地添加了图像和签名。但是,在某些文档中,Acrobat Reader 抱怨“签名字节范围无效”。

该问题似乎与中描述的问题相同this https://stackoverflow.com/questions/21257507/attachment-damages-singature问题。该问题的答案更详细地描述了该问题:问题是我的代码在文档中留下了交叉引用类型的混合(流和表)。事实上,由于由此产生的问题,某些文档甚至无法打开。

我的问题是:我该如何防止这种情况发生?如何将图像添加到现有 pdf 文档而不创建多个交叉引用类型?

public class TC3 implements SignatureInterface{

private char[] pin = "123456".toCharArray();
private BouncyCastleProvider provider = new BouncyCastleProvider();
private PrivateKey privKey;
private Certificate[] cert;

public TC3() throws Exception{
    Security.addProvider(provider);
    KeyStore keystore = KeyStore.getInstance("PKCS12", provider);        
    keystore.load(new FileInputStream(new File("resources/IIS_keystore.pfx")), pin.clone());
    String alias = keystore.aliases().nextElement();
    privKey = (PrivateKey) keystore.getKey(alias, pin);
    cert = keystore.getCertificateChain(alias);
}

public void doSign() throws Exception{
    byte inputBytes[] = IOUtils.toByteArray(new FileInputStream("resources/rooster.pdf"));
    PDDocument pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));
    PDJpeg ximage = new PDJpeg(pdDocument, ImageIO.read(new File("resources/logo.jpg")));
    PDPage page = (PDPage)pdDocument.getDocumentCatalog().getAllPages().get(0);
    PDPageContentStream contentStream = new PDPageContentStream(pdDocument, page, true, true);
    contentStream.drawXObject(ximage, 50, 50, 356, 40);
    contentStream.close();
    ByteArrayOutputStream os = new ByteArrayOutputStream();
    pdDocument.save(os);
    os.flush();        
    pdDocument.close();

    inputBytes = os.toByteArray(); 
    pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));

    PDSignature signature = new PDSignature();
    signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
    signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
    signature.setName("signer name");
    signature.setLocation("signer location");
    signature.setReason("reason for signature");
    signature.setSignDate(Calendar.getInstance());

    pdDocument.addSignature(signature, this);

    File outputDocument = new File("resources/signed.pdf");
    ByteArrayInputStream fis = new ByteArrayInputStream(inputBytes);
    FileOutputStream fos = new FileOutputStream(outputDocument);
    byte[] buffer = new byte[8 * 1024];
    int c;
    while ((c = fis.read(buffer)) != -1)
    {
        fos.write(buffer, 0, c);
    }
    fis.close();
    FileInputStream is = new FileInputStream(outputDocument);

    pdDocument.saveIncremental(is, fos);
    pdDocument.close();     
}

public byte[] sign(InputStream content) {
    CMSProcessableInputStream input = new CMSProcessableInputStream(content);
    CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
    List<Certificate> certList = Arrays.asList(cert);
    CertStore certStore = null;
    try{
        certStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList), provider);
        gen.addSigner(privKey, (X509Certificate) certList.get(0), CMSSignedGenerator.DIGEST_SHA256);
        gen.addCertificatesAndCRLs(certStore);
        CMSSignedData signedData = gen.generate(input, false, provider);
        return signedData.getEncoded();
    }catch (Exception e){}
    return null;
}

public static void main(String[] args) throws Exception {
    new TC3().doSign();
}

问题

正如已经解释过的这个答案 https://stackoverflow.com/a/21260292/1729265,这里的问题是

  • 当非增量存储带有添加图像的文档时,PDFBox 1.8.9 会使用交叉引用表来执行此操作,无论原始文件使用表还是流;如果原始文件使用流,则交叉引用流字典条目将复制到trailer字典;

    ...
    0000033667 00000 n
    0000033731 00000 n
    trailer
    <<
    /DecodeParms <<
    /Columns 4
    /Predictor 12
    >>
    /Filter /FlateDecode
    /ID [<5BD95916CAE5E84E9D964396022CBDCD> <6420B4547602C943AF37DD6C77496BE8>]
    /Info 6 0 R
    /Length 61
    /Root 1 0 R
    /Size 35
    /Type /XRef
    /W [1 2 1]
    /Index [20 22]
    >>
    startxref
    35917
    %%EOF
    

    (其中大部分trailer这里的条目毫无用处,甚至具有误导性,见下文。)

  • 当增量保存签名时,COSWriter.doWriteXRefInc uses COSDocument.isXRefStream确定现有文档(我们上面存储的文档)是否使用交叉引用流。正如上面提到的,事实并非如此。但不幸的是,COSDocument.isXRefStream在 PDFBox 1.8.9 中实现为

    public boolean isXRefStream()
    {
        if (trailer != null)
        {
            return COSName.XREF.equals(trailer.getItem(COSName.TYPE));
        }
        return false;
    }
    

    因此,误导性的trailer entry Type如上所示,PDFBox 认为它必须使用交叉引用流。

结果是一个文档,其初始修订版以交叉引用表和奇怪的预告片条目结束,其第二修订版以交叉引用流结束。这是无效的。

解决方法

不过幸运的是,了解问题是如何出现的可以提供一种解决方法:消除麻烦的问题trailer条目,例如像这样:

    inputBytes = os.toByteArray();
    pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));
    pdDocument.getDocument().getTrailer().removeItem(COSName.TYPE); // <<<<<<<<<< Remove misleading entry <<<<<<<<<<

通过此解决方法,签名文档中的两个修订都使用交叉引用表,并且签名有效。

Beware, 如果即将推出的 PDFBox 版本更改为使用外部引用流来保存从具有交叉引用流的源加载的文档,则必须再次删除该解决方法。

不过,我认为这不会在即将到来的 1.x.x 版本中发生,并且 2.0.0 版本将引入一个根本性改变的 API,因此无论如何,原始代码将无法开箱即用。


其他想法

我也尝试过其他方法来规避这个问题,试图

  • 也将第一个操作存储为增量更新,或者
  • 在与签名相同的增量更新期间添加图像,

cf. SignLikeUnOriginalToo.java https://github.com/mkl-public/testarea-pdfbox1/blob/master/src/test/java/mkl/testarea/pdfbox1/sign/SignLikeUnOriginalToo.java,但失败了。 PDFBox 1.8.9 增量更新似乎仅适用于添加签名。


重新审视其他想法

在进一步研究使用 PDFBox 创建附加修订版后,我再次尝试了其他想法,现在成功了!

关键部分是将添加和更改的对象标记为已更新,包括文档目录中的路径。

应用第一个想法(添加图像作为明确的中间修订版)相当于这一变化doSign:

...
FileOutputStream fos = new FileOutputStream(intermediateDocument);
FileInputStream fis = new FileInputStream(intermediateDocument);

byte inputBytes[] = IOUtils.toByteArray(inputStream);

PDDocument pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));
PDJpeg ximage = new PDJpeg(pdDocument, ImageIO.read(logoStream));
PDPage page = (PDPage) pdDocument.getDocumentCatalog().getAllPages().get(0);
PDPageContentStream contentStream = new PDPageContentStream(pdDocument, page, true, true);
contentStream.drawXObject(ximage, 50, 50, 356, 40);
contentStream.close();

pdDocument.getDocumentCatalog().getCOSObject().setNeedToBeUpdate(true);
pdDocument.getDocumentCatalog().getPages().getCOSObject().setNeedToBeUpdate(true);
page.getCOSObject().setNeedToBeUpdate(true);
page.getResources().getCOSObject().setNeedToBeUpdate(true);
page.getResources().getCOSDictionary().getDictionaryObject(COSName.XOBJECT).setNeedToBeUpdate(true);
ximage.getCOSObject().setNeedToBeUpdate(true);

fos.write(inputBytes);
pdDocument.saveIncremental(fis, fos);
pdDocument.close();

pdDocument = PDDocument.load(intermediateDocument);

PDSignature signature = new PDSignature();
...

(as in SignLikeUnOriginalToo.java https://github.com/mkl-public/testarea-pdfbox1/blob/master/src/test/java/mkl/testarea/pdfbox1/sign/SignLikeUnOriginalToo.java method doSignTwoRevisions)

应用第二个想法(添加图像作为签名修订的一部分)相当于这一变化doSign:

...
byte inputBytes[] = IOUtils.toByteArray(inputStream);
PDDocument pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));

PDJpeg ximage = new PDJpeg(pdDocument, ImageIO.read(logoStream));
PDPage page = (PDPage) pdDocument.getDocumentCatalog().getAllPages().get(0);
PDPageContentStream contentStream = new PDPageContentStream(pdDocument, page, true, true);
contentStream.drawXObject(ximage, 50, 50, 356, 40);
contentStream.close();

page.getResources().getCOSObject().setNeedToBeUpdate(true);
page.getResources().getCOSDictionary().getDictionaryObject(COSName.XOBJECT).setNeedToBeUpdate(true);
ximage.getCOSObject().setNeedToBeUpdate(true);

PDSignature signature = new PDSignature();
...

(as in SignLikeUnOriginalToo.java https://github.com/mkl-public/testarea-pdfbox1/blob/master/src/test/java/mkl/testarea/pdfbox1/sign/SignLikeUnOriginalToo.java method doSignOneStep)

这两种变体显然都比原始方法更可取。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

附件损坏签名第 2 部分 的相关文章

  • “JSONArray 文本必须在 null 的第 1 个字符处以 '[' 开头”

    只是想知道这个错误可能意味着什么 我从下面的代码中得到它 try JSONArray jArray new JSONArray result for int i 0 i
  • Java Swing BoxLayout 忽略 AlignmentX

    在下面的代码中 通过调用setAlignmentX with Component LEFT ALIGNMENT我希望在居中的滑块上获得左对齐的标签 由于某种原因 标签也居中 似乎与传递给 setAlignmentX 的值无关 我必须向 se
  • 不支持的字段:将瞬间格式化为日期 ISO 时的年份[重复]

    这个问题在这里已经有答案了 我正在尝试将 Instant 格式化为 ldap 日期 ISO8601 但在 f format Instant now 处失败 String input 20161012235959 0Z DateTimeFor
  • c# itextsharp如何获取数字签名图像

    是否可以使用 C 代码使用 itextsharp 获取 pdf 文件中任何数字签名的图像 PdfReader pdf new PdfReader location pdf AcroFields acroFields pdf AcroFiel
  • 无论线程如何,对象是否总是能看到其最新的内部状态?

    假设我有一个带有简单整数计数变量的可运行对象 每次可运行对象运行时该变量都会递增 该对象的一个 实例被提交以在计划的执行程序服务中定期运行 class Counter implements Runnable private int coun
  • 如何在ArrayList中的特定位置插入对象

    假设我有一个大小为 n 的对象的 ArrayList 现在我想在特定位置插入另一个对象 假设在索引位置 k 大于 0 且小于 n 并且我希望索引位置 k 处及其之后的其他对象向前移动一个索引位置 那么有没有什么方法可以直接在Java中做到这
  • JAVA 中的 Composer 相当于什么? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我目前从 PHP 转向 java 有没有类似的工具composer https getcomposer org 在 PHP 中用于 JAV
  • H2数据库:如何进行加密保护,而不暴露文件加密密钥

    我们在服务器模式下使用Java H2数据库 因为我们不希望用户访问数据库文件 为了对数据库文件添加更多保护 我们计划使用 AES 加密 将 CIPHER AES 添加到数据库 URL 以防存储被盗 但是 每个用户在连接时还需要提供文件保护密
  • 使用 Jena 查询维基数据

    目前 Wikidata 有一个 SPARQL 端点 https query wikidata org https query wikidata org 我想使用 Jena 3 0 1 查询此网站 我使用以下代码 但收到错误消息 端点返回的
  • 对对象集合进行排序[重复]

    这个问题在这里已经有答案了 如果我有一个简单的字符串列表 List
  • FileObserver 不适用于 Android 6.0 Marshmallow (API 23) 中的外部存储

    我有一个应用程序可以观察外部存储上的公共目录FileObserver 它运行良好Lollipop设备 我想添加对Marshmallow 所以我用它设置了一台 Nexus 9 平板电脑 在 Marshmallow 设备上 它失败 在 Loll
  • 绘制平滑曲线

    我想创建更平滑的曲线 而不仅仅是线角 这是我现在画的图 这是我的代码 case FREEHAND float pts float ptk ptk new float 2 imageMatrix invert inv if mCurrentS
  • 如何在 Eclipse 中获得完全限定的类名?

    有没有一种快速方法可以在 Eclipse 中单击 Java 类并获取其完全限定名称 或将其复制到剪贴板 2016年6月29日编辑 正如 Jeff 所指出的 您只需要执行以下第二步 1 Double click on the class na
  • 使用 Guava Ordering 对对象列表进行多条件排序

    我有一个类无法实现可比较 但需要根据 2 个字段进行排序 我怎样才能用番石榴实现这一目标 假设班级是 class X String stringValue java util Date dateValue 我有一个清单 List
  • 了解Kafka流groupBy和window

    我无法理解 kafka 流中的 groupBy groupById 和窗口的概念 我的目标是聚合一段时间内 例如 5 秒 的流数据 我的流数据看起来像 value 0 time 1533875665509 value 10 time 153
  • Janusgraph 0.3.2 + HBase 1.4.9 - 无法设置 graph.timestamps

    我在 Docker 容器中运行 Janusgraph 0 3 2 并尝试使用运行 HBase 1 4 9 的 AWS EMR 集群作为存储后端 我可以运行 gremlin server sh 但如果我尝试保存某些内容 我会得到粘贴在下面的堆
  • 开发者环境-如何调用/消费其他微服务

    背景 我的环境 Java Play2 MySql 我在 Play2 gt S1 S2 S3 上编写了 3 个无状态 Restful 微服务 S1 消耗来自 S2 和 S3 的数据 因此 当用户点击 S1 时 该服务会异步调用 S2 S3 合
  • 为什么 java.util.Arraylist#clear 按照 OpenJDK 中的方式实现?

    http grepcode com file repository grepcode com java root jdk openjdk 6 b14 java util ArrayList java 473 http grepcode co
  • Java 中序列化的目的是什么?

    我读过很多关于序列化的文章 以及它如何如此美好和伟大 但没有一个论点足够令人信服 我想知道是否有人能真正告诉我通过序列化一个类我们真正可以实现什么 让我们先定义序列化 然后我们才能讨论它为什么如此有用 序列化只是将现有对象转换为字节数组 该
  • Java时区混乱

    我正在运行 Tomcat 应用程序 并且需要显示一些时间值 不幸的是 时间快到了 还有一个小时的休息时间 我调查了一下 发现我的默认时区被设置为 sun util calendar ZoneInfo id GMT 08 00 offset

随机推荐

  • 如何使用 xslt 删除重复的 xml 节点?

    当所有变量都使用 xslt 完全匹配时 我想删除重复项 在此 xml 中 应删除节点 3 因为它是节点 1 的完美副本
  • 有/没有捕获变量的 lambda 之间的签名差异?

    我现在正在使用 C 11 发现使用 lambda 作为 sqlite 回调存在以下问题 当捕获 lambda 内的向量变量时 我收到一条错误消息 指出签名不匹配 不在 lambda 中使用该变量 代替 ret 并且不使用ret内 工作正常
  • 如何使用for循环减少编译时间

    我有下面的 R 代码 客观的 我正在尝试检查中存在的字符串kind对象是由以下内容组成的word通过迭代和比较两个对象的角色定位来识别对象 如果它是另一个的复合 则返回正值 否则返回负值 问题陈述 如果 kind 对象值在每个字符串中包含最
  • 如果当前阶段有任何作业,如何动态引用 Azure Pipelines 中的先前作业

    我正在尝试设置一个 azure yaml 管道 该管道使用两个部署模板来执行两个相应的作业 测试和部署 每个阶段的作业应按顺序运行 因为测试作业会创建部署作业使用的工件 这很好用 但是 对于一种环境 我将部署分为两个阶段 一个阶段仅运行测试
  • iOS中通过处理cookie来维护Session信息

    我是 iOS 开发新手 我正在使用 NSURLSession 来管理会话信息 下面是我用来调用任何服务器 API 的示例代码 NSURLSessionDataTask task NSURLSession sharedSession data
  • 如何将 DataGridViewComboBoxColumn 绑定到返回列表的对象的属性/方法?

    我有一个具有多个属性的自定义对象 其中一个属性返回一个列表 这是该对象的代码 public class SearchResult private int eventId private String eventTitle private i
  • 在子域上安装 GitLab

    我正在尝试在子域上安装 GitLab 我对网络服务器之类的东西不是很熟悉 知识很少 我目前通过以下方式连接到子域ssh email protected cdn cgi l email protection在 Mac 的终端上 然后我ls到子
  • 如何中断 Hover 的 handlerOut

    我有以下情况 我有一个对象 我们称之为 按钮 当您将鼠标悬停在 按钮 上时 它会使另一个对象 信息 向下滑动 当然 当你的鼠标离开Button时 Info就会向上滑动并消失 但是 Info 有一个链接 用户可能想要单击它 我可以延迟信息向上
  • 2 个向量的样本协方差

    我正在尝试计算这两个向量之间的样本协方差 我定义了一个带有两个输入变量的函数 不知道是否正确 我的样本协方差公式也无法运行 谁能帮我用R写出来 xv c 1 5 5 7 8 4 2 2 7 5 4 8 9 yv c 0 1 1 5 0 8
  • Android 2.0:支持Actionbar库(appcompat v7支持库无资源):找不到资源@style/Theme.AppCompat.Light.DarkActionBar

    我跟着http developer android com guide topics ui actionbar html http developer android com guide topics ui actionbar html在
  • Python OrderedDict 与 dict() 比较

    这件事让我完全困惑不解 asset hist for key host val hist list in am output asset history items for index hist item in enumerate val
  • Gist (gist.el / Emacs) -- 在创建时设置“描述”

    默认行为gist region就是离开描述空白的 要设置描述 需要切换到gist list缓冲然后使用该函数gist edit current description设置描述 我希望能够设置描述在创建要点的同时 无需切换到gist list
  • php-ffmpeg 获取视频时长

    当我尝试使用 php ffmpeg 包装器和 ffprobe 获取视频的持续时间时 我得到一个巨大的对象 而不仅仅是持续时间 ffprobe FFMpeg FFProbe create ffprobe gt format this gt v
  • WebClient 下载文件已损坏

    我正在尝试使用 C WebClient 下载文件 这是网址 http www czce com cn cn DFSStaticFiles Future 2018 20180821 FutureDataClearParams txt http
  • 如何使用 CSS 网格布局在 CSS 中制作固定列?

    我制作了一个简单的网站 containerdiv 是两个 div 的父级 left and right 通过使用网格布局 https developer mozilla org en US docs Web CSS CSS Grid Lay
  • 为什么需要目标网络?

    我想了解为什么 DQN 中需要目标网络 我正在阅读有关 通过深度强化学习实现人类水平控制 的论文 我了解 Q learning Q learning 是一种基于价值的强化学习算法 它学习状态 动作之间的 最佳 概率分布 从而在一系列时间步长
  • 带有按钮和输入的弹出框 HTML 内容未呈现[重复]

    这个问题在这里已经有答案了 我正在尝试向弹出窗口添加按钮 但它似乎没有呈现 是像我那样做的还是有更好的方法 myinput popover trigger focus container body placement bottom html
  • 将数据库引用传递到路由中不适用于我的 Node / Express 项目

    我正在使用 Node Express 创建一个简单的 REST API 并尝试将路由逻辑与数据库逻辑分开 我在从路线访问数据库时遇到问题 这是我的 server js 代码 var express require express path
  • AngularJS 中的 ExpressJS 变量 - Mean Stack

    我正在构建一个 MEAN 应用程序 我遇到的一个问题是我想让我的用户对所使用的路由进行某种控制 所以我希望我的服务器端代码 expressJS 在我的客户端代码中设置一些变量 本质上 我希望能够从服务器端代码生成客户端 JS 例如 在 PH
  • 附件损坏签名第 2 部分

    我创建了将图像添加到现有 pdf 文档然后对其进行签名的代码 所有这些都使用 PDFBox 请参阅下面的代码 该代码很好地添加了图像和签名 但是 在某些文档中 Acrobat Reader 抱怨 签名字节范围无效 该问题似乎与中描述的问题相