数字 PDF 文档签名

Alw*_*per 0 pdf sign itext digital itext7

更新2:

\n\n

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

\n\n

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

\n\n

更新1:

\n\n

我已经确认了流程。所以我很清楚这一点。

\n\n

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

\n\n
    \n
  1. 有第 3 方内部系统可以从 Word 生成 PDF 文档。
  2. \n
  3. 该 PDF 将被发送到另一个服务,该服务将生成该 PDF 的哈希值
  4. \n
  5. 该哈希值将发送到外部服务以使用私钥进行哈希计算。
  6. \n
  7. 外部系统将发送签名的哈希值和公钥证书,内部服务将使用该证书在 PDF 文档中添加签名。
  8. \n
\n\n

我有以下问题。

\n\n
    \n
  1. 在上述第 1 点中,内部服务正在创建 PDF 以及签名块。是否需要创建签名块?因为这是延期签署?
  2. \n
  3. 如果是这样,第2点中的服务如何获取PDF文档的原始内容以生成哈希值。
  4. \n
\n\n

我们使用现有的带有签名的 PDF 并使用 iText 7 来获取原始内容。\n这个方法正确吗?\nFormB.PDF 有签名,通过删除signaure1 字段,我们可以获得原始内容。这个过程有效并且可取吗?

\n\n

我们还尝试使用 pdfsigner.getRangeStream() 方法,但它在文档中不太清楚,而且还不清楚。请帮忙

\n\n
package com.abc.sd;\n\nimport java.io.IOException;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.List;\n\nimport com.itextpdf.forms.PdfAcroForm;\nimport com.itextpdf.kernel.pdf.PdfDocument;\nimport com.itextpdf.kernel.pdf.PdfReader;\nimport com.itextpdf.kernel.pdf.PdfWriter;\nimport com.itextpdf.signatures.SignatureUtil;\n\npublic class ItextPdf7 {\n\n    public static void main(String [] args) throws IOException, NoSuchAlgorithmException {\n        String filePath ="C:\\\\\\\\abc\\\\\\\\test\\\\\\\\FormB.pdf";\n        PdfReader reader = new PdfReader(filePath);\n        PdfDocument pdfDoc = new PdfDocument(reader);\n        PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, false);\n        SignatureUtil signUtil = new SignatureUtil(pdfDoc);\n        List<String> names = signUtil.getSignatureNames();\n        System.out.println("Signature Name>>>"+names);\n      //  System.out.println("Singature Data>>"+signUtil.readSignatureData("Signature1"));\n\n\n        PdfReader reader1 = new PdfReader(filePath);\n        PdfDocument pdfDoc1 = new PdfDocument(reader1, new PdfWriter("C:\\\\\\\\\\\\\\\\abc\\\\\\\\\\\\\\\\test\\\\\\\\\\\\\\\\unsigned_latest_iext7.pdf"));\n        PdfAcroForm form1 = PdfAcroForm.getAcroForm(pdfDoc1, true);\n        form1.flattenFields();\n        pdfDoc1.close();\n\n\n    }\n\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

我们正在寻求签署 PDF 文档。根据我的理解,这是步骤。

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

  2. \n
  3. \xc2\xa0 \xc2\xa0 \xc2\xa0 中央系统会将摘要(使用消费者\xe2\x80\x99s 私钥/公钥签名?不确定)发送给消费者

  4. \n
  5. \xc2\xa0 \xc2\xa0 \xc2\xa0 用户系统将在 PDF 文档的签名部分添加摘要(可能与公钥一起??)

  6. \n
\n\n

您能帮忙关注一下吗?

\n\n
    \n
  1. 如果我对上述流程的理解是正确的?任何小的参考指南/链接或任何流程图都会有所帮助。

  2. \n
  3. 对于 .Net 和 Java,有哪些库可以完成这项工作?开源的和付费的。iTextSharp 在这里相关吗?

  4. \n
  5. 如果客户打开 PDF,将如何进行验证?是否有任何具体行动需要文件签署?

  6. \n
\n\n

请帮忙。

\n

mkl*_*mkl 5

这里有很多方面和子问题,无论是在问题文本中还是在其下面的评论中。在首先介绍一些背景之后,这个答案阐明了其中一些。

一些背景

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

  • 签名 AcroForm 表单字段。该表单字段可以有一个小部件注释(一种可视化效果,可以包含您想要放入其中的任何信息),但它不需要有一个。

  • 此签名表单字段中的值。与其他表单字段不同,签名字段的值不仅仅是一个字符串,而是一个键值对的字典。内容因签名的具体类型而异。但是,在可互操作类型的情况下,始终存在一个Contents条目,其值是一个二进制字符串,其中包含实际的 PKCS1/PKCS7/CMS/RFC3161 签名或时间戳,涵盖除此二进制字符串之外的整个文件。

    PDF签名草图

    (该草图有点误导:“<”和“>”十六进制字符串分隔符不是签名数据的一部分。)

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

  • 如果类型为ETSI.RFC3161,则内容条目包含 RFC 3161 时间戳标记。

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

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

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

    在这种情况下,LTV 信息可以稍后在 PDF 的增量更新中添加,它们不需要出现在签名的 PDF 中。

  • 对于adbe.pkcs7.detachedadbe.pkcs7.sha1,通常不需要签名属性。不过,根据具体的签名策略(法律或合同规定),可能仍然需要签名属性,特别是 ESS 签名证书签名属性。

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

签名前是否需要签名证书?

在评论中,您引用了 iText“PDF 和数字签名”电子书,该电子书似乎表示检索签名证书和签名就足够了。

然而,根据上面解释的背景,我们意识到

  • 对于adbe.x509.rsa_sha1签名,签名证书必须位于签名值字典的Cert条目的值中。由于该条目不在内容条目中,因此该证书是签名数据的一部分。因此,在签字之前必须了解这一点。

  • 对于ETSI.CAdES.detached签名,签名属性必须包含 ESS 签名证书或签名证书 v2 属性。该属性引用签名者证书。因此,在签字之前必须了解这一点。

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

    但是,在仅基于 ISO 32000-1 的签名策略的情况下,LTV 信息必须存储在签名属性中(如果有的话),并且要检索 LTV 信息,显然需要知道尝试检索它们的证书,特别是签名者证书。

因此,要回答本主题标题中的问题:只有在宽松的签名策略背景下,只要您不需要添加 LTV 信息,您就可以在签名之前不知道签名者证书。

如果是 PAdES 签名呢?

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

这得看情况。

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

但如果它只需要 PDF 中 CMS 数字签名的 PAdES 配置文件(本质上是 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;
}
Run Code Online (Sandbox Code Playgroud)

现在要签名的 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; }
}
Run Code Online (Sandbox Code Playgroud)

对于上面在变量中计算的哈希值,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();
Run Code Online (Sandbox Code Playgroud)

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

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

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;
Run Code Online (Sandbox Code Playgroud)

您现在可以将签名容器字节嵌入到 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));
}
Run Code Online (Sandbox Code Playgroud)

这里使用的类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; }
}
Run Code Online (Sandbox Code Playgroud)

不过,如上所述,此签名容器不是 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;

}
Run Code Online (Sandbox Code Playgroud)

不幸的是PrivateKeySignature.Sign,期望消息对sh参数进行签名,特别是首先对其进行哈希处理。另一方面,在您的用例中sh已经是要签名的消息的哈希值。因此,您可以有效地对应该散列一次的地方进行两次散列。

您可以通过替换来修复此问题

IExternalSignature pks = new PrivateKeySignature(pk, DigestAlgorithms.SHA256);
byte[] data = pks.Sign(sh);
Run Code Online (Sandbox Code Playgroud)

在上面的代码中

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

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)
    { }
}
Run Code Online (Sandbox Code Playgroud)

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