首页 > 解决方案 > 带有签名哈希的 itextsharp 签名 pdf

问题描述

我正在尝试通过签名服务签署 pdf。该服务需要发送一个十六进制编码的 SHA256 摘要,作为回报,我会收到一个十六进制编码的签名值。除此之外,我还收到了一个签名证书、中间证书、OCSP 响应和 TimeStampToken。但是,我已经在尝试使用 signatureValue 签署 pdf 时遇到困难。

我已经阅读了布鲁诺的白皮书,过度浏览了互联网,并尝试了许多不同的方法,但签名一直显示为无效。

我最近的尝试:

一、准备pdf

PdfReader reader = new PdfReader(src);
FileStream os = new FileStream(dest, FileMode.Create);
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.Certificate = signingCertificate;
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(appearance, external, 8192);

string hashAlgorithm = "SHA-256";
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, false);
PdfSignatureAppearance appearance2 = stamper.SignatureAppearance;
Stream stream = appearance2.GetRangeStream();
byte[] hash = DigestAlgorithms.Digest(stream, hashAlgorithm);
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);

散列 byte[] sh 并转换为字符串如下

private static String sha256_hash(Byte[] value)
{
    using (SHA256 hash = SHA256.Create())
    {
         return String.Concat(hash.ComputeHash(value).Select(item => item.ToString("x2"))).ToUpper();
    }
}

并发送到签名服务。然后将接收到的十六进制编码的签名值转换为字节

private static byte[] StringToByteArray(string hex)
{
    return Enumerable.Range(0, hex.Length).Where(x => x % 2 == 0).Select(x => Convert.ToByte(hex.Substring(x, 2), 16)).ToArray();
}

最后,创建签名

private void CreateSignature(string src, string dest, byte[] sig) 
{
    PdfReader reader = new PdfReader(src); // src is now prepared pdf
    FileStream os = new FileStream(dest, FileMode.Create);
    IExternalSignatureContainer external = new MyExternalSignatureContainer(sig);
    MakeSignature.SignDeferred(reader, "Signature1", os, external);

    reader.Close();
    os.Close();
}
private class MyExternalSignatureContainer : IExternalSignatureContainer
{
    protected byte[] sig;
    public MyExternalSignatureContainer(byte[] sig)
    {
        this.sig = sig;
    }
    public byte[] Sign(Stream s)
    {
        return sig;
    }
    public void ModifySigningDictionary(PdfDictionary signDic) { }
}

我究竟做错了什么?非常感谢您的帮助。谢谢!

编辑:当前状态

感谢 mkl 的帮助并遵循 Bruno 的延迟签名示例,我已经通过了无效签名消息。显然我没有收到来自签名服务的完整链,而只是一个中间证书,这导致了无效消息。不幸的是,签名仍然存在缺陷。

我像这样构建链:

List<X509Certificate> certificateChain = new List<X509Certificate>
{
     signingCertificate,
     intermediateCertificate
}; 

在 MyExternalSignatureContainer 的 sign 方法中,我现在构造并返回签名容器:

public byte[] Sign(Stream s)
{
    string hashAlgorithm = "SHA-256";
    PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, false);

    byte[] ocspResponse = Convert.FromBase64String("Base64 encoded DER representation of the OCSP response received from signing service");
    byte[] hash = DigestAlgorithms.Digest(s, hashAlgorithm);
    byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, ocspResponse, null, CryptoStandard.CMS);

    string messageDigest = Sha256_hash(sh);
    // messageDigest sent to signing service
    byte[] signatureAsByte = StringToByteArray("Hex encoded SignatureValue received from signing service");

    sgn.SetExternalDigest(signatureAsByte, null, "RSA");

    ITSAClient tsaClient = new MyITSAClient();

    return sgn.GetEncodedPKCS7(hash, tsaClient, ocspResponse, null, CryptoStandard.CMS); 
}

public class MyITSAClient : ITSAClient
{
    public int GetTokenSizeEstimate()
    {
        return 0;
    }

    public IDigest GetMessageDigest()
    {
        return new Sha256Digest();
    }

    public byte[] GetTimeStampToken(byte[] imprint)
    {
        string hashedImprint = HexEncode(imprint);
        // Hex encoded Imprint sent to signing service

        return Convert.FromBase64String("Base64 encoded DER representation of TimeStampToken received from signing service");
    }
}

仍然收到这些消息:

  1. “签名者的身份未知,因为它没有包含在受信任的身份列表中,并且没有一个或其父证书是受信任的身份”
  2. “签名带有时间戳,但无法验证时间戳”

再次非常感谢您的进一步帮助!

标签: pdfhashitextsignature

解决方案


“我究竟做错了什么?”

问题是一方面您开始使用PdfPKCS7实例构建 CMS 签名容器

PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, false);

并且对于计算出的文档摘要hash检索签名的属性是

byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);

送他们签字。

到目前为止,一切都很好。

但是随后您忽略了您开始构建的 CMS 容器,而是将您从服务中获得的裸签名字节注入到 PDF中。

这不起作用,因为您的签名字节不直接对文档进行签名,而是对这些签名属性进行签名(因此,作为文档哈希的文档间接地是签名属性之一)。因此,通过忽略正在构建的 CMS 容器,您删除了实际签名的数据......

此外,ADBE_PKCS7_DETACHED您使用的子过滤器承诺嵌入的签名是一个完整的 CMS 签名容器,而不是几个裸签名字节,因此格式也是错误的。

该怎么做呢?

不必将您从服务中获得的裸签名字节按原样注入 PDF,您必须在PdfPKCS7最初开始构建签名容器的实例中将它们设置为外部摘要:

sgn.SetExternalDigest(sig, null, ENCRYPTION_ALGO);

ENCRYPTION_ALGO必须是签名算法的加密部分,我假设你的情况"RSA"。)

然后您可以检索生成的 CMS 签名容器:

byte[] encodedSig = sgn.GetEncodedPKCS7(hash, null, null, null, CryptoStandard.CMS);

现在这是要使用以下方法注入文档的签名容器MyExternalSignatureContainer

IExternalSignatureContainer external = new MyExternalSignatureContainer(encodedSig);
MakeSignature.SignDeferred(reader, "Signature1", os, external);

剩下的问题

更正您的代码后,Adobe Reader 仍然会警告您的签名:

  1. “签名者的身份未知,因为它没有包含在受信任的身份列表中,并且没有一个或其父证书是受信任的身份”

这个警告是可以预料的并且是正确的!

签名者的身份未知,因为您的签名服务仅使用演示证书,而不是生产使用的证书:

证书查看器中的签名者证书

如您所见,证书是由“GlobalSign Non-Public HVCA Demo”颁发的,出于显而易见的原因,不得信任非公共演示颁发者(除非您出于测试目的手动将它们添加到您的信任库中)。

  1. “签名带有时间戳,但无法验证时间戳”

Adobe 不批准您的时间戳有两个原因:

一方面,就像上面一样,时间戳证书是非公开的演示证书(“DSS Non-Public Demo TSA Responder”)。因此,验证者没有理由信任您的时间戳。

但另一方面,您的时间戳代码中存在实际错误,您应用了两次散列算法!在你的MyITSAClient课堂上,你有

public byte[] GetTimeStampToken(byte[] imprint)
{
    string hashedImprint = Sha256_hash(imprint);
    // hashedImprint sent to signing service

    return Convert.FromBase64String("Base64 encoded DER representation of TimeStampToken received from signing service");
}

你的实现imprint参数GetTimeStampToken已经被散列了,所以你必须对这些字节进行十六进制编码并将它们发送给时间戳。但是你应用你的方法Sha256_hash,它首先散列然后十六进制编码这个新的散列。

因此,而不是Sha256_hash仅仅应用十六进制编码imprint


推荐阅读