簡體   English   中英

iTextSharp - 如何獲取用於簽名的PDF內容,然后在以后簽名

[英]iTextSharp - How to get PDF content for signing and then sign at a later time

我正在開發一個客戶端 - 服務器應用程序,客戶端必須使用簽名對PDF文檔進行簽名並將其上載到服務器。 由於客戶端無法將簽名嵌入到PDF中,因此它們只能讀取原始字節並以原始字節的形式生成簽名,這使得任務變得復雜。

我正在嘗試實現以下工作流程:

  1. 客戶端將未簽名的PDF上載到服務器
  2. 服務器打開PDF,提取客戶端需要簽名的字節,然后將這些字節發回
  3. 客戶端接收這些字節,使用客戶端證書對其進行簽名,並將簽名發送到服務器
  4. 服務器將收到的簽名嵌入到之前收到的PDF中。

我發現了一些提取字節的代碼示例,用於簽名並將簽名字節嵌入到PDF中( 這是我正在使用的主要示例 )。

問題是這個示例執行一個程序中的所有步驟,它在獲取文檔哈希后立即嵌入簽名而不關閉PdfStamper 我需要的是在添加簽名字段並獲取sha.Hash之后保存文檔的某種方式,然后在稍后的某個時間(當服務器收到計算的簽名時)打開文檔並將簽名值嵌入到PDF中。

您能否建議一種修改此代碼的方法,以便步驟(2)和(4)可以獨立,並且不需要PdfReaderPdfStamper共享實例?

自己搞清楚了。 這段代碼向我指出了正確的方向。

原來服務器上的進程必須如下:

  • 獲取未簽名的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這里),沒有任何評論。 這使得使用這個庫變得更加困難和煩人。

下面的答案來自我們關於數字簽名的白皮書 ,第4章,第4.3.3節使用在客戶端上創建的簽名在服務器上簽名文檔。 代碼示例在這里

所需的工作流程可視為3個主要步驟:

  1. Presign:
    必填:pdf,證書鏈
    Serverside,設置簽名基礎結構,提取消息摘要並將摘要作為字節數組發送到客戶端

  2. 簽名:
    必需:消息摘要為字節數組,私鑰
    客戶端,將加密算法應用於消息摘要,以從散列生成簽名摘要,並將此簽名發送到服務器

  3. Postsign:必需:簽名摘要為字節數組,pdf Serverside將簽名摘要插入准備好的簽名中,將簽名插入pdf文檔

代碼示例,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();

郵政署服務員

// 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代碼轉換而且我目前沒有客戶端 - 服務器設置來測試它們,因此復制和運行需要您自擔風險。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM