C#使用Spire.Pdf包对PDF文档进行数字签名
背景
- 对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