数字PDF文档签名

时间:2019-10-21 12:37:08

标签: pdf itext sign itext7 digital

更新2:

我已经在https://1drv.ms/u/s!Al69FgQ8jwmZbgiBMXLLM4j5sbU?e=vyGF4m上上传了示例

可以请您检查一下。我被困在最后一步。但是,请确认其他方法是否正确。

更新1:

我已确认流程。所以我很清楚。

作为该数字签名PDF文档流程的一部分,我们希望使用第三方提供PDF的签名哈希。 步骤如下:

  1. 有第三方内部系统,可以从单词生成PDF文档。
  2. 该PDF将被发送到另一个服务,该服务将生成该PDF的哈希值
  3. 该哈希值将被发送到外部服务,以使用私钥唱歌哈希。
  4. 外部系统将发送签名的哈希和公钥证书,内部服务将使用这些证书和公钥证书在PDF文档中添加签名。

我有以下问题。

  1. 内部服务上方的第1点是创建PDF和签名块。是否需要创建签名块?因为这是延迟签名?
  2. 如果是,则第2点中的服务如何能够获取PDF文档的原始内容以生成哈希。

我们使用了具有签名的现有PDF,并使用iText 7获取原始内容。 这种方法正确吗? FormB.PDF具有签名,并且通过删除signaure1字段,我们得到的是原始内容。这个过程会行得通吗?

我们也尝试使用pdfsigner.getRangeStream()方法,但是在文档中并不清楚。请帮助

package com.abc.sd;

import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.List;

import com.itextpdf.forms.PdfAcroForm;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.signatures.SignatureUtil;

public class ItextPdf7 {

    public static void main(String [] args) throws IOException, NoSuchAlgorithmException {
        String filePath ="C:\\\\abc\\\\test\\\\FormB.pdf";
        PdfReader reader = new PdfReader(filePath);
        PdfDocument pdfDoc = new PdfDocument(reader);
        PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, false);
        SignatureUtil signUtil = new SignatureUtil(pdfDoc);
        List<String> names = signUtil.getSignatureNames();
        System.out.println("Signature Name>>>"+names);
      //  System.out.println("Singature Data>>"+signUtil.readSignatureData("Signature1"));


        PdfReader reader1 = new PdfReader(filePath);
        PdfDocument pdfDoc1 = new PdfDocument(reader1, new PdfWriter("C:\\\\\\\\abc\\\\\\\\test\\\\\\\\unsigned_latest_iext7.pdf"));
        PdfAcroForm form1 = PdfAcroForm.getAcroForm(pdfDoc1, true);
        form1.flattenFields();
        pdfDoc1.close();


    }

}

******************************

我们希望签署PDF文档。根据我的理解,这是一些步骤。

  1. 消费者会将PDF文档的摘要发送到中央系统。       PDF的摘要将排除签名部分

  2. 中央系统会将摘要(使用消费者的私钥/公钥签名?不确定)发送给消费者

  3. 消费者系统将在PDF文档的签名部分中添加摘要(可能与公钥一起使用)

能帮忙吗?

  1. 以上流程对我的理解是否正确?任何小的参考指南/链接都会对您有所帮助或提供任何流程图。

  2. 使用.Net和Java,可以完成这项工作的库是什么?都是开源的,还是付费的。 iTextSharp在这里有用吗?

  3. 如果客户打开PDF,如何进行验证?是否需要采取任何具体措施来签署文件?

请帮助。

1 个答案:

答案 0 :(得分:1)

在这里,问题文本和下面的注释中都有很多方面和子问题。这个答案很有启发性,但是其中一些是在首先介绍一些背景之后提出的。

一些背景

集成的PDF签名意味着PDF中存在许多结构:

  • 签名AcroForm表单字段。该表单字段可以具有窗口小部件注释(可视化内容可以包含您希望放入的任何信息),但不必具有该注释。

  • 此签名表单字段中的值。与其他表单字段不同,签名字段的值不是单纯的字符串,而是键值对的字典。内容根据签名的确切类型而有所不同。但是,在互操作类型的情况下,总是有一个 Contents 条目,其值是一个二进制字符串,其中包含实际的PKCS1 / PKCS7 / CMS / RFC3161签名或时间戳,该时间戳或时间戳覆盖了整个文件,但该二进制文件除外字符串。

    PDF signature sketch

    (该草图有点误导性:十六进制字符串定界符'<'和'>'是签名数据的 not 部分。)

  • 对于类型为 adbe.x509.rsa_sha1 的情况,内容条目包含PKCS1签名。签名值字典还必须包含一个包含签名证书的 Cert 条目。

  • 如果类型为 ETSI.RFC3161 ,则 Contents 条目将包含RFC 3161时间戳记令牌。

  • 对于类型为 ETSI.CAdES.detached adbe.pkcs7.detached adbe.pkcs7.sha1 的类型内容条目包含CMS签名容器。由于签名容器可以保存签名证书,因此不需要证书条目。

    CMS签名容器可以包含“签名属性”的结构。如果是这样,则这些属性之一必须是签名的PDF字节的哈希值(请参见上文, Contents 以外的所有内容)以及包装在容器中的实际签名字节对这些签名的属性进行签名。是否允许不带签名属性的变体以及另外需要哪些属性,取决于签名的确切类型。

  • 对于 ETSI.CAdES.detached ,CMS容器必须包含签名的属性。此外,已签名属性之一必须是引用签名者证书的ESS签名证书或signing-certificate-v2属性。

    在这种情况下,LTV信息可以在以后以增量更新的方式添加到PDF中,而不必出现在已签名的PDF中。

  • 对于 adbe.pkcs7.detached adbe.pkcs7.sha1 ,通常不需要签名属性。但是,根据确切的签名策略(由法律或合同规定),仍然可能需要签名属性,尤其是ESS签名证书的签名属性。

    这些签名类型已在ISO 32000-1中定义。如果一个人的签名策略仅基于ISO 32000-1,则LTV信息必须存储在adbe-revocationInfoArchival属性中,该属性必须是一个签名属性。

签名之前需要签名证书吗?

在注释中,您引用了iText“ PDF和数字签名”电子书,该电子书似乎表明它足以与签名一起检索签名证书。

不过,根据上述背景,我们意识到

  • 对于 adbe.x509.rsa_sha1 签名,签名证书必须位于签名值字典的 Cert 条目中。由于该条目不在 Contents 条目中,因此该证书是已签名数据的一部分。因此,必须在签名之前 知道。

  • 对于 ETSI.CAdES.detached 签名,签名属性必须包含ESS签名证书或signing-certificate-v2属性。此属性引用签名者证书。因此,必须在签名之前 知道。

  • 对于 adbe.pkcs7.detached adbe.pkcs7.sha1 ,这取决于必须遵守的实际签名策略。 ESS signing-certificate或signing-certificate-v2属性是必需的。因此,这取决于在签名之前是否需要知道签名证书。

    但是,如果仅基于ISO 32000-1进行签名,则LTV信息必须存储在完全签名的属性中,并且要检索LTV信息,很显然,人们需要知道一个试图对其进行认证的证书。检索它们,特别是签名者证书。

因此,为回答本主题标题中的问题,因此:仅在宽松的签名策略的情况下,只要不需要添加LTV信息,您就可以在不知道签名者身份的情况下逃脱签名。

如果有PAdES签名?

在评论中,您提到您需要使用PAdES和LTV 。这是否意味着您在签名之前需要签名者证书?

嗯,这取决于。

如果使用PAdES 意味着使用PAdES基线配置文件或扩展的PAdES配置文件(BES / EPES),则必须创建 ETSI.CAdES.detached 签名。因此,您在签名之前确实需要签名者证书。

但是,如果只需要PAdES配置文件来进行PDF中的CMS数字签名(本质上是ISO 32000-1兼容性配置文件),则不需要签名者证书。

但是此配置文件特别暗示:如果存在,则任何吊销信息都应是PDF签名的签名属性。因此,对于“ PAdES和LTV”,您再次需要签名之前的签名者证书。

如何在不及早知道签名者证书的情况下创建PDF签名

因此,在某些设置中,在计算实际签名之前不需要签署者证书。通常,尽管如此,安全API仍然需要尽早提供证书。

使用Bouncy Castle低级API,您可以执行以下操作。 (我假设您正在使用SHA256withRSA。)

首先准备PDF并确定哈希值

byte[] Hash = null;

using (PdfReader reader = new PdfReader("original.pdf"))
using (FileStream fout = new FileStream("prepared.pdf", FileMode.Create))
{
    StampingProperties sp = new StampingProperties();
    sp.UseAppendMode();

    PdfSigner pdfSigner = new PdfSigner(reader, fout, sp);
    pdfSigner.SetFieldName("Signature");

    PdfSignatureAppearance appearance = pdfSigner.GetSignatureAppearance();
    appearance.SetPageNumber(1);

    int estimatedSize = 12000;
    ExternalHashingSignatureContainer container = new ExternalHashingSignatureContainer(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
    pdfSigner.SignExternalContainer(container, estimatedSize);
    Hash = container.Hash;
}

现在要签名的PDF字节的哈希值位于Hash中。

此处使用的ExternalHashingSignatureContainer类是以下帮助程序类:

public class ExternalHashingSignatureContainer : ExternalBlankSignatureContainer
{
    public ExternalHashingSignatureContainer(PdfName filter, PdfName subFilter) : base(filter, subFilter)
    { }

    public override byte[] Sign(Stream data)
    {
        SHA256 sha = new SHA256CryptoServiceProvider();
        Hash = sha.ComputeHash(data);
        return new byte[0];
    }

    public byte[] Hash { get; private set; }
}

对于上面在Hash变量中计算出的哈希,您现在可以请求PKCS#1签名和签名者证书。然后,您可以按以下方式构造CMS容器:

byte[] signatureBytes = THE_RETRIEVED_SIGNATURE_BYTES;
byte[] certificateBytes = THE_RETRIEVED_CERTIFICATE_BYTES;

X509Certificate x509Certificate = new X509CertificateParser().ReadCertificate(certificateBytes);

SignerIdentifier sid = new SignerIdentifier(new IssuerAndSerialNumber(x509Certificate.IssuerDN, x509Certificate.SerialNumber));
AlgorithmIdentifier digAlgorithm = new AlgorithmIdentifier(NistObjectIdentifiers.IdSha256);
Attributes authenticatedAttributes = null;
AlgorithmIdentifier digEncryptionAlgorithm = new AlgorithmIdentifier(Org.BouncyCastle.Asn1.Pkcs.PkcsObjectIdentifiers.Sha256WithRsaEncryption);
Asn1OctetString encryptedDigest = new DerOctetString(signatureBytes);
Attributes unauthenticatedAttributes = null;
SignerInfo signerInfo = new SignerInfo(sid, digAlgorithm, authenticatedAttributes, digEncryptionAlgorithm, encryptedDigest, unauthenticatedAttributes);

Asn1EncodableVector digestAlgs = new Asn1EncodableVector();
digestAlgs.Add(signerInfo.DigestAlgorithm);
Asn1Set digestAlgorithms = new DerSet(digestAlgs);
ContentInfo contentInfo = new ContentInfo(CmsObjectIdentifiers.Data, null);
Asn1EncodableVector certs = new Asn1EncodableVector();
certs.Add(x509Certificate.CertificateStructure.ToAsn1Object());
Asn1Set certificates = new DerSet(certs);
Asn1EncodableVector signerInfs = new Asn1EncodableVector();
signerInfs.Add(signerInfo);
Asn1Set signerInfos = new DerSet(signerInfs);
SignedData signedData = new SignedData(digestAlgorithms, contentInfo, certificates, null, signerInfos);

contentInfo = new ContentInfo(CmsObjectIdentifiers.SignedData, signedData);

byte[] Signature = contentInfo.GetDerEncoded();

现在CMS签名容器字节位于Signature中。

对于上述内容,请使用这些BouncyCastle using

using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Cms;
using Org.BouncyCastle.Asn1.Nist;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Signers;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.X509;

您现在可以像这样将签名容器字节嵌入到PDF中:

using (PdfReader reader = new PdfReader("prepared.pdf"))
using (PdfDocument document = new PdfDocument(reader))
using (FileStream fout = new FileStream("signed.pdf", FileMode.Create))
{
    PdfSigner.SignDeferred(document, "Signature", fout, new ExternalPrecalculatedSignatureContainer(Signature));
}

此处使用的ExternalPrecalculatedSignatureContainer类是以下帮助程序类:

public class ExternalPrecalculatedSignatureContainer : ExternalBlankSignatureContainer
{
    public ExternalPrecalculatedSignatureContainer(byte[] cms) : base(new PdfDictionary())
    {
        Cms = cms;
    }

    public override byte[] Sign(Stream data)
    {
        return Cms;
    }

    public byte[] Cms { get; private set; }
}

但是,如上所述,此签名容器不是CAdES容器。因此,您的PDF签名将不是真正的PAdES签名(基准或扩展配置文件),但最多只能是ISO 32000-1兼容性的PAdES签名。

基于上述的测试代码中的问题

您的Client方法createSignedData如下所示:

public byte[] createSignedData(byte[] sh)
{
    string dire = Directory.GetParent(Directory.GetParent(Directory.GetCurrentDirectory()).ToString()).ToString();
    string PROPERTIES = dire + "\\resources\\signkey.properties";
    Properties properties = new Properties();
    properties.Load(new FileStream(PROPERTIES, FileMode.Open, FileAccess.Read));
    String path = properties.GetProperty("PRIVATE");
    char[] pass = properties.GetProperty("PASSWORD").ToCharArray();
    string alias = null;
    Pkcs12Store pk12;
    pk12 = new Pkcs12Store(new FileStream(path, FileMode.Open, FileAccess.Read), pass);
    foreach (var a in pk12.Aliases)
    {
        alias = ((string)a);
        if (pk12.IsKeyEntry(alias))
            break;
    }

    ICipherParameters pk = pk12.GetKey(alias).Key;
    IExternalSignature pks = new PrivateKeySignature(pk, DigestAlgorithms.SHA256);
    byte[] data = pks.Sign(sh);
    return data;

}

不幸的是,PrivateKeySignature.Sign希望消息登录sh参数,尤其是首先对其进行哈希处理。另一方面,在您的用例中,sh已经是要签名的消息的哈希。因此,您可以有效地对应该进行哈希处理的位置进行两次哈希处理,而只需一次。

您可以通过替换

来解决此问题
IExternalSignature pks = new PrivateKeySignature(pk, DigestAlgorithms.SHA256);
byte[] data = pks.Sign(sh);

在上面的代码中

StaticDigest digest = new StaticDigest();
digest.AlgorithmName = "SHA-256";
digest.Digest = sh;
RsaDigestSigner signer = new RsaDigestSigner(digest);
signer.Init(true, pk);
byte[] data = signer.GenerateSignature();

StaticDigest是以下帮助程序类:

public class StaticDigest : IDigest
{
    public string AlgorithmName { get; set; }
    public byte[] Digest { get; set; }

    public void BlockUpdate(byte[] input, int inOff, int length)
    { }

    public int DoFinal(byte[] output, int outOff)
    {
        Array.Copy(Digest, 0, output, outOff, Digest.Length);
        return Digest.Length;
    }

    public int GetByteLength()
    {
        return 64;
    }

    public int GetDigestSize()
    {
        return Digest.Length;
    }

    public void Reset()
    { }

    public void Update(byte input)
    { }
}

此更改后,您的测试项目将返回数学上有效的签名。