iText 使用智能卡外部签名对 PDF 进行签名

问题描述 投票:0回答:2

我已经使用 iTextSharp 5.5.7 一段时间了,找不到从智能卡为 PDF 进行有效数字签名的正确方法 - Adobe Reader 总是说其签名者未知,无法解码签名” DER 数据。

我查看了 MakeSignature.cs 代码以供参考,其作用是:

        Stream data = signatureAppearance.GetRangeStream(); 
        // gets the first hash
        byte[] hash = DigestAlgorithms.Digest(data, hashAlgorithm);
        // gets the second hash or is it not a hash at all ?
        byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, ocsp, crlBytes, sigtype);

然后按照IExternalSignature.cs

中的“sign”方法

“@param message 您想要进行哈希和签名的消息”

        // looks like externalSignature.Sign() should make another hash out of "sh"
        // and use this hash to compute a signature
        byte[] extSignature = externalSignature.Sign(sh); 

所以我理解的签名流程如下:

  1. 源 PDF 已加载
  2. 创建了带有空签名字段的新 PDF
  3. 该字段的字节范围被散列(默认情况下为 sha-1 生成 20 字节,也尝试使用 sha-256 生成 32 字节)
  4. Hash + 一些其他属性再次被散列(字节数 各不相同,为什么?可能根本就不是哈希?)
  5. 第二个哈希在外部签名对象内部再次进行哈希处理
  6. 第三个哈希值最终被发送到智能卡以计算 签名
  7. 签名已插入新 PDF 中

当我使用 Adobe Reader 签署 PDF 时,在第 6 步中,第三个哈希值的长度为 32 字节。 从智能卡的角度来看,我使用 Acrobat 和 iText 执行相同的步骤,但使用 iText 时签名无效,可能出了什么问题?


我使用的代码:

public void StartTest(){
        X509Certificate2 cert = new X509Certificate2();
        cert.Import("cert.cer"); // certificate obtained from smart card

        X509CertificateParser certParse = new Org.BouncyCastle.X509.X509CertificateParser();

        Org.BouncyCastle.X509.X509Certificate[] chain = new Org.BouncyCastle.X509.X509Certificate[] { certParse.ReadCertificate(cert.RawData) };

        // Reader and stamper
        PdfReader pdfReader = new PdfReader("original.pdf");
        Stream signedPdf = new FileStream("signed.pdf", FileMode.Create);
        PdfStamper stamper = PdfStamper.CreateSignature(pdfReader, signedPdf, '\0', null, false);

        // Appearance
        PdfSignatureAppearance appearance = stamper.SignatureAppearance;
        appearance.SignatureCreator = "Me";
        appearance.Reason = "Testing iText";
        appearance.Location = "On my Laptop";
        appearance.SignatureGraphic = Image.GetInstance("img.png"); // visual image
        appearance.SetVisibleSignature(new Rectangle(50, 50, 250, 100), pdfReader.NumberOfPages, "Signature");
        appearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.GRAPHIC_AND_DESCRIPTION;

        // Timestamp
        TSAClientBouncyCastle tsc = new TSAClientBouncyCastle("http://ts.cartaodecidadao.pt/tsa/server", "", "");

        // Digital signature
        IExternalSignature externalSignature = new MyExternalSignature2("SHA-1");
        MyMakeSignature.SignDetached(appearance, externalSignature, chain, null, null, tsc, 0, CryptoStandard.CADES);

        stamper.Close();
}

外部签名实现(类MyExternalSignature2):

    class MyExternalSignature2 : IExternalSignature
{
    private String hashAlgorithm;
    private String encryptionAlgorithm;

    public MyExternalSignature2(String hashAlgorithm)
    {
        this.encryptionAlgorithm = "RSA";
        this.hashAlgorithm = DigestAlgorithms.GetDigest(DigestAlgorithms.GetAllowedDigests(hashAlgorithm));
    }

    public virtual byte[] Sign(byte[] message) {

        byte[] hash = null;
        using (SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider())
        {
            hash = sha1.ComputeHash(message);
        }

        byte[] sig = MySC.GetSignature(hash);

        return sig;
    }

    public virtual String GetHashAlgorithm() {
        return hashAlgorithm;
    }

    public virtual String GetEncryptionAlgorithm() {
        return encryptionAlgorithm;
    }
}
pdf itext digital-signature smartcard itextpdf
2个回答
0
投票

OP 提供了许多签名文件样本,对它们进行分析后发现,我最初的答案显然是基于误解。

分析所提供的签名 PDF

OP 提供了三个由他的代码签名的 PDF,首先是这两个:

检查它们后,iText CAdES 使用 SHA1 签名的特殊性就显现出来了:对于

CryptoStandard.CADES
,iText 即使对于 SHA1 也使用
SigningCertificateV2
属性,但规范建议在那里使用
SigningCertificate
属性。为了防止这种特性的干扰,OP提供了第三个文件

但事实证明,这个怪癖并不是OP观察的原因,Adobe Reader仍然报告密码学库错误。

因此,回到分析。

由于签名算法为 SHA1withRSA/2048 和 SHA256withRSA/2048,因此可以使用相应证书中的公钥简单地解密内部签名值。

对于 ex_signed.pdf 成功,但对于 ex_signed_2.pdf 或 ex_signed_3.pdf 失败。

OP同时在评论中指出:

这些文件之间的区别是用于签名的证书,在第一个文件中,我使用证书进行身份验证,这是(我认为)可以接受的,但我有另一个专门用于数字签名的证书,我在第二个和第三个文件中使用了它。

因此,我尝试使用第一个文件中的证书来解码第二个和第三个文件中的签名值,事实上,这有效!因此,第二个和第三个文件声称是由与该替代证书关联的私钥签名的,但实际上是使用与前一个证书关联的私钥签名的。

因此: 问题 1:文件 2 和 3 中的签名使用错误的私钥/智能卡上的错误应用程序进行签名。

为了进一步分析问题,我查看了使用身份验证证书成功解码的签名值:

  • ex_signed.pdf:

      2a8945abe450b2c1cd232249b8f811d352ad0d29
    
  • ex_signed_2.pdf

      cc24acc848002df63733941e34437f8aef1c746c
    
  • ex_signed_3.pdf

      45f8e451f8b9f39f0c1f59eea8b6308fba22176ac62ebd14bbf07e5407aed7e8
    

因此,对于 SHA1,有 20 个字节,对于 SHA-256,有 32 个字节。这正是哈希值的大小,因此很可能这些只是裸哈希值。

但是,这是错误的,XXXwithRSA 签名应包含一个加密结构,其中包含哈希算法的 OID 和哈希值(采用 ASN.1 表示法):

DigestInfo ::= SEQUENCE {
  digestAlgorithm DigestAlgorithmIdentifier, 
  digest Digest
  }

DigestAlgorithmIdentifier ::= AlgorithmIdentifier

Digest ::= OCTET STRING

对于背景参见。 RFC 3447.

这解释了OP的观察:

第一个错误被描述为“BER解码错误”,

验证者尝试将裸哈希解释为使用 BER 编码的 ASN.1 序列,但这显然失败了。

因此: 问题 2:智能卡加密裸哈希值,但必须加密封装该哈希的

DigestInfo
对象。

最初的、过时的答案

如果您的

MySC.GetSignature
在签署数据时调用哈希(并且不希望数据之前已经经过哈希处理),则应替换

public virtual byte[] Sign(byte[] message)
{
    byte[] hash = null;
    using (SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider())
    {
        hash = sha1.ComputeHash(message);
    }

    byte[] sig = MySC.GetSignature(hash);

    return sig;
}

通过类似

public virtual byte[] Sign(byte[] message)
{
    byte[] sig = MySC.GetSignature(message);

    return sig;
}

0
投票

嗨,我也有类似的问题,我的服务器只接受string格式的哈希值,但我的签名方法存在于externalsignature中,哈希值是Byte[],你@mkl可以帮我吗这里

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.