如何获得PDF内容的签名,然后在以后的时间签署
本文关键字:然后 签署 时间 PDF 何获得 | 更新日期: 2023-09-27 18:06:54
我正在开发一个客户机-服务器应用程序,其中客户机必须使用其签名对PDF文档进行签名并将其上传到服务器。由于客户端无法将签名嵌入到pdf中,因此任务变得复杂,它们只能读取原始字节并以原始字节的形式生成签名。
我正在尝试实现以下工作流程:
- 客户端上传未签名PDF到服务器
- 服务器打开PDF,提取客户端需要签名的字节,并将这些字节发送回
- 客户端接收这些字节,使用客户端证书对它们进行签名,并将签名发送给服务器
- 服务器将收到的签名嵌入到之前收到的PDF中。
我发现了一些代码示例,用于提取要签名的字节并将签名字节嵌入PDF(这是我使用的主要示例)。
问题是这个示例在一个程序中执行所有步骤,它在获得文档散列后立即嵌入签名,而不关闭PdfStamper
。我需要的是在添加签名字段并获得sha.Hash
后保存文档的某种方法,然后在稍后的时间(当服务器接收计算签名时)打开文档并将签名值嵌入PDF中。
你能建议一种方法来修改这段代码,使步骤(2)和(4)可以独立,而不需要PdfReader
和PdfStamper
的共享实例吗?
我自己想出来的。这段代码为我指明了正确的方向。
结果显示服务器上的进程必须如下所示:
- 取未签名PDF并添加一个空签名字段
- 根据修改后的文件 计算需要签名的字节数
- 将修改后的PDF以空签名保存到临时文件
- 发送计算字节到客户端
- 当客户端响应签名时,打开临时文件并将签名插入先前创建的字段
相关服务器代码:
public static byte[] GetBytesToSign(string unsignedPdf, string tempPdf, string signatureFieldName)
{
using (PdfReader reader = new PdfReader(unsignedPdf))
{
using (FileStream os = File.OpenWrite(tempPdf))
{
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, ''0');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.SetVisibleSignature(new Rectangle(36, 748, 144, 780), 1, signatureFieldName);
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKMS, PdfName.ADBE_PKCS7_SHA1);
MakeSignature.SignExternalContainer(appearance, external, 8192);
return SHA1Managed.Create().ComputeHash(appearance.GetRangeStream());
}
}
}
public static void EmbedSignature(string tempPdf, string signedPdf, string signatureFieldName, byte[] signedBytes)
{
using (PdfReader reader = new PdfReader(tempPdf))
{
using (FileStream os = File.OpenWrite(signedPdf))
{
IExternalSignatureContainer external = new MyExternalSignatureContainer(signedBytes);
MakeSignature.SignDeferred(reader, signatureFieldName, os, external);
}
}
}
private class MyExternalSignatureContainer : IExternalSignatureContainer
{
private readonly byte[] signedBytes;
public MyExternalSignatureContainer(byte[] signedBytes)
{
this.signedBytes = signedBytes;
}
public byte[] Sign(Stream data)
{
return signedBytes;
}
public void ModifySigningDictionary(PdfDictionary signDic)
{
}
}
旁注:在所有这些ittext示例中,困扰我的是没有任何注释的魔术数字(如8192
)的存在。
下面的答案摘自我们关于数字签名的白皮书,第4章,第4.3.3节,使用在客户端创建的签名在服务器端签署文档。这里的代码示例
期望的工作流可以看作是3个主要步骤:
-
Presign:
要求:pdf,证书链
服务器端,设置签名基础设施,提取消息摘要并将摘要作为字节数组发送给客户端 签署 :
要求:消息摘要为字节数组,私钥
客户端,将加密算法应用于消息摘要,以从散列生成签名摘要,并将此签名发送给服务器- Postsign: 必需:签名摘要作为字节数组,pdf服务器端将签名摘要插入准备好的签名中,插入签名到pdf-document
代码示例,iText5和c#:
Presign (服务器)
//hello :
//location of the pdf on the server
//or
//bytestream variable with teh pdf loaded in
//chain: certificate chain
// we create a reader and a stamper
PdfReader reader = new PdfReader(hello);
Stream baos = new MemoryStream();
PdfStamper stamper = PdfStamper.CreateSignature(reader, baos., ''0');
// we create the signature appearance
PdfSignatureAppearance sap = stamper.SignatureAppearance;
sap.Reason = "Test";
sap.Location = "On a server!";
sap.SetVisibleSignature ( new Rectangle(36, 748, 144, 780), 1, "sig");
sap.Certificate = chain[0];
// we create the signature infrastructure
PdfSignature dic = new PdfSignature(
PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
dic.Reason = sap.Reason;
dic.Location = sap.Location;
dic.Contact = sap.Contact;
dic.Date = new PdfDate(sap.SignDate);
sap.CryptoDictionary = dic;
Dictionary<PdfName, int> exc = new Dictionary<PdfName, int>();
exc.Add(PdfName.CONTENTS, (int)(8192 * 2 + 2));
sap.PreClose(exc);
PdfPKCS7 sgn = new PdfPKCS7(null, chain, "SHA256", false);
//Extract the bytes that need to be signed
Stream data = sap.GetRangeStream();
byte[] hash = DigestAlgorithms.Digest(data,"SHA256");
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash,null, null, CryptoStandard.CMS);
//Store sgn, hash,sap and baos on the server
//...
//Send sh to client
签署(客户端)
// we receive a hash that needs to be signed
Stream istream = response.GetResponseStream();
MemoryStream baos = new MemoryStream();
data = new byte[0x100];
while ((read = istream.Read(data, 0, data.Length)) != 0)
baos.Write(data, 0, read);
istream.Close();
byte[] hash = baos.ToArray();
// we load our private key from the key store
Pkcs12Store store = new Pkcs12Store(new FileStream(KEYSTORE, FileMode.Open), PASSWORD);
String alias = "";
// searching for private key
foreach (string al in store.Aliases)
if (store.IsKeyEntry(al) && store.GetKey(al).Key.IsPrivate) {
alias = al;
break;
}
AsymmetricKeyEntry pk = store.GetKey(alias);
// we sign the hash received from the server
ISigner sig = SignerUtilities.GetSigner("SHA256withRSA");
sig.Init(true, pk.Key);
sig.BlockUpdate(hash, 0, hash.Length);
data = sig.GenerateSignature();
// we make a connection to the PostSign Servlet
request = (HttpWebRequest)WebRequest.Create(POST);
request.Headers.Add(HttpRequestHeader.Cookie,cookies.Split(";".ToCharArray(), 2)[0]);
request.Method = "POST";
// we upload the signed bytes
os = request.GetRequestStream();
os.Write(data, 0, data.Length);
os.Flush();
os.Close();
Postsign (服务器)
// we read the signed bytes
MemoryStream baos = new MemoryStream();
Stream InputStream iStream = req.GetInputStream();
int read;
byte[] data = new byte[256];
while ((read = iStream.read(data, 0, data.Length)) != -1) {
baos.Write(data, 0, read);
}
// we complete the PDF signing process
sgn.SetExternalDigest(baos.ToArray(), null, "RSA");
byte[] encodedSig = sgn.getEncodedPKCS7(hash, cal, null,
null, null, CryptoStandard.CMS);
byte[] paddedSig = new byte[8192];
paddedSig.
encodedSig.CopyTo(paddedSig, 0);
PdfDictionary dic2 = new PdfDictionary();
dic2.Put(PdfName.CONTENTS, new PdfString(paddedSig).SetHexWriting(true));
try
{
sap.close(dic2);
}
catch (DocumentException e)
{
throw new IOException(e);
}
我省略了大部分客户机-服务器通信代码,只关注签名逻辑。我也没有对这些代码段进行彻底的测试,因为我必须将它们从java代码转换过来,而且我目前没有客户机-服务器设置来测试它们,所以复制并运行这些代码段要自担风险。