背景

  • 对PDF文档进行数字签名的需求
  • 对PDF文档添加水印的需求
  • 网上资料版本不一或不全

本文章提到的Spire.Pdf均是使用的Spire.Pdf for .NET,除此之前还有其他语言的版本,如Spire.Pdf for JAVA;
Spire.Pdf主要用于操作PDF,另外还有Spire.Excel、Spire.Doc等
主要介绍了在C#中使用Spire.Pdf组件包对PDF文档进行数字签名、添加水印功能,旨在引导大家快速、轻松的对PDF文档进行数字签名和添加水印功能;

简介

Spire.PDF for .NET 是一款专业的基于.NET平台的PDF文档控制组件。它能够让开发人员在不使用Adobe Acrobat和其他外部控件的情况下,运用.NET 应用程序创建,阅读,编写和操纵PDF 文档。Spire.PDF for .NET 功能丰富,除了基本的功能比如:绘制多种图形,图片,创建窗体字段,插入页眉页脚,输入数据表,自动对大型表格进行分页外,Spire.PDF for .NET还支持PDF数字签名,将HTML转换成PDF格式,提取PDF文档中的文本信息和图片等,目前Spire.PDF for .NET共有两个版本,一个是免费版本一个是付费版本,免费版本如果只是处理简单的pdf是没问题的,但是如果涉及到输出为pdf则会只显示前10页,第十一页则是预定的购买页介绍,我这里主要是对PDF文档的数字签名和水印,所以不涉及输出pdf;

依赖

本文示例代码依赖于Spire.Pdf,可以在项目中使用NuGet程序包引入。

源码

核心代码

  1 public class DigitalSignature
2 {
3 /// <summary>
4 /// 页顶部红色警告字样覆盖白色图片Base64.
5 /// </summary>
6 private const string WatermarkCoverBase64 = "/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCABHAycDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9U6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//Z";
7
8 /// <summary>
9 /// 构造函数.
10 /// </summary>
11 /// <param name="waitSignFile">待签名文件.</param>
12 /// <param name="imageSign">签名图片.</param>
13 /// <param name="pfx">签名证书.</param>
14 /// <param name="pfxPwd">签名证书密码.</param>
15 public DigitalSignature(byte[] waitSignFile, byte[] imageSign, byte[] pfx, string pfxPwd)
16 {
17 this.WaitSignFile = waitSignFile;
18 this.ImageSign = imageSign;
19 this.Pfx = pfx;
20 this.PfxPwd = pfxPwd;
21 }
22
23 /// <summary>
24 /// 构造函数.
25 /// </summary>
26 /// <param name="waitSignFile">待签名文件.</param>
27 /// <param name="charactersSign">签名文字.</param>
28 /// <param name="signRightLeftWidth">签名右向左宽度.</param>
29 /// <param name="signBottomUpHeight">签名低向上高度.</param>
30 /// <param name="pfx">签名证书.</param>
31 /// <param name="pfxPwd">签名证书密码.</param>
32 public DigitalSignature(byte[] waitSignFile, string charactersSign, float signRightLeftWidth, float signBottomUpHeight, byte[] pfx, string pfxPwd)
33 {
34 this.WaitSignFile = waitSignFile;
35 this.CharactersSign = charactersSign;
36 this.SignRightLeftWidth = signRightLeftWidth;
37 this.SignBottomUpHeight = signBottomUpHeight;
38 this.Pfx = pfx;
39 this.PfxPwd = pfxPwd;
40 }
41
42 /// <summary>
43 /// 构造函数.
44 /// </summary>
45 /// <param name="waitSignFile">待签名文件.</param>
46 /// <param name="imageSign">签名图片.</param>
47 /// <param name="charactersSign">签名文字.</param>
48 /// <param name="pfx">签名证书.</param>
49 /// <param name="pfxPwd">签名证书密码.</param>
50 public DigitalSignature(byte[] waitSignFile, byte[] imageSign, string charactersSign, byte[] pfx, string pfxPwd)
51 {
52 this.WaitSignFile = waitSignFile;
53 this.ImageSign = imageSign;
54 this.CharactersSign = charactersSign;
55 this.Pfx = pfx;
56 this.PfxPwd = pfxPwd;
57 }
58
59 /// <summary>
60 /// Gets or sets 待签名文件.
61 /// </summary>
62 public byte[] WaitSignFile { get; set; }
63
64 /// <summary>
65 /// Gets or sets 图签名.
66 /// </summary>
67 public byte[] ImageSign { get; set; }
68
69 /// <summary>
70 /// Gets or sets 文字签名.
71 /// </summary>
72 public string CharactersSign { get; set; }
73
74 /// <summary>
75 /// Gets or sets 签名右向左的宽度.
76 /// </summary>
77 public float? SignRightLeftWidth { get; set; }
78
79 /// <summary>
80 /// Gets or sets 签名顶向上高度.
81 /// </summary>
82 public float? SignBottomUpHeight { get; set; }
83
84 /// <summary>
85 /// Gets or sets 签名索引页面(不指定默认所有页进行签名).
86 /// </summary>
87 public int? SignIndexPages { get; set; }
88
89 /// <summary>
90 /// Gets or sets Pfx证书.
91 /// </summary>
92 public byte[] Pfx { get; set; }
93
94 /// <summary>
95 /// Gets or sets Pfx证书密码.
96 /// </summary>
97 public string PfxPwd { get; set; }
98
99 public Stream Signature()
100 {
101 ///加载PDF文档
102 PdfDocument pdf = new PdfDocument();
103 pdf.LoadFromBytes(this.WaitSignFile);
104
105 if (pdf?.Pages?.Count <= 0)
106 {
107 throw new Exception("文件有误");
108 }
109
110 X509Certificate2 x509 = new X509Certificate2(this.Pfx, this.PfxPwd);
111 PdfOrdinarySignatureMaker signatureMaker = new PdfOrdinarySignatureMaker(pdf, x509);
112
113 var appearance = new PdfCustomSignatureAppearance(this.CharactersSign, this.ImageSign, this.SignRightLeftWidth, this.SignBottomUpHeight);
114 IPdfSignatureAppearance signatureAppearance = appearance;
115
116 // 绘画白底图片
117 PdfRubberStampAnnotation logoStamp = new PdfRubberStampAnnotation(new RectangleF(new PointF(0, 0), new SizeF(350, 22)));
118 PdfAppearance logoApprearance = new PdfAppearance(logoStamp);
119 //var logoPath = AppDomain.CurrentDomain.BaseDirectory + "\\white.jpg";
120 byte[] byt = Convert.FromBase64String(WatermarkCoverBase64);
121 Stream streamByLogo = new MemoryStream(byt);
122 PdfImage image = PdfImage.FromStream(streamByLogo);
123 PdfTemplate template = new PdfTemplate(350, 22);
124 template.Graphics.DrawImage(image, 0, 0);
125 logoApprearance.Normal = template;
126 logoStamp.Appearance = logoApprearance;
127
128 if (this.SignIndexPages.HasValue)
129 {
130 if (this.SignIndexPages.Value < 0 || this.SignIndexPages.Value > pdf?.Pages?.Count)
131 {
132 throw new Exception("签名索引页有误");
133 }
134
135 var page = pdf.Pages[this.SignIndexPages.Value];
136
137 // 添加白底图片覆盖页面顶部印记
138 page.AnnotationsWidget.Add(logoStamp);
139
140 // 在页面中的指定位置添加可视化签名
141 signatureMaker.MakeSignature("signName_", page, page.ActualSize.Width - appearance.SignRightLeftWidth, page.ActualSize.Height - appearance.SignBottomUpHeight, appearance.SignRightLeftWidth, appearance.SignBottomUpHeight, signatureAppearance);
142 }
143 else
144 {
145 foreach (PdfPageBase page in pdf.Pages)
146 {
147 // 添加白底图片覆盖页面顶部印记
148 page.AnnotationsWidget.Add(logoStamp);
149
150 // 在页面中的指定位置添加可视化签名
151 signatureMaker.MakeSignature("signName_", page, page.ActualSize.Width - appearance.SignRightLeftWidth, page.ActualSize.Height - appearance.SignBottomUpHeight, appearance.SignRightLeftWidth, appearance.SignBottomUpHeight, signatureAppearance);
152 }
153 }
154
155 MemoryStream stream = new MemoryStream();
156 pdf.SaveToStream(stream, FileFormat.PDF);
157 pdf.Close();
158 return stream;
159 }
160
161 /// <summary>
162 /// 使用第三方插件 =》 去除 Evaluation Warning : The document was created with Spire.PDF for .NET.
163 /// </summary>
164 /// <param name="sourcePdfs">原文件地址</param>
165 //private static MemoryStream ClearPdfFilesFirstPage(MemoryStream sourcePdf)
166 //{
167 // iTextSharp.text.pdf.PdfReader reader = null;
168 // iTextSharp.text.Document document = new iTextSharp.text.Document();
169 // iTextSharp.text.pdf.PdfImportedPage page = null;
170 // iTextSharp.text.pdf.PdfCopy pdfCpy = null;
171 // int n = 0;
172 // reader = new iTextSharp.text.pdf.PdfReader(sourcePdf);
173 // reader.ConsolidateNamedDestinations();
174 // n = reader.NumberOfPages;
175 // document = new iTextSharp.text.Document(reader.GetPageSizeWithRotation(1));
176 // MemoryStream memoryStream = new MemoryStream();
177 // pdfCpy = new iTextSharp.text.pdf.PdfCopy(document, memoryStream);
178 // document.Open();
179 // for (int j = 2; j <= n; j++)
180 // {
181 // page = pdfCpy.GetImportedPage(reader, j);
182 // pdfCpy.AddPage(page);
183
184 // }
185 // reader.Close();
186 // document.Close();
187 // return memoryStream;
188 //}
189 }
190
191
192 public class PdfCustomSignatureAppearance : IPdfSignatureAppearance
193 {
194 public PdfCustomSignatureAppearance(string charactersSign, byte[] sign, float? signRightLeftWidth, float? signBottomUpHeight)
195 {
196 this.CharactersSign = charactersSign;
197
198 if (sign != null && sign.Length > 0)
199 {
200 this.Sign = sign;
201 MemoryStream ms = new MemoryStream(sign);
202 var image = System.Drawing.Image.FromStream(ms);
203 if (!signRightLeftWidth.HasValue)
204 {
205 signRightLeftWidth = image.Width;
206 }
207
208 if (!signBottomUpHeight.HasValue)
209 {
210 signBottomUpHeight = image.Height;
211 }
212 }
213
214 this.SignRightLeftWidth = signRightLeftWidth.Value;
215 this.SignBottomUpHeight = signBottomUpHeight.Value;
216 }
217
218 /// <summary>
219 /// Gets or sets 签名.
220 /// </summary>
221 public byte[] Sign { get; set; }
222
223 /// <summary>
224 /// Gets or sets 签名右向左的宽度.
225 /// </summary>
226 public float SignRightLeftWidth { get; set; }
227
228 /// <summary>
229 /// Gets or sets 签名顶向上高度.
230 /// </summary>
231 public float SignBottomUpHeight { get; set; }
232
233 /// <summary>
234 /// Gets or sets 文字签名.
235 /// </summary>
236 public string CharactersSign { get; set; }
237
238 public void Generate(PdfCanvas g)
239 {
240 if (!string.IsNullOrWhiteSpace(CharactersSign))
241 {
242 float fontSize = 15;
243 var font = new System.Drawing.Font("Arial", fontSize);
244 PdfTrueTypeFont fontByPdf = new PdfTrueTypeFont(font, true);
245 g.DrawString(CharactersSign, fontByPdf, PdfBrushes.Black, new PointF(0, 0));
246 }
247
248 if (this.Sign != null && this.Sign.Length > 0)
249 {
250 Stream stream = new MemoryStream(this.Sign);
251 g.DrawImage(Spire.Pdf.Graphics.PdfImage.FromStream(stream), new PointF(20, 20));
252 }
253 }
254 }

调用实现

 1 static void Main(string[] args)
2 {
3
4 /*
5 前言:最近有个需求是需要对文档进行数字签名;
6 描述:本示例基于Spire.Pdf组件对PDF进行数字签名,演示了
7 签名证书使用项目
8 CreateSelfSignedCertificateByBouncyCastle(https://github.com/daileass/CreateSelfSignedCertificateByBouncyCastle.git)
9 生成的自签名证书pfx,解决了数字签名后文档头部有警告
10
11 */
12
13 var fileCert = System.Environment.CurrentDirectory + "\\Cert\\";
14 var file = System.Environment.CurrentDirectory + "\\File\\";
15 var filePath = file + "dome.pdf";
16 var newFilePath = file + $"dome_{DateTime.Now.ToString("yyyyMMddHHmmss")}.pdf";
17 var pfxFilePath = fileCert + "edd9386229324d969692dcabf97ac095dpps.fun.pfx";
18 var pfxFilePwd = "ABCD123456";
19 var signFilePath = file + "sign.png";
20
21 // 数字签名
22 var digitalSignature = new DigitalSignature(
23 File2Bytes(filePath),
24 File2Bytes(signFilePath),
25 "Sign Here:",
26 File2Bytes(pfxFilePath),
27 pfxFilePwd
28 );
29 var stream = digitalSignature.Signature();
30
31 // 保存签名后的文件
32 using (var fileStream = File.Create(newFilePath))
33 {
34 stream.Seek(0, SeekOrigin.Begin);
35 stream.CopyTo(fileStream);
36 }
37
38 Console.WriteLine("OK");
39 Console.ReadLine();
40 }
41
42 /// <summary>
43 /// 将文件转换为byte数组
44 /// </summary>
45 /// <param name="path">文件地址</param>
46 /// <returns>转换后的byte数组</returns>
47 public static byte[] File2Bytes(string path)
48 {
49 if (!System.IO.File.Exists(path))
50 {
51 return new byte[0];
52 }
53
54 FileInfo fi = new FileInfo(path);
55 byte[] buff = new byte[fi.Length];
56
57 FileStream fs = fi.OpenRead();
58 fs.Read(buff, 0, Convert.ToInt32(fs.Length));
59 fs.Close();
60
61 return buff;
62 }

源码下载:https://github.com/daileass/PDFDigitalSignatureBySelfSignedCertificate.git

C#使用Spire.Pdf包对PDF文档进行数字签名的更多相关文章

  1. 《R语言入门与实践》第二章:R包和帮助文档

    这一章讲了两方面的内容,如何使用 R 包和帮助文档. R包 下载 R 包 命令: install.packages("<nameofpackage>") R 会话中启用 ...

  2. java读取pdf和MS Office文档

    有时候PDF中的文字无法复制,这可能是因为PDF文件加密了,不过使用PDFBox开源软件就可以把它读出来. 还有一个用于创建PDF文件的项目----iText. PDFBox下面有两个子项目:Font ...

  3. Linux系统下Java 转换Word到PDF时,结果文档内容乱码的解决方法

    本文分享在Linux系统下,通过Java 程序代码将Word转为PDF文档时,结果文档内容出现乱码该如何解决.具体可参考如下内容: 1.问题出现的背景 在Windows系统中,使用Spire.Doc ...

  4. PDF/WORD/EXCEL/PPT 文档在线阅读

    查资料看了2种解决方法: 1.通过办公软件dll转换,用flans去看 2.通过Aspose转换成pdf格式,在用js前台读pdf(我用的pdf.js) 今天我解决的就是WORD/EXCEL/PPT ...

  5. pdf如何转换为word文档

    我们经常会遇到需要将PDF转换为WORD文档,对于我来讲,有些PDF没有目录,看起来非常不方便,于是就特别想转成WORD,然后增加目录,想看某一节内容时,快速查找. 这里我总结了一些方法,后续也会不断 ...

  6. asp.net 实现pdf、swf等文档的浏览

    一.pdf的浏览 可以借助于pdf.js插件完成,使用pdf.js的好处是不需要安装额外的插件(比如flash),是纯web的解决方案.插件的下载链接:http://mozilla.github.io ...

  7. Solr 4.4.0利用dataimporthandler导入本地pdf、word等文档

    1. 创建本地目录 $ mkdir /usr/local/contentplatform/solr/solr/core1/file1 $ ls -lh total 88M -rw-r--r-- tnu ...

  8. 导出jar包和api文档

    导出jar包过程 右击项目名称->export 选择java->JAR file next->选择路径 导出成功 生成api文档 选择doc->右键export java-&g ...

  9. Java使用poi包读取Excel文档

    项目需要解析Excel文档获取数据,就在网上找了一些资料,结合自己这次使用,写下心得: 1.maven项目需加入如下依赖: <dependency> <groupId>org. ...

随机推荐

  1. C#中常用的目录|文件|路径信息操作

    更新记录 本文迁移自Panda666原博客,原发布时间:2021年5月16日. 说明 .NET的类库API设计的非常优秀,再加上文档docs.com写的非常优秀,写代码给人一种十分优雅的感觉. 获得当 ...

  2. 【Java面试】Kafka 怎么避免重复消费

    Hi,大家好,我是Mic 一个工作5年的粉丝找到我. 他说: "Mic老师,你要是能回答出这个问题,我就佩服你" 我当场就懵了,现在打赌都这么随意了吗? 我问他问题是什么,他说&q ...

  3. RPA应用场景-自动轮询汇总报表

    场景概述 自动轮询汇总报表 所涉系统名称 券商披露网站 人工操作(时间/次) 36小时 所涉人工数量 1 操作频率 每月 场景流程 1.每月初机器人自动登录网站轮询36家券商披露的财务报告,并下载 2 ...

  4. UiPath条件判断活动Flow Decision的介绍与使用

    一.Flow Decision介绍 FlowDecision节点是一个条件节点,它根据指定条件是否成立来控制流程的两个分支. 当条件为True时,流程执行一个分支 当条件为False时,流程执行另外一 ...

  5. 人人都能学会的 Python 多线程指南~

    大家好鸭!有没有想我~(https://jq.qq.com/?_wv=1027&k=rX9CWKg4) 在 Python 中,多线程最常见的一个场景就是爬虫,例如这样一个需求,有多个结构一样的 ...

  6. Electron学习(三)之简单交互操作

    写在前面 最近一直在做批量测试工具的开发,打包的exe,执行也是一个黑乎乎的dos窗口,真的丑死了,总感觉没个界面,体验不好,所以就想尝试写桌面应用程序. 在技术选型时,Java窗体实现使用JavaF ...

  7. NC50528 滑动窗口

    NC50528 滑动窗口 题目 题目描述 给一个长度为N的数组,一个长为K的滑动窗体从最左端移至最右端,你只能看到窗口中的K个数,每次窗体向右移动一位,如下图: 你的任务是找出窗体在各个位置时的最大值 ...

  8. 关于 k 进制线性基

    本质还是高斯消元,使其成为上三角矩阵.但是 \(k\) 不一定是质数. 但我们不需要保证已有数字不改变,只要维护的是一个上三角矩阵就行.所以我们可以利用更相减损让其中一个向量的最高位 \(= 0\) ...

  9. windows10 使用elasticsearch和kibana

    https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.6.2-windows-x86_64.zip https:// ...

  10. (一)java基础篇---第一个程序

    先认识java的基础知识 1.变量命名规则 :1)变量名由数字字母下划线组成,2)不能使用java的关键字,比如public这种,3)遵循小驼峰命名法 2.数据类型 2.1基本数据类型有8种 其中分为 ...