iTextSharp - 如何获取用于签名的PDF内容,然后在以后签名

时间:2016-09-13 15:01:57

标签: c# itext

我正在开发一个客户端 - 服务器应用程序,客户端必须使用签名对PDF文档进行签名并将其上传到服务器。由于客户无法将签名嵌入到PDF中,因此他们只能读取原始字节并以原始字节的形式生成签名,这使得任务变得复杂。

我正在尝试实施以下工作流程:

  1. 客户端将未签名的PDF上载到服务器
  2. 服务器打开PDF,提取客户端需要签名的字节,并将这些字节发回
  3. 客户端接收这些字节,使用客户端证书对其进行签名并将签名发送到服务器
  4. 服务器将收到的签名嵌入到之前收到的PDF中。
  5. 我找到了一些提取字节的代码示例,用于签名并将签名字节嵌入PDF(this is the main sample I'm using)。

    问题是此示例执行一个程序中的所有步骤,它在获取文档哈希后立即嵌入签名而不关闭PdfStamper。我需要的是在添加签名字段并获取sha.Hash之后保存文档的某种方式,然后在稍后的某个时间(当服务器收到计算的签名时)打开文档并将签名值嵌入到PDF中。

    您能否建议一种修改此代码的方法,以便步骤(2)和(4)可以是独立的,而不需要PdfReaderPdfStamper的共享实例?

2 个答案:

答案 0 :(得分:5)

自己想出来。 This piece of code向我指出了正确的方向。

原来服务器上的进程必须如下:

  • 获取未签名的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)
    {
    }
}

旁注:在所有这些iText示例中困扰我的是魔术数字(如8192此处)的存在,没有任何评论。这使得使用这个库变得更加困难和烦人。

答案 1 :(得分:2)

以下答案取自我们的white paper数字签名,第4章,第4.3.3节使用客户端上创建的签名在服务器上签名文档。代码示例here

所需的工作流程可视为3个主要步骤:

  1. <强> Presign:
    必填:pdf,证书链
    Serverside,设置签名基础设施,提取消息摘要并将摘要作为字节数组发送到客户端

  2. <强>签名:
    必需:消息摘要为字节数组,私钥
    客户端,将加密算法应用于消息摘要,以从散列生成签名摘要并将此签名发送到         服务器

  3. Postsign: 必需:签名摘要为字节数组,pdf Serverside 将签名的摘要插入准备好的签名中,插入 签名到pdf文件
  4. 代码示例,iText5和C#:

    预设(服务器)

    //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代码转换而且我目前没有客户端 - 服务器设置来测试它们,因此复制和运行需要您自担风险。 / p>