PDF文档的数字签名技术

数字签名技术在确保PDF文档的来源认证和完整性方面发挥着重要作用。本文将展示如何对PDF文档应用数字签名,以及如何验证这些签名的有效性。

数字签名的基本概念

数字签名可以用于验证PDF文档的来源(谁签署的?)以及确保PDF文档的完整性(签署后文档是否被更改?)。

数字签名的应用

考虑以下表单:

图1:带有字段和两个空白签名字段的PDF表单

此表单包含两个部分的表单字段;一个用于学生,一个用于教师。通常,首先由学生填写他/她的部分并签署文档。这可以通过以下方式编程实现:

C++ using (FileStream sourceFile = new FileStream( "form.pdf", FileMode.Open, FileAccess.Read)) { // 打开表单 Document document = new Document(sourceFile); // 填写数据字段 TextField projectNr = document.Fields["projectNumber"] as TextField; projectNr.Value = "FF-235"; TextField sName = document.Fields["studentName"] as TextField; sName.Value = "Bob Stapleton"; TextField sDate = document.Fields["studentDate"] as TextField; sDate.Value = "April 18, 2007"; // 检索签名字段 SignatureField sigField = document.Fields["studentSignature"] as SignatureField; // 打开证书存储 Pkcs12Store store = null; using (FileStream storeFile = new FileStream("ChrisSharp.pfx", FileMode.Open, FileAccess.Read)) { store = new Pkcs12Store(storeFile, "Sample"); } // 让工厂决定使用哪种类型 SignatureHandler handler = StandardSignatureHandler.Create(store); // 将处理器与签名字段关联 sigField.SignatureHandler = handler; // 设置可选信息 sigField.ContactInfo = "+31 (0)77 4748677"; sigField.Location = "The Netherlands"; sigField.Reason = "I hereby declare!"; // 保存已签名的文档 - 在保存时,处理器将被调用以签署保存的内容 using (FileStream outFile = new FileStream("signedByStudent.pdf", FileMode.Create, FileAccess.ReadWrite)) { document.Write(outFile); } }

执行上述代码并在PDF阅读器中打开PDF文档后,文档将如下所示:

图2:应用第一个签名后

请注意PDF阅读器显示的问号。这意味着证书尚未被客户端机器信任。这仅仅是将证书添加到信任存储的问题。

接下来,教师将审查学生的数据,然后填写最终字段并签署文档。这可以通过以下方式编程实现:

C++ using (FileStream sourceFile = new FileStream( "..\..\signedByStudent.pdf", FileMode.Open, FileAccess.Read)) { // 打开表单 Document document = new Document(sourceFile); // 填写数据字段 TextField tName = document.Fields["teacherName"] as TextField; tName.Value = "Max Boulton"; TextField tDate = document.Fields["teacherDate"] as TextField; tDate.Value = "April 18, 2007"; // 检索签名字段 SignatureField sigField = document.Fields["teacherSignature"] as SignatureField; // 打开证书存储 Pkcs12Store store = null; using (FileStream storeFile = new FileStream("../..MaxBoulton.pfx", FileMode.Open, FileAccess.Read)) { store = new Pkcs12Store(storeFile, "teacherpassword"); } // 让工厂决定使用哪种类型 SignatureHandler handler = StandardSignatureHandler.Create(store); // 将处理器与签名字段关联 sigField.SignatureHandler = handler; // 设置可选信息 sigField.ContactInfo = "+31 (0)77 4748677"; sigField.Location = "The Netherlands"; sigField.Reason = "I hereby declare!"; // 保存已签名的文档 - 在保存时,处理器将被调用以签署保存的内容 using (FileStream outFile = new FileStream("../..\signedByTeacher.pdf", FileMode.Create, FileAccess.ReadWrite)) { document.Write(outFile, DocumentWriteMode.AppendUpdate); } }

如果查看代码,它实际上与之前的代码示例相同。唯一的显著区别是向Document.Write方法传递了一个额外的参数:

DocumentWriteMode.AppendUpdate

。这指示PDFKit.NET将新字段数据和签名保存为所谓的“更新”。将在下一节中讨论这一点。

执行上述代码并在PDF阅读器中打开PDF文档后,文档将如下所示:

图3:应用第二个签名后

请注意第一个签名的图标已更改为警告标志。这表明“自签署以来文档已更新”。这正是情况。

更新

请注意,当保存第二个签名时,向Document.Write传递了一个额外的参数,即DocumentWriteMode.AppendUpdate。这指示PDFKit.NET将新字段数据和签名保存为“更新”。这意味着原始PDF数据完全保持不变,更改被附加。下面的图示说明了这一点。

图4:PDF更新

因此,第一个签名仍然有效,因为被签署的确切数据没有变化;只是添加了更新。

因此,在保存更新后,现在实际上有两个版本的文档;一个由学生签署,一个由教师签署。检索给定签名应用的确切文档是有用的。显然,签名者只为那个版本发誓,而不是为之后创建的版本。

给定一个文档,可以枚举所有更新或版本的文档,并将副本保存到磁盘,如下所示:

C++ using (FileStream sourceFile = new FileStream( "..\..\signedByTeacher.pdf", FileMode.Open, FileAccess.Read)) { // 打开PDF文档 Document document = new Document(sourceFile); // 计算更新的数量 Console.WriteLine("This document has {0} updates.", document.Updates.Count); // 将每个更新保存为新的PDF文档 foreach (Update update in document.Updates) { string name = string.Format(@"..\..\signedByTeacher_{0}.pdf", update.Index); using (FileStream updateFile = new FileStream(name, FileMode.Create, FileAccess.Write)) { update.Write(updateFile); } } }

但也许更有趣的是,可以打开一个已签名的文档,并且对于每个签名字段,可以检索已签名的更新。以下代码示例枚举了所有签名字段,并保存了已签名的更新。

C++ using (FileStream sourceFile = new FileStream( "..\..\signedByTeacher.pdf", FileMode.Open, FileAccess.Read)) { // 打开表单 Document document = new Document(sourceFile); foreach (Field field in document.Fields) { // 这是一个签名字段吗? SignatureField sigField = field as SignatureField; if (null != sigField) { // 它被签署了吗? if (sigField.IsSigned) { // 保存更新并按字段命名 string name = string.Format(@"..\..\{0}.pdf", sigField.FullName); using (FileStream updateFile = new FileStream(name, FileMode.Create, FileAccess.Write)) { sigField.SignedUpdate.Write(updateFile); } } } } }

执行此代码后,将保存两个新的PDF文档:studentSignature.pdf和teacherSignature.pdf。每个文档显示由相应字段签署的版本。

using (FileStream inFile = new FileStream( "..\..\signedByTeacher.pdf", FileMode.Open, FileAccess.Read)) { // 打开表单 Document document = new Document(inFile); foreach (Field field in document.Fields) { // 这是一个签名字段吗? SignatureField sigField = field as SignatureField; if (null != sigField) { Console.WriteLine("Field '{0}'", sigField.FullName); // 它被签署了吗? if (sigField.IsSigned) { // 验证,基于默认处理器。 bool verified = sigField.Verify(); Console.WriteLine("-- {0}", verified ? "Verified" : "Not verified"); if (verified) { // 文档在签署后是否被修改? bool modified = sigField.DocumentModifiedAfterSigning; Console.WriteLine("-- {0}", modified ? "Modified after signing" : "Not modified after signing"); } } else { Console.WriteLine("-- Not signed", sigField.FullName); } } } }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485