手动创建时 iText 7 pdf 签名无效
iText 7 pdf signature invalid when created manually
我想使用 iText 7 对 pdf 文档进行数字签名。签名是由外部服务创建的,returns 只有 PKCS1 签名。然后我必须创建并应用 PKCS7。
iText 中针对此场景提供了很好的文档:https://kb.itextpdf.com/home/it7kb/examples/how-to-use-a-digital-signing-service-dss-such-as-globalsign-with-itext-7
示例应用程序
我创建了一个示例应用程序,它通过本地证书签署 pdf 文档。可以从 https://github.com/suntsu42/PdfSignSamplePkcs1 克隆此示例应用程序。在此示例应用程序中,有两种不同的创建 PKCS7 的方法。一次手动,一次通过 IExternalSignature(PrivateKeySignature) 实现。
对于这两种情况,必须以相同的方式创建必须签名的 pdf 摘要。唯一的区别是 PKCS7 的创建方式。
github (https://github.com/suntsu42/PdfSignSamplePkcs1) 上的项目已完成且独立。在资源文件夹中是一个用于创建签名和根证书的私钥文件 (pfx)。为了 运行 该示例,只需更改 resourcePath 变量的值以适应您的本地系统就足够了。
可以通过更改 createSignatureViaPlainPkcs1
的值来切换签名创建
using iText.Kernel.Pdf;
using iText.Signatures;
using System;
using System.IO;
namespace PdfSignSamplePkcs1
{
class Program
{
static void Main(string[] args)
{
// TODO >> Change this path based on your local system
var resourcePath = @"c:\project\github\PdfSignSamplePkcs1\Resources\";
var pdfToSignPath = Path.Combine(resourcePath, "test.pdf");
var signedPdfPath = Path.Combine(resourcePath, "signedPdf.pdf");
var privateKey = Path.Combine(resourcePath, "SignTest.pfx"); // not critical, self signed certificate
var privateKeyPassword = "test";
// ############
// Change value in order to create the PKCS7
// either manually or via Itext
// ############
bool createSignatureViaPlainPkcs1 = false;
//delete signed file if it exists
if (System.IO.File.Exists(signedPdfPath))
System.IO.File.Delete(signedPdfPath);
var pdfToSign = System.IO.File.ReadAllBytes(pdfToSignPath);
byte[] pdfDigest = null;
//#1 Prepare pdf for signing
var SignatureAttributeName = $"SignatureAttributeName_{DateTime.Now:yyyyMMddTHHmmss}";
byte[] preparedToSignPdf = null;
using (MemoryStream input = new MemoryStream(pdfToSign))
{
using (var reader = new PdfReader(input))
{
StampingProperties sp = new StampingProperties();
sp.UseAppendMode();
using (MemoryStream baos = new MemoryStream())
{
var signer = new PdfSigner(reader, baos, sp);
signer.SetCertificationLevel(PdfSigner.NOT_CERTIFIED);
signer.SetFieldName(SignatureAttributeName);
DigestCalcBlankSigner external = new DigestCalcBlankSigner(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
signer.SignExternalContainer(external, 32000);
//get digest to be signed
pdfDigest = external.PdfDigest;
preparedToSignPdf = baos.ToArray();
}
}
}
//#2 Create PKCS7
SignService ss = new SignService(pdfDigest, privateKey, privateKeyPassword);
byte[] signatureAsPkcs7 = null;
if (createSignatureViaPlainPkcs1)
signatureAsPkcs7 = ss.CreatePKCS7ViaPkcs1(); // >> Creates invalid pdf signature
else
signatureAsPkcs7 = ss.CreatePKCS7(); // Creates valid pdf signature
//#3 apply cms(PKCS7) to prepared pdf
ReadySignatureSigner extSigContainer = new ReadySignatureSigner(signatureAsPkcs7);
using (MemoryStream preparedPdfStream = new MemoryStream(preparedToSignPdf))
{
using (var pdfReader = new PdfReader(preparedPdfStream))
{
using (PdfDocument docToSign = new PdfDocument(pdfReader))
{
using (MemoryStream outStream = new MemoryStream())
{
PdfSigner.SignDeferred(docToSign, SignatureAttributeName, outStream, extSigContainer);
System.IO.File.WriteAllBytes(signedPdfPath, outStream.ToArray());
}
}
}
}
}
}
}
手动创建 pkcs7 签名
在此示例中,首先使用本地证书创建 PKCS1 签名。然后通过 SetExternalDigest
将创建的 PKCS1 签名应用于 PdfPKCS7 容器
这种方式创建的pdf无效。
public byte[] CreatePKCS7ViaPkcs1()
{
//Load the certificate used for signing
signCertificatePrivateKey = LoadCertificateFromFile();
// create sha256 message digest
// This is from https://kb.itextpdf.com/home/it7kb/examples/how-to-use-a-digital-signing-service-dss-such-as-globalsign-with-itext-7
// Not sure if this is required, but the created signature is invalid either way
using (SHA256 sha256 = SHA256.Create())
{
Digest = sha256.ComputeHash(Digest);
}
//Create pkcs1 signature
byte[] signature = null;
using (var key = signCertificatePrivateKey.GetRSAPrivateKey())
{
signature = key.SignData(Digest, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
Org.BouncyCastle.X509.X509Certificate cert = DotNetUtilities.FromX509Certificate(signCertificatePrivateKey);
PdfPKCS7 sgn = new PdfPKCS7(null, new[] { cert }, "SHA256", false);
sgn.SetExternalDigest(signature, null, "RSA");
//Return the complete PKCS7 CMS
return sgn.GetEncodedPKCS7(Digest, PdfSigner.CryptoStandard.CMS, null, null, null);
}
使用 PrivateKeySignature 实现创建 PKCS7 签名
在此示例中,PKCS7 是使用 iText PrivateKeySignature 创建的。签名是使用与另一个示例中相同的摘要和相同的私钥创建的。
此处创建的pdf有效。但是由于这种方法不允许使用外部服务来创建签名,所以我不能使用它。
public byte[] CreatePKCS7ViaPkcs1()
{
//Load the certificate used for signing
signCertificatePrivateKey = LoadCertificateFromFile();
// create sha256 message digest
// This is from https://kb.itextpdf.com/home/it7kb/examples/how-to-use-a-digital-signing-service-dss-such-as-globalsign-with-itext-7
// Not sure if this is required, but the created signature is invalid either way
using (SHA256 sha256 = SHA256.Create())
{
Digest = sha256.ComputeHash(Digest);
}
//Create pkcs1 signature using RSA
byte[] signature = null;
using (var key = signCertificatePrivateKey.GetRSAPrivateKey())
{
signature = key.SignData(Digest, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
Org.BouncyCastle.X509.X509Certificate cert = DotNetUtilities.FromX509Certificate(signCertificatePrivateKey);
PdfPKCS7 sgn = new PdfPKCS7(null, new[] { cert }, "SHA256", false);
sgn.SetExternalDigest(signature, null, "RSA");
//Return the complete PKCS7 CMS
return sgn.GetEncodedPKCS7(Digest, PdfSigner.CryptoStandard.CMS, null, null, null);
}
备注(编辑)
我认为问题的原因是我没有使用 GetAuthenticatedAttributeBytes 来获取要签名的散列。但是我不能使用这种方法。时间戳、ocsp 和 CLR 作为服务调用的一部分返回。由于 GetAuthenticatedAttributeBytes 的参数必须与应用签名时的参数相同,我想我无法使用此功能。
问题
通过 RSA 创建的签名在生成的 pdf 中无效的原因是什么?
编辑:更具体地说:当签名服务 returns PKCS1、时间戳、Ocsp 和 CRL 时,如何创建有效的 pkcs7 容器。在这种情况下究竟必须签署什么?
一个错误相当明显:
在 CreatePKCS7
中,您对签名容器 (GetAuthenticatedAttributeBytes
) 的待签名属性进行签名,其中包含文档摘要 (Digest
):
var sh = sgn.GetAuthenticatedAttributeBytes(Digest, PdfSigner.CryptoStandard.CMS, null, null);
byte[] extSignature = signature.Sign(sh);
在 CreatePKCS7ViaPkcs1
中,您签署文档摘要 (Digest
) 本身:
//Create pkcs1 signature using RSA
byte[] signature = null;
using (var key = signCertificatePrivateKey.GetRSAPrivateKey())
{
signature = key.SignData(Digest, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
在这两种情况下,您都可以继续将返回的签名注入 PdfPKCS7
:
sgn.SetExternalDigest(extSignature, null, signature.GetEncryptionAlgorithm());
和
sgn.SetExternalDigest(signature, null, "RSA");
分别
第一个变体有效,是一个指标,告诉您 SetExternalDigest
期望将要签名的签名(外部签名摘要)作为第一个参数属性,而不是直接的文档摘要。
因此,在 CreatePKCS7ViaPkcs1
中,您只是简单地签署了错误的字节!
您可以通过(就像在 CreatePKCS7
中一样)在之前创建 PdfPKCS7
实例并从中接收要签名的属性(使用 GetAuthenticatedAttributeBytes
).在签名之前,您可能需要也可能不需要对此处的结果进行哈希处理 - 我不太精通 .NET 加密 API。
我想使用 iText 7 对 pdf 文档进行数字签名。签名是由外部服务创建的,returns 只有 PKCS1 签名。然后我必须创建并应用 PKCS7。
iText 中针对此场景提供了很好的文档:https://kb.itextpdf.com/home/it7kb/examples/how-to-use-a-digital-signing-service-dss-such-as-globalsign-with-itext-7
示例应用程序
我创建了一个示例应用程序,它通过本地证书签署 pdf 文档。可以从 https://github.com/suntsu42/PdfSignSamplePkcs1 克隆此示例应用程序。在此示例应用程序中,有两种不同的创建 PKCS7 的方法。一次手动,一次通过 IExternalSignature(PrivateKeySignature) 实现。
对于这两种情况,必须以相同的方式创建必须签名的 pdf 摘要。唯一的区别是 PKCS7 的创建方式。
github (https://github.com/suntsu42/PdfSignSamplePkcs1) 上的项目已完成且独立。在资源文件夹中是一个用于创建签名和根证书的私钥文件 (pfx)。为了 运行 该示例,只需更改 resourcePath 变量的值以适应您的本地系统就足够了。
可以通过更改 createSignatureViaPlainPkcs1
的值来切换签名创建using iText.Kernel.Pdf;
using iText.Signatures;
using System;
using System.IO;
namespace PdfSignSamplePkcs1
{
class Program
{
static void Main(string[] args)
{
// TODO >> Change this path based on your local system
var resourcePath = @"c:\project\github\PdfSignSamplePkcs1\Resources\";
var pdfToSignPath = Path.Combine(resourcePath, "test.pdf");
var signedPdfPath = Path.Combine(resourcePath, "signedPdf.pdf");
var privateKey = Path.Combine(resourcePath, "SignTest.pfx"); // not critical, self signed certificate
var privateKeyPassword = "test";
// ############
// Change value in order to create the PKCS7
// either manually or via Itext
// ############
bool createSignatureViaPlainPkcs1 = false;
//delete signed file if it exists
if (System.IO.File.Exists(signedPdfPath))
System.IO.File.Delete(signedPdfPath);
var pdfToSign = System.IO.File.ReadAllBytes(pdfToSignPath);
byte[] pdfDigest = null;
//#1 Prepare pdf for signing
var SignatureAttributeName = $"SignatureAttributeName_{DateTime.Now:yyyyMMddTHHmmss}";
byte[] preparedToSignPdf = null;
using (MemoryStream input = new MemoryStream(pdfToSign))
{
using (var reader = new PdfReader(input))
{
StampingProperties sp = new StampingProperties();
sp.UseAppendMode();
using (MemoryStream baos = new MemoryStream())
{
var signer = new PdfSigner(reader, baos, sp);
signer.SetCertificationLevel(PdfSigner.NOT_CERTIFIED);
signer.SetFieldName(SignatureAttributeName);
DigestCalcBlankSigner external = new DigestCalcBlankSigner(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
signer.SignExternalContainer(external, 32000);
//get digest to be signed
pdfDigest = external.PdfDigest;
preparedToSignPdf = baos.ToArray();
}
}
}
//#2 Create PKCS7
SignService ss = new SignService(pdfDigest, privateKey, privateKeyPassword);
byte[] signatureAsPkcs7 = null;
if (createSignatureViaPlainPkcs1)
signatureAsPkcs7 = ss.CreatePKCS7ViaPkcs1(); // >> Creates invalid pdf signature
else
signatureAsPkcs7 = ss.CreatePKCS7(); // Creates valid pdf signature
//#3 apply cms(PKCS7) to prepared pdf
ReadySignatureSigner extSigContainer = new ReadySignatureSigner(signatureAsPkcs7);
using (MemoryStream preparedPdfStream = new MemoryStream(preparedToSignPdf))
{
using (var pdfReader = new PdfReader(preparedPdfStream))
{
using (PdfDocument docToSign = new PdfDocument(pdfReader))
{
using (MemoryStream outStream = new MemoryStream())
{
PdfSigner.SignDeferred(docToSign, SignatureAttributeName, outStream, extSigContainer);
System.IO.File.WriteAllBytes(signedPdfPath, outStream.ToArray());
}
}
}
}
}
}
}
手动创建 pkcs7 签名
在此示例中,首先使用本地证书创建 PKCS1 签名。然后通过 SetExternalDigest
将创建的 PKCS1 签名应用于 PdfPKCS7 容器这种方式创建的pdf无效。
public byte[] CreatePKCS7ViaPkcs1()
{
//Load the certificate used for signing
signCertificatePrivateKey = LoadCertificateFromFile();
// create sha256 message digest
// This is from https://kb.itextpdf.com/home/it7kb/examples/how-to-use-a-digital-signing-service-dss-such-as-globalsign-with-itext-7
// Not sure if this is required, but the created signature is invalid either way
using (SHA256 sha256 = SHA256.Create())
{
Digest = sha256.ComputeHash(Digest);
}
//Create pkcs1 signature
byte[] signature = null;
using (var key = signCertificatePrivateKey.GetRSAPrivateKey())
{
signature = key.SignData(Digest, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
Org.BouncyCastle.X509.X509Certificate cert = DotNetUtilities.FromX509Certificate(signCertificatePrivateKey);
PdfPKCS7 sgn = new PdfPKCS7(null, new[] { cert }, "SHA256", false);
sgn.SetExternalDigest(signature, null, "RSA");
//Return the complete PKCS7 CMS
return sgn.GetEncodedPKCS7(Digest, PdfSigner.CryptoStandard.CMS, null, null, null);
}
使用 PrivateKeySignature 实现创建 PKCS7 签名
在此示例中,PKCS7 是使用 iText PrivateKeySignature 创建的。签名是使用与另一个示例中相同的摘要和相同的私钥创建的。
此处创建的pdf有效。但是由于这种方法不允许使用外部服务来创建签名,所以我不能使用它。
public byte[] CreatePKCS7ViaPkcs1()
{
//Load the certificate used for signing
signCertificatePrivateKey = LoadCertificateFromFile();
// create sha256 message digest
// This is from https://kb.itextpdf.com/home/it7kb/examples/how-to-use-a-digital-signing-service-dss-such-as-globalsign-with-itext-7
// Not sure if this is required, but the created signature is invalid either way
using (SHA256 sha256 = SHA256.Create())
{
Digest = sha256.ComputeHash(Digest);
}
//Create pkcs1 signature using RSA
byte[] signature = null;
using (var key = signCertificatePrivateKey.GetRSAPrivateKey())
{
signature = key.SignData(Digest, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
Org.BouncyCastle.X509.X509Certificate cert = DotNetUtilities.FromX509Certificate(signCertificatePrivateKey);
PdfPKCS7 sgn = new PdfPKCS7(null, new[] { cert }, "SHA256", false);
sgn.SetExternalDigest(signature, null, "RSA");
//Return the complete PKCS7 CMS
return sgn.GetEncodedPKCS7(Digest, PdfSigner.CryptoStandard.CMS, null, null, null);
}
备注(编辑)
我认为问题的原因是我没有使用 GetAuthenticatedAttributeBytes 来获取要签名的散列。但是我不能使用这种方法。时间戳、ocsp 和 CLR 作为服务调用的一部分返回。由于 GetAuthenticatedAttributeBytes 的参数必须与应用签名时的参数相同,我想我无法使用此功能。
问题
通过 RSA 创建的签名在生成的 pdf 中无效的原因是什么? 编辑:更具体地说:当签名服务 returns PKCS1、时间戳、Ocsp 和 CRL 时,如何创建有效的 pkcs7 容器。在这种情况下究竟必须签署什么?
一个错误相当明显:
在 CreatePKCS7
中,您对签名容器 (GetAuthenticatedAttributeBytes
) 的待签名属性进行签名,其中包含文档摘要 (Digest
):
var sh = sgn.GetAuthenticatedAttributeBytes(Digest, PdfSigner.CryptoStandard.CMS, null, null);
byte[] extSignature = signature.Sign(sh);
在 CreatePKCS7ViaPkcs1
中,您签署文档摘要 (Digest
) 本身:
//Create pkcs1 signature using RSA
byte[] signature = null;
using (var key = signCertificatePrivateKey.GetRSAPrivateKey())
{
signature = key.SignData(Digest, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
在这两种情况下,您都可以继续将返回的签名注入 PdfPKCS7
:
sgn.SetExternalDigest(extSignature, null, signature.GetEncryptionAlgorithm());
和
sgn.SetExternalDigest(signature, null, "RSA");
分别
第一个变体有效,是一个指标,告诉您 SetExternalDigest
期望将要签名的签名(外部签名摘要)作为第一个参数属性,而不是直接的文档摘要。
因此,在 CreatePKCS7ViaPkcs1
中,您只是简单地签署了错误的字节!
您可以通过(就像在 CreatePKCS7
中一样)在之前创建 PdfPKCS7
实例并从中接收要签名的属性(使用 GetAuthenticatedAttributeBytes
).在签名之前,您可能需要也可能不需要对此处的结果进行哈希处理 - 我不太精通 .NET 加密 API。