让我来详细讲解一下“C#实现PDF签名时添加时间戳的2种方法(附VB.NET代码)”这篇文章的完整攻略。
一、背景
在使用C#代码实现PDF文件的数字签名时,如果需要添加时间戳的话,可以使用以下两种方法:基于PDF签名规范(PDF Signature Appearances)和基于PDF变量(PDF Variables)。两种方法均需使用第三方的时间戳服务器,比如Entrust TSA、GlobalSign TSA等。
二、基于PDF签名规范(PDF Signature Appearances)
- 添加签名域
首先,需要在PDF文档中添加签名域。此处来看一下如何使用C#代码向PDF文档添加签名域:
using System;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
class Program
{
static void Main()
{
string inputFilePath = "input.pdf";
string outputFilePath = "output.pdf";
string fieldName = "sigField";
using (PdfReader reader = new PdfReader(inputFilePath))
using (FileStream stream = new FileStream(outputFilePath, FileMode.Create, FileAccess.Write))
using (PdfStamper stamper = PdfStamper.CreateSignature(reader, stream, '\0'))
{
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.SetVisibleSignature(new Rectangle(36, 748, 144, 780), reader.NumberOfPages, fieldName);
appearance.CertificationLevel = PdfSignatureAppearance.CERTIFIED_FORM_FILLING_AND_SIGNING;
appearance.Reason = "I certify that the information provided above is true and correct.";
appearance.Location = "New York, NY";
appearance.Contact = "signature@example.com";
appearance.Layer2Text = "Digitally signed by John Doe.\nDate: {0}\nTS: {1}";
appearance.Acro6Layers = true;
appearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.DESCRIPTION;
stamper.Close();
}
}
}
上述代码中,添加签名域的核心代码是:
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.SetVisibleSignature(new Rectangle(36, 748, 144, 780), reader.NumberOfPages, fieldName);
该代码指定了签名域的位置和大小,并指定了签名域的名称。其他的代码指定了签名的一些属性,比如“证明级别”、“原因”、“地点”、“联系人”等等。
- 签名操作
接下来,需要进行PDF签名操作。签名过程中需要添加时间戳。我们可以使用PDFBox提供的方法来获取时间戳:
using Org.BouncyCastle.Asn1.Ocsp;
using Org.BouncyCastle.Asn1.Tsp;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Tsp;
using Org.BouncyCastle.X509;
using System;
using System.IO;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Xml;
class Program
{
static void Main()
{
string inputFilePath = "input.pdf";
string outputFilePath = "output.pdf";
string certFilePath = "cert.pfx";
string certPassword = "password";
string tsaURL = "http://timestamp.globalsign.com/scripts/timstamp.dll";
string fieldName = "sigField";
using (PdfReader reader = new PdfReader(inputFilePath))
using (FileStream stream = new FileStream(outputFilePath, FileMode.Create, FileAccess.Write))
using (PdfStamper stamper = PdfStamper.CreateSignature(reader, stream, '\0'))
{
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
// 更改代码:添加时间戳
byte[] contentHash = SHA256.Create().ComputeHash(reader.GetPageContent(reader.NumberOfPages));
TimeStampToken timeStampToken = GetTimeStampToken(tsaURL, contentHash);
byte[] timestampBytes = timeStampToken.GetEncoded();
PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
dic.Date = new PdfDate(appearance.SignDate);
dic.Name = "Digital Signature";
dic.Reason = "I certify that the information provided above is true and correct.";
dic.Location = "New York, NY";
dic.Contact = "signature@example.com";
dic.Layer2Text = "Digitally signed by John Doe.\nDate: {0}\nTS: {1}";
dic.Bytes = new PdfStream(timestampBytes);
MakeSignature.SignDetached(appearance, new IExternalSignature[] { new X509Certificate2Signature(GetCertificate(certFilePath, certPassword), "SHA-256") }, new[] { dic }, null, null, null, 0, CryptoStandard.CMS);
stamper.Close();
}
}
static TimeStampToken GetTimeStampToken(string url, byte[] contentHash)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.ContentType = "application/timestamp-query";
request.ContentLength = contentHash.Length;
Stream stream = request.GetRequestStream();
stream.Write(contentHash, 0, contentHash.Length);
stream.Close();
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream responseStream = response.GetResponseStream();
TimeStampResponse timeStampResponse = new TimeStampResponse(responseStream);
timeStampResponse.Validate(new SignerInformationVerifier(new JcaContentVerifierProviderBuilder().Build(GetCertificate(timeStampResponse, ResponseType.TimeStampToken).PublicKey)));
return timeStampResponse.TimeStampToken;
}
static X509Certificate2 GetCertificate(string filename, string password)
{
return new X509Certificate2(filename, password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet);
}
static X509Certificate2 GetCertificate(TimeStampResponse response, ResponseType responseType)
{
if (response.Status != 0)
{
throw new Exception("TimeStampServer responded with an error : " + response.Status);
}
Asn1Sequence sequence = (Asn1Sequence)response.TimeStampToken.GetSignedContent().ToAsn1Object();
ContentInfo contentInfo = new ContentInfo(sequence);
if (responseType == ResponseType.IndividualData)
{
return new X509Certificate2(contentInfo.Content.GetOctets());
}
else if (responseType == ResponseType.TimeStampToken)
{
return new X509Certificate2(response.TimeStampToken.GetCertificates()[0].GetEncoded());
}
else
{
return null;
}
}
enum ResponseType
{
Unknown,
IndividualData,
TimeStampToken
}
}
上述代码中,“更改代码”部分指定了获取时间戳的流程,包括向时间戳服务器发送请求、获取响应、解析响应等等。而添加时间戳的核心代码是:
PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
dic.Bytes = new PdfStream(timestampBytes);
MakeSignature.SignDetached(appearance, new IExternalSignature[] { new X509Certificate2Signature(GetCertificate(certFilePath, certPassword), "SHA-256") }, new[] { dic }, null, null, null, 0, CryptoStandard.CMS);
其中,PdfSignature
类表示数字签名,并将数字签名的字节数组设置为时间戳字节数组。MakeSignature.SignDetached
方法用于进行PDF签名操作,其中包含一个签名字典数组,数组中的一个元素就是我们前面创建的数字签名。通过数字签名中包含的时间戳信息,PDF阅读器就能够确认该PDF文档的签名时间。
三、基于PDF变量(PDF Variables)
- 添加变量
首先,需要在PDF文档中添加变量。此处来看一下如何使用C#代码向PDF文档添加变量:
using System.Collections.Generic;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
class Program
{
static void Main()
{
string inputFilePath = "input.pdf";
string outputFilePath = "output.pdf";
string fieldName = "sigField";
using (PdfReader reader = new PdfReader(inputFilePath))
using (FileStream stream = new FileStream(outputFilePath, FileMode.Create, FileAccess.Write))
using (PdfStamper stamper = new PdfStamper(reader, stream))
{
stamper.AcroFields.SetField(fieldName, "Digitally signed by John Doe.\nDate: {0}\nTS: {1}");
stamper.AcroFields.SetFieldProperty(fieldName, "textfont", BaseFont.HELVETICA_BOLD, null);
stamper.AcroFields.SetFieldProperty(fieldName, "textsize", 12f, null);
stamper.Close();
}
}
}
上述代码中,添加变量的核心代码是:
stamper.AcroFields.SetField(fieldName, "Digitally signed by John Doe.\nDate: {0}\nTS: {1}");
stamper.AcroFields.SetFieldProperty(fieldName, "textfont", BaseFont.HELVETICA_BOLD, null);
stamper.AcroFields.SetFieldProperty(fieldName, "textsize", 12f, null);
该代码指定了变量的名称和内容,并设置了变量的字体、大小等。
- 签名操作
接下来,需要进行PDF签名操作。签名过程中需要添加时间戳。我们可以直接在签名过程中添加变量值,从而将时间戳信息添加到PDF中:
using Org.BouncyCastle.Asn1.Ocsp;
using Org.BouncyCastle.Asn1.Tsp;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Tsp;
using Org.BouncyCastle.X509;
using System;
using System.IO;
using System.Net;
using System.Security.Cryptography.X509Certificates;
class Program
{
static void Main()
{
string inputFilePath = "input.pdf";
string outputFilePath = "output.pdf";
string certFilePath = "cert.pfx";
string certPassword = "password";
string tsaURL = "http://timestamp.globalsign.com/scripts/timstamp.dll";
string fieldName = "sigField";
using (PdfReader reader = new PdfReader(inputFilePath))
using (FileStream stream = new FileStream(outputFilePath, FileMode.Create, FileAccess.Write))
using (PdfStamper stamper = PdfStamper.CreateSignature(reader, stream, '\0'))
{
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
// 更改代码:添加时间戳
byte[] contentHash = SHA256.Create().ComputeHash(reader.GetPageContent(reader.NumberOfPages));
TimeStampToken timeStampToken = GetTimeStampToken(tsaURL, contentHash);
string time = timeStampToken.TimeStampInfo.GenTime.ToString("yyyy-MM-dd HH:mm:ss");
string tsa = timeStampToken.TimeStampInfo.Tsa.ToString();
stamper.AcroFields.SetField(fieldName, string.Format("Digitally signed by John Doe.\nDate: {0}\nTS: {1}", time, tsa));
MakeSignature.SignDetached(appearance, new IExternalSignature[] { new X509Certificate2Signature(GetCertificate(certFilePath, certPassword), "SHA-256") }, null, null, null, null, 0, CryptoStandard.CMS);
stamper.Close();
}
}
static TimeStampToken GetTimeStampToken(string url, byte[] contentHash)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.ContentType = "application/timestamp-query";
request.ContentLength = contentHash.Length;
Stream stream = request.GetRequestStream();
stream.Write(contentHash, 0, contentHash.Length);
stream.Close();
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream responseStream = response.GetResponseStream();
TimeStampResponse timeStampResponse = new TimeStampResponse(responseStream);
timeStampResponse.Validate(new SignerInformationVerifier(new JcaContentVerifierProviderBuilder().Build(GetCertificate(timeStampResponse, ResponseType.TimeStampToken).PublicKey)));
return timeStampResponse.TimeStampToken;
}
static X509Certificate2 GetCertificate(string filename, string password)
{
return new X509Certificate2(filename, password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet);
}
static X509Certificate2 GetCertificate(TimeStampResponse response, ResponseType responseType)
{
if (response.Status != 0)
{
throw new Exception("TimeStampServer responded with an error : " + response.Status);
}
Asn1Sequence sequence = (Asn1Sequence)response.TimeStampToken.GetSignedContent().ToAsn1Object();
ContentInfo contentInfo = new ContentInfo(sequence);
if (responseType == ResponseType.IndividualData)
{
return new X509Certificate2(contentInfo.Content.GetOctets());
}
else if (responseType == ResponseType.TimeStampToken)
{
return new X509Certificate2(response.TimeStampToken.GetCertificates()[0].GetEncoded());
}
else
{
return null;
}
}
enum ResponseType
{
Unknown,
IndividualData,
TimeStampToken
}
}
上述代码中,“更改代码”部分指定了获取时间戳的流程,包括向时间戳服务器发送请求、获取响应、解析响应等等。添加变量的核心代码是:
stamper.AcroFields.SetField(fieldName, string.Format("Digitally signed by John Doe.\nDate: {0}\nTS: {1}", time, tsa));
其中,time
和tsa
分别表示签名时间和时间戳服务器地址。通过将时间戳信息添加到PDF文档中,我们就可以在PDF文档中标识出签名的时间。
至此,我们已经介绍了基于PDF签名规范(PDF Signature Appearances)和基于PDF变量(PDF Variables)两种方法,来实现添加时间戳的PDF签名操作。具体实现可以参考本文的示例代码。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C#实现PDF签名时添加时间戳的2种方法(附VB.NET代码) - Python技术站