使用纯 JavaScript 签署 PDF

Posted

技术标签:

【中文标题】使用纯 JavaScript 签署 PDF【英文标题】:Sign PDF with plain JavaScript 【发布时间】:2016-01-17 22:38:27 【问题描述】:

随着 WebCrypto API 的发展和 Chrome 和 Firefox 的支持,我想用它来对 PDF 文档进行数字签名。周围的文献不多,但我找到了一些示例 [1] 和一个名为 PKI.js [2] 的库。在示例中,描述了签名过程,但最终返回了签名。我希望我的 Base64 PDF 文件以签名的 Base64 字符串再次返回,但遗憾的是,这不是发生的情况。据我所知,PKI.js 也没有提供对我的 Base64 PDF 进行签名的方法。

有没有办法仅使用 javascript 和 WebCrypto API 签署 PDF?可以在<textarea> 中输入私钥,或者更好地存储在浏览器的证书设置中。

Base64 PDF(来自 REST API)→ 使用 JS 和证书签名 → 签名的 Base64 PDF(发送到 REST)

[1]https://github.com/diafygi/webcrypto-examples [2]https://pkijs.org/

【问题讨论】:

【参考方案1】:

这在技术上是可行的,事实上这是我们在制作 PKIjs 时想到的场景之一(这就是为什么会有这个示例)-https://pkijs.org/examples/PDFexample.html

据说进行签名需要使用 PDF 结构本身,这需要自定义解析器或修改现有解析器(例如 pdfjs)。

长话短说,在浏览器中签署 PDF 需要大量工作,但这是我们正在努力的工作。

【讨论】:

我想签署纯文本,那么这个库可以吗?我没有找到可以读取 p12 证书+私钥并生成签名的示例。 你可以用它签署任何东西。此示例签署任意文件 - pkijs.org/examples/CMSSigned_complex_example.html 此示例显示导入 PKCS#12s - pkijs.org/examples/PKCS12SimpleExample.html 请参阅 unmitigatedrisk.com/?p=543 了解一些限制和实施说明。【参考方案2】:

披露:我为 CISPL 工作。

截至目前,WebCrypto API 不提供对 (Windows) 或任何其他密钥存储或本地加密 USB/智能卡设备的访问。

同样在大多数签名场景中,为了保护服务器边界内的pdf文件,不建议将完整的pdf文件发送到浏览器或签名API服务器。

因此,它的良好做法是创建 PDF 的哈希以进行签名,将哈希发送到浏览器并通过浏览器扩展使用 javascript 访问本地系统上运行的某些应用程序以访问本地密钥库(或 USB/智能卡)并生成签名和发回(在 PDF 签名的情况下为 PKCS7 或 CMS 容器)到服务器,在该服务器上签名可能会被注入到 PDF 中,从该 PDF 中创建用于签名的哈希并发送到浏览器或签名 api 服务器。

对于基于浏览器的签名场景,我公司提供了一个这样的免费浏览器扩展 Signer.Digital 和服务器所需的 .NET 库。本地系统(windows上chrome浏览器后面运行的主机)可以从cNET Download site下载 安装此主机并重新启动 Chrome 会自动添加 Signer.Digital Chrome Extension 和/或 Signer.Digital Firefox Extension

此扩展程序的实际工作在here 中进行了说明,并附有完整的代码演练和工作示例 VS 2015 项目源代码的下载链接。

从扩展调用方法的Javascript:

 //Calculate Sign for the Hash by Calling function from Extension SignerDigital
 SignerDigital.signPdfHash(hash, $("#CertThumbPrint").val(), "SHA-256")      //or "SHA256"
  .then(
         function (signDataResp) 
           //Send signDataResp to Server
     ,
         function (errmsg) 
             //Send errmsg to server or display the result in browser.
           
  );

如果成功,则返回 Base64 编码的 pkcs7 签名 - 使用合适的库或 Signer.Digital 提供的库将签名注入 pdf

如果失败,则返回以“SDHost Error:”开头的错误消息

来自浏览器的数字签名

    服务器将要签名的数据/文档/内容的哈希发送到浏览器。 浏览器使用 Signer.Digital Browser Extension Javascript API 从 Signer.Digital Browser Extension Host 调用操作。 在 Windows 上,浏览器扩展主机使用 Microsoft 证书存储和底层 CSP 来获得哈希签名。 在 Linux 上,浏览器扩展主机使用加密设备的 PKCS#11 .SO 库来获得哈希签名。 Signer.Digital Browser EXtension Host 将原始签名(哈希签名)或签名容器返回给浏览器。 如果是加密设备,即。 USB 令牌或智能卡,用户的私钥永远不会从设备中出来,而是将要签名的哈希发送到设备进行签名。 Web 应用程序(浏览器中的 Javascript)将签名发送回服务器,并且可以将签名修改为 PDF 文档、XML 或 Json 或根据需要。

【讨论】:

“它的良好做法...” - 好的做法是什么,取决于您更信任哪个应用程序。您的答案假定服务器应用程序是值得信赖的,可以信任它为用户确实想要签名的 pdf 提供哈希值。对于第一次处理某些服务器应用程序的用户,如果他的计算机上碰巧有一个他信任的签名应用程序,这种假设可能不成立。.. @mki,我说的是在服务器上生成 pdf。如果用户在自己的计算机上拥有 PDF,那么有很多可用的工具,包括最常用的 Acrobat Reader 来签署 PDF 文档……但问题是关于使用 JavaScript 进行签名,这意味着文档在服务器上,而签名在浏览器上。跨度> “我说的是在服务器上生成 pdf。” - 即使这样,如果我不相信该服务器会向我发送正确的哈希值,我(作为用户)希望能够通过下载 pdf 来应用签名,使用受信任的软件在本地签名,然后再次上传签名的 pdf。不过,我不得不承认,任意浏览器扩展在这里并不真正算作受信任的软件,所以在这里,用例是否只向我传输哈希或整个文档确实无关紧要...... 一些例子是签署我刚刚在我公司的 Web 应用程序(比如 CRM 或基于 Web 的会计软件)上准备的发票或采购订单,我想最终签署。其他示例是 eReturn 我刚刚在我的公司 Web 应用程序或 eReturn 服务提供商的 Web UI 上进行了预览,然后我不喜欢在签名之前查看返回的 XML 或 Json ......只是试图帮助受信任的案例......你想要我用“常见做法”代替“良好做法”?! :) 无需更改任何内容。如果用户有理由信任有问题的服务器(例如,由适当的 CC 认证支持),那么该解决方案确实有其魅力。【参考方案3】:

有PDFSign.js,一个可以在浏览器中签署PDF文件的库。它使用forge 作为签名。如果 PKI.js 支持分离的 pkcs7 签名,那么替换 forge 应该很容易。

【讨论】:

【参考方案4】:

您可以使用 openpgp.js 签署任何文件(包括 pdf)

https://openpgpjs.org/openpgpjs/doc/#create-and-verify-detached-signatures

(向下滚动到“创建和验证分离签名”)

将文件作为 Uint8Array 读取并使用您的私钥对其进行签名。

【讨论】:

每当人们想要签署并强调他们想要签署 pdf 的事实时,他们通常意味着他们想要使用集成的 pdf 签名而不是使用单独的、分离的签名文件进行签名。这意味着,对于可互操作的签名,PKCS#1/PKCS#7 格式基于 X.509 证书,而不是 PGP 格式。

以上是关于使用纯 JavaScript 签署 PDF的主要内容,如果未能解决你的问题,请参考以下文章

PDFJava操作PDF之iText超入门

如何仅使用客户端 JavaScript 正确签署对 Amazon 的 ItemLookup 的 GET 请求?

JavaScript 使用纯Javascript剥离HTML

JavaScript使用纯函数避免bug

使用纯 JavaScript 和 Chromium WebAudio API 生成音调

javascript 使用纯Javascript进行AJAX GET和POST