我是数字签名概念的新手,我正在开发一个 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());
}
}
请告诉我我做错了什么,非常感谢。
我自己解决了这个问题。这是因为证书的私钥不是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 文件进行数字签名!