使用 java 和 iText 签署 PDF 哈希值

2023-12-04

我有一个生成 PDF 的应用程序,需要签名。

我们没有用于签署文档的证书,因为它们位于 HSM 中,而我们使用证书的唯一方法是使用 Web 服务。

    PdfReader reader = new PdfReader(src);
    reader.setAppendable(true);
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    FileOutputStream fout = new FileOutputStream(dest);
    PdfStamper stamper = PdfStamper.createSignature(reader, fout, '\0');
    PdfSignatureAppearance appearance  = stamper.getSignatureAppearance();

    appearance.setReason("Test");
    appearance.setLocation("footer");
    appearance.setVisibleSignature(new Rectangle(100, 100, 200, 200), 1, null);
    appearance.setCertificate(certChain[0]);

    PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
    dic.setReason(appearance.getReason());
    dic.setLocation(appearance.getLocation());
    dic.setContact(appearance.getContact());
    dic.setDate(new PdfDate(appearance.getSignDate()));
    appearance.setCryptoDictionary(dic);

    HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
    exc.put(PdfName.CONTENTS, new Integer(8192 * 2 + 2));
    appearance.preClose(exc);

    ExternalDigest externalDigest = new ExternalDigest()
    {
        public MessageDigest getMessageDigest(String hashAlgorithm) throws GeneralSecurityException
        {
            return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
        }
    };

    PdfPKCS7 sgn = new PdfPKCS7(null, certChain, "SHA256", null, externalDigest, false);
    InputStream data = appearance.getRangeStream();
    byte[] hash = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA256"));
    Calendar cal = Calendar.getInstance();

    byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, cal, null, null, CryptoStandard.CMS);
    sh = MessageDigest.getInstance("SHA256", "BC").digest(sh);

    String hashPdf = new String(Base64.encodeBytes(sh));

    String hashSignat = hashPdf;

这是我们的代码,首先,我们获取签名外观,并计算哈希值

此时,我们就得到了文档的哈希码。然后我们将哈希发送到 Web 服务,并获得签名的哈希代码。

最后,我们将签名的哈希值放入 PDF:

    sgn.setExternalDigest(Base64.decode(hashSignat), null, "RSA");
    byte[] encodedSign = sgn.getEncodedPKCS7(hash, cal, null, null, 
    null, CryptoStandard.CMS);
    byte[] paddedSig = new byte[8192];
    System.arraycopy(encodedSign, 0, paddedSig, 0, 
    encodedSign.length);

    PdfDictionary dic2 = new PdfDictionary();
    dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));

    appearance.close(dic2);

此时,我们得到了一个已签名的 PDF,但签名无效。 Adobe 表示“文档自签名以来已被更改或损坏”。

我已经经历过使用外部服务和 iText 签署 PDF, PDF签名 itext pkcs7 多重签名 and 是否可以使用哈希值和签名哈希值对 PDF 文档进行签名?但没有运气。


示例文件

您分享了一个示例文件在对您的问题的评论中由您的代码签名。

对该文件的分析(使用分析签名 test testPriyankaSignatureSampleinformedconsent_Signed)表明实际签名的哈希是

1134ED96F42AC7352E546BE0E906C0BF5D44229AEAFAC39145DB40A0BB7E817B

这应该是经过身份验证的属性的哈希值,但它们的哈希值是

E7101D9770ABF5E58D43670AAC6E9418265AE80F74B3BDFB67911C0CC5D5D949.

带符号的字节范围哈希是

D7917CF3BA9FE5D5B626ED825965D699F7C54DBBF9F18DECE18EF8DD36DC4C28,

所以它也不是这个哈希值。因此,尚不清楚该签名哈希值来自何处。

最终事实证明

Web 服务上的编码实用程序不同,因此解码的哈希值是错误的,并且MessageDigest(sh = MessageDigest.getInstance("SHA256", "BC").digest(sh);)不需要。这一行给出了错误的哈希值。

签署先前签署的文件

随后又出现了一个新问题:

当我签署已签名的文件时,该文件正在获得签名,但签名无效。它显示“签名字节范围无效”。

要签署已签名的 pdf,您必须使用附加模式。为此你需要一个不同的PdfStamper.createSignature重载多一个参数,aboolean,您设置为true.

原因是通常(即没有激活附加模式) iText 重新组织内部 PDF 结构并删除未使用的对象。在已签名的 PDF 中,这通常会移动(已存在的)签名的位置,从而使签名结构无效。使用附加模式不过,iText 保持原始 PDF 字节不变,仅附加新数据。

更改哈希值

我只是很困惑为什么同一文件的字节“sh”的值每次都会发生变化?实际上,我想存储文件的哈希值。是因为“cal”吗?

事实上,每次您开始操作 PDF 时,结果都会获得一个新的唯一 ID。此外,还存储修改时间。对于签名用例,签名时间也有所不同。

我可以一次获取哈希值并将其存储在数据库中并稍后对哈希值进行签名并附加到pdf中吗?

你要么必须

  • 暂时保持印章和外观打开,否则您必须
  • 保留准备好的文件,否则你必须
  • 修补 iText 以允许您设置签名时间、操作时间和 id 的固定值。

第一个选择对我来说是不可能的。我没有得到第二个第三个选项。您能详细说明两者或提供我可以参考的任何参考资料吗?

修补 iText 以强制每个文件使用固定哈希值

好吧,首先是第三个选项,即修补 iText,通常是您不想做的事情,因为它会使合并以后的 iText 更新变得困难。

OpenPdf(一个较旧的 iText 分支)包含一个补丁,添加EnforcedModificationDate, OverrideFileId, and IncludeFileID属性到PdfStamper. (PdfSignatureAppearance已经有一个SignDate属性。)应用此补丁是为了允许 eSignature DSS 在其签名过程中使用 OpenPdf(还包括创建签名的 PDF 两次,因此需要固定的哈希值)。

您可能不想切换到这个旧的 iText 分支,因为它错过了新 iText 版本的许多修复和新选项。

保留已经准备好的文件

因此,您可能应该保留最初创建的文件带有空签名,例如在某个临时文件夹中,然后应用延期签约一旦您检索到最终签名。

这本质上就是 iText 示例的内容C4_09_延迟签名就是这样,它首先创建一个包含所有内容的中间 PDF,仅缺少签名字节:

public void emptySignature(String src, String dest, String fieldname, Certificate[] chain) throws IOException, DocumentException, GeneralSecurityException {
    PdfReader reader = new PdfReader(src);
    FileOutputStream os = new FileOutputStream(dest);
    PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0');
    PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
    appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, fieldname);
    appearance.setCertificate(chain[0]);
    ExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
    MakeSignature.signExternalContainer(appearance, external, 8192);
}

仅在第二步中注入实际签名:

public void createSignature(String src, String dest, String fieldname, PrivateKey pk, Certificate[] chain) throws IOException, DocumentException, GeneralSecurityException {
    
    PdfReader reader = new PdfReader(src);
    FileOutputStream os = new FileOutputStream(dest);
    ExternalSignatureContainer external = new MyExternalSignatureContainer(pk, chain);
    MakeSignature.signDeferred(reader, fieldname, os, external);
}

using

class MyExternalSignatureContainer implements ExternalSignatureContainer {

    protected PrivateKey pk;
    protected Certificate[] chain;
    
    public MyExternalSignatureContainer(PrivateKey pk, Certificate[] chain) {
        this.pk = pk;
        this.chain = chain;
    }
    
    public byte[] sign(InputStream is) throws GeneralSecurityException {
        try {
            PrivateKeySignature signature = new PrivateKeySignature(pk, "SHA256", "BC");
            String hashAlgorithm = signature.getHashAlgorithm();
            BouncyCastleDigest digest = new BouncyCastleDigest();
            PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false);
            byte hash[] = DigestAlgorithms.digest(is, digest.getMessageDigest(hashAlgorithm));
            Calendar cal = Calendar.getInstance();
            byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, cal, null, null, CryptoStandard.CMS);
            byte[] extSignature = signature.sign(sh);
            sgn.setExternalDigest(extSignature, null, signature.getEncryptionAlgorithm());
            return sgn.getEncodedPKCS7(hash, cal, null, null, null, CryptoStandard.CMS);
        }
        catch (IOException ioe) {
            throw new ExceptionConverter(ioe);
        }
    }

    public void modifySigningDictionary(PdfDictionary signDic) {
    }
    
}

这里的has是在第二步中计算的,但是你不需要这样做,你可以

  • 已经在第一步中计算出来,不使用原始数据ExternalBlankSignatureContainer as in emptySignature()上面但是将其扩展为计算哈希值MyExternalSignatureContainer does,
  • 将此值存储在您的数据库中,并且
  • (一旦您检索到签名)使用以下变体注入该签名MyExternalSignatureContainer它不计算哈希值,而是准确注入返回的签名。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 java 和 iText 签署 PDF 哈希值 的相关文章

随机推荐

  • 使用 EF 伙伴类上的自定义属性 (AttributeTargets.Class) 进行数据验证

    我有一个实体框架生成的类 具有以下属性 public DateTime LaunchDate public DateTime ExpirationDate 我需要强制执行 到期日期 gt 启动日期 我正在使用一个伙伴类 如各种帖子中所述 我
  • JavaScript 无法在 Chrome 上创建/识别 cookie

    我之前问过一个关于为什么我的 Javascript 不起作用的问题 现在要问另一个问题 当我最终决定在我的默认浏览器 Google Chrome 之外的其他浏览器上测试我的代码时 令人惊讶的是 创建和读取 cookie 可以在 Intern
  • Win32 确定键盘何时连接/断开

    我正在尝试确定键盘何时连接或断开连接 我尝试过以下策略 注册设备通知 使用 RegisterDeviceNotification 如上所述MSDN被建议于如何在 DirectInput 中确定键盘已断开 当我尝试这个时 我得到了DB DEV
  • 从分配给正文的按键处理程序中排除表单字段

    我有一个keypress网页上分配给 body 元素的处理程序 我真的希望它在网页的任何地方都处于活动状态 或者说我是这么想的 这keypress文本输入表单中的事件也会激活正文处理程序 这是有道理的 但我不想要 理想情况下 我想保留key
  • rJava 警告含义

    我正在尝试为 R 编译一个使用的包openNLP这依赖于rJava 编译失败并显示以下消息 当我尝试在 R 中加载包时 它显示 library openNLP Warning message replacing previous impor
  • 使用 boost::concept_check 检查模板参数时出现编译错误

    我正在尝试编译一个使用小桥模板 externally locked 的简单示例 该模板仅在锁定其父 AccountManager 对象后才能控制对 BankAccount 的访问 参考增强同步 include
  • Unity3d 在 Android 上共享图像

    尝试在 Unity 游戏上实现 Android 图像共享 我整理了几个源代码 这是我当前的代码 public static void shareImage string subject string title string message
  • 对枚举值进行排序

    我想知道是否有任何方法可以为不同的类订购枚举 例如 如果我有一组固定的化学物质 它们以不同的方式与其他化学物质发生反应 有些反应强烈 有些反应较弱 我基本上希望能够根据该组应该反应的化学物质 即取决于类别 来改变它们的排列顺序 我确实知道我
  • Javascript:用逗号分割字符串,括号内除外

    给定字符串的形式 abc ab c d e f g zyx h 123 如何拆分它以获得以下数组格式 abc ab c d e f g zyx h 123 我已经尝试过正常的 javascript split 但是它无法按预期工作 尝试正则
  • 浮点数比较不匹配

    我有一个非常奇怪的错误 我无法弄清楚 float distance Utils distance this spriteStartX this spriteStartY this getX this getY Utils log D Che
  • Java,按对象获取ArrayList索引

    所以我在我的代码中遇到了一个小问题 synchronized clients clients remove this 当客户端断开连接时 但现在我需要能够将该客户端的名称发送给所有其他客户端 为此 我本质上需要执行类似的操作 synchro
  • 确保方法声明是继承的

    如何防止在打算继承定义的情况下意外定义非继承方法 我听说有一个技巧可以表达它 但没有人能记住它 解释 我有类树 Base 在派生链的某个地方 agrglist 中存在微妙的错误 导致 D 不可继承 程序顺利编译 并且在运行时调用了错误的方法
  • 如何检查VB.NET代码中Access SysCmd失败的原因?

    我们有一个用 VB NET 以及客户端中的 VBA 开发的应用程序 在 VB NET 开发的安装程序自定义操作中 我们使用 Access 的 SysCmd 603 将 mdb 文件编译为 mde 该命令众所周知未记录 示例代码如下所示 a
  • Android 上 OpenCV 中位图和 Mat 之间转换的正确方法?

    我目前正在尝试将一些遗留代码从 iPhone 迁移到 Android 此代码使用 OpenCV 库进行一些图像处理 我不明白如何在 Mat 和 Android Bitmap 类之间进行转换 此代码显示了一个非常简化的示例 它将位图加载到 M
  • 使用总和而不是计数绘制分箱数据

    我试图寻找答案 但似乎找不到适合我的答案 我有一个数据集 data 有两个变量 人们的年龄 age 和奖项数量 awards 我的目标是绘制 R 中奖项数量与年龄的关系 仅供参考 一个人可以获得多个奖项 并且人们可以拥有相同的年龄 我尝试绘
  • __LITTLE_ENDIAN_BITFIELD 和 __BIG_ENDIAN_BITFIELD? [复制]

    这个问题在这里已经有答案了 我想知道内核编译器将如何处理不同的字节序位域 struct iphdr if defined LITTLE ENDIAN BITFIELD u8 ihl 4 version 4 elif defined BIG
  • 将 SetFields 与 MongoDB C# 驱动程序 2.0 结合使用

    使用旧驱动程序 我可以指定要从查询返回的字段 如下所示 var cursor Collection Find query SetFields Fields
  • 将索引添加到 Google App Engine 上数据存储区中已存在的实体属性

    我知道您应该将 Index 添加到实体模型中的所有属性以添加索引 但是如果您忘记并且数据存储中已经存在实体的实时写入怎么办 有没有办法通过开发人员控制台或其他方式手动向所有这些属性添加索引 我发现 如果您将 Index 添加到实体并重新部署
  • 如何在 Jaxb 中忽略 XML 中的某些标签

    我的xml文件如下
  • 使用 java 和 iText 签署 PDF 哈希值

    我有一个生成 PDF 的应用程序 需要签名 我们没有用于签署文档的证书 因为它们位于 HSM 中 而我们使用证书的唯一方法是使用 Web 服务 PdfReader reader new PdfReader src reader setApp