使用纯JavaScript对PDF进行签名

15
随着WebCrypto API的发展并得到Chrome和Firefox的支持,我想使用它来为PDF文档进行数字签名。虽然没有太多相关的文献,但我找到了一些示例 [1] 和一个名为PKI.js [2] 的库。在这些示例中,描述了签名过程,但最终返回的是一个签名而不是我预期的以Base64字符串格式返回已签名的PDF文件。据我所知,PKI.js也没有提供一种方法来对我的Base64 PDF进行签名。
是否有一种仅使用JavaScript和WebCrypto API对PDF进行签名的方法?私钥可以在<textarea>中输入,或更好的方法是在浏览器的证书设置中存储。
Base64 PDF (from REST API) → 使用JS和证书进行签名 → Signed Base64 PDF (发送到REST)
[1] https://github.com/diafygi/webcrypto-examples [2] https://pkijs.org/
4个回答

8

声明:我在CISPL工作。

目前,WebCrypto API无法访问(Windows)或任何其他密钥存储或本地加密USB /智能卡设备。

此外,在大多数签名方案中,为了保护服务器边界内的PDF文件的要求,不建议将完整的PDF文件发送到浏览器或签名API服务器。

因此,最好的做法是创建PDF的哈希值进行签名,将哈希值发送到浏览器,并通过浏览器扩展使用JavaScript访问运行在本地系统上的某个应用程序来访问本地密钥库(或USB /智能卡)并生成签名,然后将签名发送回服务器(PDF签名的PKCS7或CMS容器),其中签名可以被注入回从中创建哈希的PDF,该哈希已经被发送到浏览器或签名api服务器。

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

该扩展程序的实际工作过程在此处进行说明,包括完整的代码演示和可工作的示例VS 2015项目源代码的下载链接。

Javascript调用扩展程序中的方法:

 //Calculate Sign for the Hash by Calling function from Extension SignerDigital

 SignerDigital.signPdfHash(hash, $("#CertThumbPrint").val(), "SHA-256")      //or "SHA256"
  //SignerDigitial.signHashCAdESBr method may be used for producing ICP-Brazil Signature

  .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:”开头的错误消息。
从浏览器进行数字签名。

Digital Signing from Browser

  1. 服务器向浏览器发送要签名的数据/文档/内容的哈希值。
  2. 浏览器使用Signer.Digital浏览器扩展Javascript API从Signer.Digital浏览器扩展主机中调用操作。
  3. 在Windows上,浏览器扩展主机使用Microsoft证书存储和底层CSP来获取哈希签名。
  4. 在Linux上,浏览器扩展主机使用加密设备的PKCS#11 .SO库来获取哈希签名。
  5. 原始签名(哈希的签名)或签名容器由Signer.Digital浏览器扩展主机返回给浏览器。
  6. 对于USB令牌或智能卡等加密设备,用户的私钥永远不会离开设备,但要签名的哈希值会被发送到设备进行签名。
  7. Web应用程序(浏览器中的Javascript)将签名发送回服务器,并可以将其嵌入PDF文档、XML、Json或其他所需格式中。

2022年7月:新增方法signCAdESBr,以ICP-Brazil标准签署PDF或内容,以及方法signCAdESEg,以Egypt ITIDA CAdES-BES标准签署。


1
“这是一个好的实践…” - 好的实践取决于您信任哪个应用程序。您的答案假定服务器应用程序是可信的,可以信任其提供用户想要签名的PDF文件的哈希值。对于第一次使用某个服务器应用程序并恰好在计算机上拥有可信赖的签名应用程序的用户来说,这种假设可能不成立。 - mkl
@mki,我在谈论在服务器上生成PDF。如果用户在自己的计算机上有PDF,则有许多可用的工具,包括最常用的Acrobat Reader来签署PDF文档...但问题是关于使用JavaScript签名,这意味着文档在服务器上,而签名在浏览器上。 - Bharat Vasant
1
我正在谈论在服务器上生成pdf的问题。即使那时,如果我不相信该服务器会向我发送正确的哈希值,作为用户,我仍希望能够通过下载pdf,在本地使用可信软件对其进行签名,然后再上传已签名的pdf。不过,我必须承认,任意浏览器扩展程序在这里并不算作可信软件,因此,无论使用情况是否只向我传输哈希值或整个文档,这确实都几乎无关紧要... - mkl
一些例子包括在我公司的Web应用程序(例如CRM或基于Web的会计软件)上刚刚准备好的发票或采购订单进行签名,我想最终签署。另一个例子是我刚在公司的Web应用程序或eReturn服务提供商的Web UI上预览的eReturn,然后我不想在签名之前查看返回的XML或Json。只是试图帮助可信赖的情况...你想让我用“常规做法”代替“良好做法”?! :) - Bharat Vasant
1
如果用户有理由信任相关服务器(例如,由适当的CC认证支持),那么该解决方案确实具有其魅力。 - mkl
显示剩余2条评论

7
技术上讲,这是可以实现的,事实上,这是我们制作PKIjs时考虑的情况之一(这也是为什么有这个示例的原因)- https://pkijs.org/examples/PDFexample.html 话虽如此,在浏览器中进行签名需要处理PDF结构本身,这要求使用自定义解析器或修改现有解析器(例如pdfjs)。
简而言之,在浏览器中签署PDF需要大量工作,但我们正在努力解决这个问题。

我想对纯文本进行签名,这个库能实现吗?我没有找到一个可以读取p12证书+私钥并生成签名的示例。 - Michael Chourdakis
1
您可以使用它签署任何内容。此示例签署任意文件-https://pkijs.org/examples/CMSSigned_complex_example.html,此示例显示导入PKCS#12s-https://pkijs.org/examples/PKCS12SimpleExample.html,请参见http://unmitigatedrisk.com/?p=543以获取一些限制和实现说明。 - rmhrisk

2

有一个名为PDFSign.js的库,可以在浏览器中对PDF文件进行签名。它使用forge用于签名。如果PKI.js支持分离式pkcs7签名,则应容易替换掉forge。


0

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

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