且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何使用iText7将.p7s字节数组插入PDF?

更新时间:2023-02-08 21:38:22

首先,您不必像以前那样拆分签名过程.我已经看到了很多开发人员想要执行此操作的问题,但严格来说,这是没有必要的(嗯,iText仍然会先创建一个准备好的PDF,然后再注入签名容器,但可以保留它.在幕后).

First of all, you do not have to split the signing process like you do. I've seen a lot of questions in which the developers want to do this, but strictly speaking it is not necessary (well, under the hood iText still will first create a prepared PDF and later inject the signature container, but it can be kept under the hood).

仅当外部签名服务需要很长时间才能创建签名并且您不能将PDF保留在内存中时,才需要拆分过程.

Splitting the process only is necessary if the external signing service takes very long to create the signature and you cannot keep the PDF in memory for that time.

我将在这里研究这两种变体.

I'll look into both variants here.

如果外部签名服务以足够快的速度返回结果(完整的PKCS#7签名容器),则应使用此方法.基本代码与您的类似:

If your external signing service returns the result (a full PKCS#7 signature container) fast enough, you should use this approach. The base code starts similar to yours:

PdfSigner signer = new PdfSigner(new PdfReader("example.pdf"), new FileStream("example-signed.pdf", FileMode.Create), new StampingProperties().UseAppendMode());
signer.SetFieldName("Signature1");

PdfSignatureAppearance sigAppearance = signer.GetSignatureAppearance();
sigAppearance
    .SetPageRect(new Rectangle(144, 144, 200, 100))
    .SetPageNumber(1)
    .SetContact("This is contact")
    .SetReason("This is reason")
    .SetLocation("This is location")
    .SetSignatureCreator("This is signature creator");

ExternalServiceSignatureContainer container = new ExternalServiceSignatureContainer();

signer.SignExternalContainer(container, 8192);

与您的代码的不同之处在于 IExternalSignatureContainer 实现:

The difference to your code is in the IExternalSignatureContainer implementation:

public class ExternalServiceSignatureContainer : IExternalSignatureContainer
{
    public void ModifySigningDictionary(PdfDictionary signDic)
    {
        signDic.Put(PdfName.Filter, PdfName.Adobe_PPKLite);
        signDic.Put(PdfName.SubFilter, PdfName.Adbe_pkcs7_detached);
    }

    public byte[] Sign(Stream data)
    {
        // Call your external signing service to create a CMS signature container
        // for the data in the InputStream and return that signature container

        [... see below ...]
    }
}

根据您的API访问该外部签名服务, Sign 的实现有所不同.在每种情况下,我都假定API_CALL以字节数组的形式返回结果PKCS#7签名容器:

Depending on your API to access that external signing service the implementation of Sign differs. In each case I assume the API_CALL to return the result PKCS#7 signature container as byte array:

  • 您也许可以直接在流中调用它

  • You may either be able to directly call it with the stream

return YOUR_SIGNING_API_CALL_FOR_STREAM(data);

  • 或带有从流内容生成的byte []

  • or with a byte[] generated from the stream contents

    return YOUR_SIGNING_API_CALL_FOR_ARRAY(StreamUtil.InputStreamToArray(data));
    

    作为参数

    ,否则您可能首先必须自己对数据进行哈希处理(例如,如下所示),然后将哈希发送给服务.

    or you may first have to hash the data yourself (e.g. as follows) and send your hash to the service.

    byte[] hash = DigestAlgorithms.Digest(data, DigestAlgorithms.SHA256);
    return YOUR_SIGNING_API_CALL_FOR_HASH(hash)
    

  • signer 的输出已经是最终的,经过签名的PDF.

    The output of the signer already is the finalized, signed PDF.

    基本上,这是此答案中已经讨论的情况.

    This essentially is the case already discussed in this answer.

    如果您的外部签名服务没有足够快地返回结果(完整的PKCS#7签名容器)(例如,在使用批处理签名API的情况下,或者如果服务需要长时间等待确认的情况),或者在您这边,您可以在调用签名服务之前实现该部分,然后在单独的程序中实现该部分(确实有人这样做),您可以使用这种方法.

    If your external signing service does not return the result (a full PKCS#7 signature container) fast enough (e.g. in case of batch signature APIs or in case of services waiting for some confirmation for a long time if necessary) or if on your side you implement the part before calling the signing service and the part thereafter in separate programs (some people indeed do so), you can use this approach.

    重新开始,基本代码与您的基本代码类似:

    Again the base code starts similar to yours:

    PdfSigner signer = new PdfSigner(new PdfReader("example.pdf"), new FileStream("example-prepared.pdf", FileMode.Create), new StampingProperties().UseAppendMode());
    signer.SetFieldName("Signature1");
    
    PdfSignatureAppearance sigAppearance = signer.GetSignatureAppearance();
    sigAppearance
        .SetPageRect(new Rectangle(144, 144, 200, 100))
        .SetPageNumber(1)
        .SetContact("This is contact")
        .SetReason("This is reason")
        .SetLocation("This is location")
        .SetSignatureCreator("This is signature creator");
    
    ExternalEmptySignatureContainer container = new ExternalEmptySignatureContainer();
    
    signer.SignExternalContainer(container, 8192);
    
    byte[] dataToSign = container.Data;
    

    ExternalEmptySignatureContainer 现在仅提供稍后由签名服务签名的数据,它尚未注入签名容器

    The ExternalEmptySignatureContainer now only provides the data to sign by the signing service later, it does not inject a signature container yet

    public class ExternalEmptySignatureContainer : IExternalSignatureContainer
    {
        public void ModifySigningDictionary(PdfDictionary signDic)
        {
            signDic.Put(PdfName.Filter, PdfName.Adobe_PPKLite);
            signDic.Put(PdfName.SubFilter, PdfName.Adbe_pkcs7_detached);
        }
    
        public byte[] Sign(Stream data)
        {
            // Store the data to sign and return an empty array
    
            [... see below ...]
    
            return new byte[0];
        }
    
        public byte[] Data;
    }
    

    根据您的API来访问该外部签名服务, Sign 的实现有所不同.

    Depending on your API to access that external signing service the implementation of Sign differs.

    • 如果您的签名API需要原始数据进行签名,请使用从流内容生成的byte [].

    • If your signing API expects the original data for signing, use a byte[] generated from the stream contents

    Data = StreamUtil.InputStreamToArray(data);
    

  • 如果您的签名API期望原始数据的哈希值用于签名,请从流内容中像这样计算它

  • If your signing API expects the a hash of the original data for signing, calculate it like this from the stream contents

    Data = DigestAlgorithms.Digest(data, DigestAlgorithms.SHA256);
    

  • 签名者的输出是中间的,准备好的PDF.

    The output of the signer is the intermediary, prepared PDF.

    下一步是调用签名服务并检索PKCS#7签名容器:

    The next step is to call the signing service and to retrieve the PKCS#7 signature container:

    byte[] signature = YOUR_SIGNING_API_CALL(dataToSign);
    

    最后,将签名容器注入准备好的PDF中:

    Finally you inject that signature container into the prepared PDF:

    PdfDocument document = new PdfDocument(new PdfReader("example-prepared.pdf"));
    using (Stream output = new FileStream("example-prepared-signed.pdf", FileMode.Create))
    {
        ExternalInjectingSignatureContainer container2 = new ExternalInjectingSignatureContainer(signature);
    
        PdfSigner.SignDeferred(document, "Signature1", output, container2);
    }
    

    IExternalSignatureContainer 实现仅注入签名字节:

    The IExternalSignatureContainer implementation merely injects the signature bytes:

    public class ExternalInjectingSignatureContainer : IExternalSignatureContainer
    {
        public ExternalInjectingSignatureContainer(byte[] signature)
        {
            Signature = signature;
        }
    
        public void ModifySigningDictionary(PdfDictionary signDic)
        {
        }
    
        public byte[] Sign(Stream data)
        {
            return Signature;
        }
    
        public byte[] Signature;
    }
    

    输出是已完成签名的PDF.

    The output is the finalized, signed PDF.