首页 文章

iText使用智能卡外部签名签署PDF

提问于
浏览
0

我一直在用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消息您希望被哈希并签名的消息”

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

所以我理解签署的程序如下:

  • 源PDF已加载
    创建具有空签名字段的

  • 新PDF

  • 该字段的字节范围经过哈希处理(默认情况下为sha-1生成20个字节,尝试使用sha-256生成32个字节)

  • 哈希其他一些属性再次进行哈希处理(字节数变化,为什么?毕竟可能不是哈希?)

  • 第二个哈希在外部签名对象内再次进行哈希处理

  • 第三个哈希最终被发送到智能卡以计算签名

  • 签名将插入到新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;
    }
}

1 回答

  • 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同时在评论中指出:

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

    所以我尝试使用第一个文件中的证书解码第二个和第三个文件中的签名值,实际上,这有效!因此,第二个和第三个文件声称由与该备用证书关联的私钥签名,但实际上使用与前一个证书关联的私钥进行签名 .

    因此: Problem 1: Signatures in files 2 and 3 are signed with the wrong private key / the wrong application on the smart card.

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

    • 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
    

    对于背景cf. RFC 3447 .

    这解释了OP的观察:

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

    验证者试图将裸散列解释为使用明显失败的BER编码的ASN.1序列 .

    因此: Problem 2: The smart card encrypts the naked hash value but has to encrypt a DigestInfo object encapsulating that hash.

    初步的,过时的答案

    如果您在签署数据时调用 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;
    }
    

相关问题