如何使用智能卡和 iTextSharp 对 pdf 文件进行数字签名?

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

我是数字签名概念的新手,我正在开发一个 winform 应用程序,允许用户从 Windows 证书存储中选择证书,并使用所选证书签署到 pdf 文件,插入 usbtoken 时它工作正常其中包含已安装的 usbtoken 的证书和中间件,当我签署 pdf 文件时,中间件提示我输入 pin,输入正确的 pin 后,我的应用程序可以轻松签署 pdf 文件。但是当我尝试使用智能卡(TokenMe Evo – Bit4id)时,我仍然可以从 Windows 商店获取证书,但是当通过以下代码行创建签名时:

    IExternalSignature externalSignature = new X509Certificate2Signature(certificate, 
    "SHA-1");        

没有任何提示我输入密码,并且出现以下异常:

    System.ArgumentException: Unknown encryption algorithm 
    System.Security.Cryptography.RSACng

这就是我从 Windows 商店获取证书的方式:

    using iTextSharp.text.pdf.security;
    using iTextSharp.text.pdf;
    using Org.BouncyCastle.Security;
    using Org.BouncyCastle.X509;
    using System.Security.Cryptography.X509Certificates;
    using X509Certificate = Org.BouncyCastle.X509.X509Certificate;
    using System.Windows.Forms;
    using System.Threading;
    using System.Collections.Concurrent;
    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using Org.BouncyCastle.Tls;
    using System.Text.Json;
    using Serilog;
    using iTextSharp.text;

    private static IList<X509Certificate> chain = new List<X509Certificate>();
    private static X509Certificate2 certificate = null;

    X509CertificateParser cp = new X509CertificateParser();

    //Get Sertifiacte
    X509Store st = new X509Store(StoreName.My, StoreLocation.CurrentUser);
    st.Open(OpenFlags.MaxAllowed);
    X509Certificate2Collection collection = 
    X509Certificate2UI.SelectFromCollection(st.Certificates,
        "Please select certificate:", "", X509SelectionFlag.SingleSelection);
    if (collection.Count > 0)
    {
        certificate = collection[0];
    }
    if (certificate == null)
    {
        MessageBox.Show("No certificate selected!");
        button3.Enabled = true;
        return;
    }
    st.Close();
    //Get Cert Chain

    X509Chain x509Chain = new X509Chain();

    x509Chain.Build(certificate);

    foreach (X509ChainElement x509ChainElement in x509Chain.ChainElements)
    {
        chain.Add(DotNetUtilities.FromX509Certificate(x509ChainElement.Certificate));
    }

这就是我签署pdf文件并发生异常的方式:

    private void signPdf(
        string inputFile, 
        string outputPath, 
        string imagePath, 
        int pageNum, 
        int position, 
        float imageWidth, 
        float imageHeight)
    {
        try
        {
            string fileName = Path.GetFileName(inputFile);
            ouputFile = ouputFile + "\\" + fileName;
            PdfReader inputPdf = new PdfReader(inputFile);

            FileStream signedPdf = new FileStream(ouputFile, FileMode.Create);

            PdfStamper pdfStamper = PdfStamper.CreateSignature(inputPdf, signedPdf, 
            '\0');

            IExternalSignature externalSignature = 
            new X509Certificate2Signature(certificate, "SHA-1");

            PdfSignatureAppearance signatureAppearance = 
            pdfStamper.SignatureAppearance;
            int NumberOfPages = inputPdf.NumberOfPages;
            if (imagePath != "" && imagePath != null)
            {
                signatureAppearance.SignatureGraphic = 
                iTextSharp.text.Image.GetInstance(imagePath);
            }


            iTextSharp.text.Rectangle pageSize = inputPdf.GetPageSize(pageNum);
            float x0 = 0;
            float y0 = 0;
            float x1 = 0;
            float y1 = 0;

            switch (position)
            {
                case 0:
                    x0 = 0;
                    y0 = pageSize.Height - imageHeight;
                    break;
                case 1:
                    x0 = pageSize.Width - imageWidth;
                    y0 = pageSize.Height - imageHeight;
                    break;
                case 2:
                    x0 = 0;
                    y0 = 0;
                    break;
                case 3:
                    x0 = pageSize.Width - imageWidth;
                    y0 = 0;
                    break;
                default:
                    break;
            }

            x1 = x0 + imageWidth;
            y1 = y0 + imageHeight;
            signatureAppearance.SetVisibleSignature(new iTextSharp.text.Rectangle(
                x0, 
                y0, 
                x1, 
                y1), 
                pageNum, 
                GetOrganizationName(certificate));
            if (renderingMode == 0)
            {
                signatureAppearance.SignatureRenderingMode = 
                PdfSignatureAppearance.RenderingMode.GRAPHIC_AND_DESCRIPTION;
            }
            else if (renderingMode == 1)
            {
                signatureAppearance.SignatureRenderingMode = 
                PdfSignatureAppearance.RenderingMode.GRAPHIC;
            }
            else if (renderingMode == 2)
            {
                signatureAppearance.SignatureRenderingMode = 
                PdfSignatureAppearance.RenderingMode.DESCRIPTION;
            }

            MakeSignature.SignDetached(signatureAppearance, externalSignature, chain, 
                null, null, null, 0,CryptoStandard.CMS);
            inputPdf.Close();
            pdfStamper.Close();
    }
    catch (Exception ex)
    {
        Log.Error(ex.ToString());

    }
}

请告诉我我做错了什么,非常感谢。

c# winforms itext digital-signature
1个回答
0
投票

我自己解决了这个问题。这是因为证书的私钥不是RSA或DSA的形式,它实际上是:

System.Security.Cryptography.RSACng

iTextSharp.text.pdf.security 中的 X509Certificate2Signature 类仅实现两种证书。PrivateKey 是 RSA 和 DSA,如下所示:

public X509Certificate2Signature(X509Certificate2 certificate, string 
    hashAlgorithm)
{
    if (!certificate.HasPrivateKey)
    {
        throw new ArgumentException("No private key.");
    }

    this.certificate = certificate;
    this.hashAlgorithm = 
    DigestAlgorithms.GetDigest(
        DigestAlgorithms.GetAllowedDigests(hashAlgorithm));
    if (certificate.PrivateKey is RSACryptoServiceProvider)
    {
        encryptionAlgorithm = "RSA";
        return;
    }

    if (certificate.PrivateKey is DSACryptoServiceProvider)
    {
        encryptionAlgorithm = "DSA";
        return;
    }

    throw new ArgumentException("Unknown encryption algorithm " + 
    certificate.PrivateKey);
}

这是未知加密算法的原因,因为当certificate.PrivateKey是System.Security.Cryptography.RSACng时,它不会设置加密算法并抛出新的ArgumentException,我无法编辑命名空间iTextSharp.text.pdf.security的类X509Certificate2Signature ,所以我创建了一个名为 X509Certificate2Signature1 的类,我自己实现了 IExternalSignature 接口,并添加了额外的代码来操作certificate.PrivateKey 是否为 System.Security.Cryptography.RSACng,下面是我如何实现我的 X509Certificate2Signature1 类:

public class X509Certificate2Signature1 : IExternalSignature
{
    //
    // Summary:
    //     The certificate with the private key
    private X509Certificate2 certificate;

    private string hashAlgorithm;

    private string encryptionAlgorithm;

    //
    // Summary:
    //     Creates a signature using a X509Certificate2. It supports 
    //smartcards without
    //     exportable private keys.
    //
    // Parameters:
    //   certificate:
    //     The certificate with the private key
    //
    //   hashAlgorithm:
    //     The hash algorithm for the signature. As the Windows CAPI is 
    //used to do the signature
    //     the only hash guaranteed to exist is SHA-1
    public X509Certificate2Signature1(X509Certificate2 certificate, string 
        hashAlgorithm)
    {
        if (!certificate.HasPrivateKey)
        {
            throw new ArgumentException("No private key.");
        }

        this.certificate = certificate;
        this.hashAlgorithm = 

        DigestAlgorithms.GetDigest(
            DigestAlgorithms.GetAllowedDigests(hashAlgorithm));
        if (certificate.PrivateKey is RSACryptoServiceProvider)
        {
            encryptionAlgorithm = "RSA";
            return;
        }
        else if (certificate.PrivateKey is DSACryptoServiceProvider)
        {
            encryptionAlgorithm = "DSA";
            return;
        }
        else if (certificate.PrivateKey is 
            System.Security.Cryptography.RSACng)
        {
            encryptionAlgorithm = "RSA";
            return;
        }
        else
        {
            encryptionAlgorithm = "RSA";
            return;
        }

        throw new ArgumentException("Unknown encryption algorithm " + 
            certificate.PrivateKey);
    }

    public virtual byte[] Sign(byte[] message)
    {
        if (certificate.PrivateKey is RSACryptoServiceProvider)
        {
            RSACryptoServiceProvider rsa = 
               (RSACryptoServiceProvider)certificate.PrivateKey;
            return rsa.SignData(message, hashAlgorithm);
        }
        else if (certificate.PrivateKey is 
           System.Security.Cryptography.RSACng)
        {
            System.Security.Cryptography.RSACng rSACng = 
              (System.Security.Cryptography.RSACng)certificate.PrivateKey;
            return rSACng.SignData(message, HashAlgorithmName.SHA1, 
                RSASignaturePadding.Pkcs1);
        }
        else
        {
            DSACryptoServiceProvider dsa = 
                (DSACryptoServiceProvider)certificate.PrivateKey;
            return dsa.SignData(message);
        }
    }

    public virtual string GetHashAlgorithm()
    {
        return hashAlgorithm;
    }

    public virtual string GetEncryptionAlgorithm()
    {
        return encryptionAlgorithm;
    }
}

我更改了导致问题的代码行:

externalSignature = new X509Certificate2Signature(certificate, 
    "SHA-1");

致:

externalSignature = new X509Certificate2Signature1(certificate, 
    "SHA-1");

现在我的应用程序可以完美地对 pdf 文件进行数字签名!

© www.soinside.com 2019 - 2024. All rights reserved.