我已使用 ECDSA (SHA-256) 证书使用 iText 8.0.2 签署了 PDF 文档。不幸的是,Adobe Reader 说签名无效,原因是“文档自签名以来已被更改或损坏”。由于数字签名主题不是我的专长,因此我无法弄清楚如何以及在哪里查找错误。有人可以指导我吗?
using iText.Kernel.Pdf;
using iText.Signatures;
using System;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
namespace PdfSignTest
{
class Program
{
static void Main(string[] args)
{
if (args.Length < 2)
{
Console.WriteLine("Usage: PdfSignTest.exe pdfFilePath pfxFilePath");
Console.WriteLine(" - pdfFilePath: path of an unsigned pdf file which will be signed");
Console.WriteLine(" - pfxFilePath: path of a pfx file containing the full certificate chain");
Console.ReadKey();
return;
}
string srcPath = args[0];
string cerPath = args[1];
string dstPath = srcPath.Replace(".pdf", ".signed.at." + DateTime.Now.ToString("HH-mm-ss") + ".pdf");
var signed = SignPdfECC(srcPath, cerPath);
File.WriteAllBytes(dstPath, signed);
Console.WriteLine(string.Format("File {0} successfully signed and saved as {1}.", srcPath, dstPath));
Console.ReadKey();
}
private static byte[] SignPdfECC(string unsignedPdfPath, string certificatePfxPath)
{
byte[] certificatePfx = File.ReadAllBytes(certificatePfxPath);
using (PdfReader reader = new PdfReader(unsignedPdfPath))
using (MemoryStream mem = new MemoryStream())
{
PdfSigner signer = new PdfSigner(reader, mem, new StampingProperties().UseAppendMode());
X509Certificate cert = new X509Certificate(certificatePfx, "password", X509KeyStorageFlags.Exportable); // byte tömbben a pfx file tartalmát adjuk be
X509Certificate2 signatureCert = new X509Certificate2(cert);
ECDsa pk = signatureCert.GetECDsaPrivateKey();
IExternalSignature signature = new EcdsaSignature(pk, DigestAlgorithms.SHA256);
iText.Bouncycastle.X509.X509CertificateBC[] chain = new iText.Bouncycastle.X509.X509CertificateBC[] { new iText.Bouncycastle.X509.X509CertificateBC(new Org.BouncyCastle.X509.X509Certificate(signatureCert.GetRawCertData())) };
signer.SignDetached(signature, chain, null, null, null, 0, PdfSigner.CryptoStandard.CMS);
return mem.ToArray();
}
}
}
public class EcdsaSignature : IExternalSignature
{
private readonly string _encryptionAlgorithm;
private readonly string _hashAlgorithm;
private readonly ECDsa _pk;
public EcdsaSignature(ECDsa pk, string hashAlgorithm)
{
_pk = pk;
_hashAlgorithm = DigestAlgorithms.GetDigest(DigestAlgorithms.GetAllowedDigest(hashAlgorithm));
_encryptionAlgorithm = "ECDSA";
}
public string GetDigestAlgorithmName()
{
return "SHA-256";
}
public virtual string GetEncryptionAlgorithm()
{
return _encryptionAlgorithm;
}
public virtual string GetHashAlgorithm()
{
return _hashAlgorithm;
}
public string GetSignatureAlgorithmName()
{
return "ECDSA";
}
public ISignatureMechanismParams GetSignatureMechanismParameters()
{
return null;
}
public virtual byte[] Sign(byte[] message)
{
return _pk.SignData(message, new HashAlgorithmName(_hashAlgorithm));
}
}
}
我删除了所有附加功能,例如 TSA 和 LTV,预计可能是这些步骤之一导致了错误。我还尝试使用从 OpenSSL 测试证书集合中获取的测试证书以及从 w3.org 下载的测试 PDF。所有这些都在这里
ECDsa.SignData
返回 PLAIN 格式的签名值,而 iText 使用 DER 格式的 ECDSA 签名的 OID。因此,在从
EcdsaSignature.Sign
返回签名值之前,您必须将其从 PLAIN 转换为 DER 格式,例如像这样:using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Math;
[...]
public virtual byte[] Sign(byte[] message)
{
return PlainToDer(_pk.SignData(message, new HashAlgorithmName(_hashAlgorithm)));
}
byte[] PlainToDer(byte[] plain)
{
int valueLength = plain.Length / 2;
BigInteger r = new BigInteger(1, plain, 0, valueLength);
BigInteger s = new BigInteger(1, plain, valueLength, valueLength);
return new DerSequence(new DerInteger(r), new DerInteger(s)).GetEncoded(Asn1Encodable.Der);
}
(SignWithEcdsaCert类EcdsaSignature
方法)
PlainToDer
辅助方法借用自 iText 知识库文章
使用 iText 进行数字签名/第一部分 - 概述和简单案例
/使用 .NET 进行签名的
X509Certificate2Signature
实现IExternalSignature
CryptoAPI/CNG 密钥.