我已经在https://1drv.ms/u/s!Al69FgQ8jwmZbgiBMXLLM4j5sbU?e=vyGF4m上上传了示例
可以请您检查一下。我被困在最后一步。但是,请确认其他方法是否正确。
我已确认流程。所以我很清楚。
作为该数字签名PDF文档流程的一部分,我们希望使用第三方提供PDF的签名哈希。 步骤如下:
我有以下问题。
我们使用了具有签名的现有PDF,并使用iText 7获取原始内容。 这种方法正确吗? FormB.PDF具有签名,并且通过删除signaure1字段,我们得到的是原始内容。这个过程会行得通吗?
我们也尝试使用pdfsigner.getRangeStream()方法,但是在文档中并不清楚。请帮助
package com.abc.sd;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import com.itextpdf.forms.PdfAcroForm;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.signatures.SignatureUtil;
public class ItextPdf7 {
public static void main(String [] args) throws IOException, NoSuchAlgorithmException {
String filePath ="C:\\\\abc\\\\test\\\\FormB.pdf";
PdfReader reader = new PdfReader(filePath);
PdfDocument pdfDoc = new PdfDocument(reader);
PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, false);
SignatureUtil signUtil = new SignatureUtil(pdfDoc);
List<String> names = signUtil.getSignatureNames();
System.out.println("Signature Name>>>"+names);
// System.out.println("Singature Data>>"+signUtil.readSignatureData("Signature1"));
PdfReader reader1 = new PdfReader(filePath);
PdfDocument pdfDoc1 = new PdfDocument(reader1, new PdfWriter("C:\\\\\\\\abc\\\\\\\\test\\\\\\\\unsigned_latest_iext7.pdf"));
PdfAcroForm form1 = PdfAcroForm.getAcroForm(pdfDoc1, true);
form1.flattenFields();
pdfDoc1.close();
}
}
我们希望签署PDF文档。根据我的理解,这是一些步骤。
消费者会将PDF文档的摘要发送到中央系统。 PDF的摘要将排除签名部分
中央系统会将摘要(使用消费者的私钥/公钥签名?不确定)发送给消费者
消费者系统将在PDF文档的签名部分中添加摘要(可能与公钥一起使用)
能帮忙吗?
以上流程对我的理解是否正确?任何小的参考指南/链接都会对您有所帮助或提供任何流程图。
使用.Net和Java,可以完成这项工作的库是什么?都是开源的,还是付费的。 iTextSharp在这里有用吗?
如果客户打开PDF,如何进行验证?是否需要采取任何具体措施来签署文件?
请帮助。
答案 0 :(得分:1)
在这里,问题文本和下面的注释中都有很多方面和子问题。这个答案很有启发性,但是其中一些是在首先介绍一些背景之后提出的。
集成的PDF签名意味着PDF中存在许多结构:
签名AcroForm表单字段。该表单字段可以具有窗口小部件注释(可视化内容可以包含您希望放入的任何信息),但不必具有该注释。
此签名表单字段中的值。与其他表单字段不同,签名字段的值不是单纯的字符串,而是键值对的字典。内容根据签名的确切类型而有所不同。但是,在互操作类型的情况下,总是有一个 Contents 条目,其值是一个二进制字符串,其中包含实际的PKCS1 / PKCS7 / CMS / RFC3161签名或时间戳,该时间戳或时间戳覆盖了整个文件,但该二进制文件除外字符串。
(该草图有点误导性:十六进制字符串定界符'<'和'>'是签名数据的 not 部分。)
对于类型为 adbe.x509.rsa_sha1 的情况,内容条目包含PKCS1签名。签名值字典还必须包含一个包含签名证书的 Cert 条目。
如果类型为 ETSI.RFC3161 ,则 Contents 条目将包含RFC 3161时间戳记令牌。
对于类型为 ETSI.CAdES.detached , adbe.pkcs7.detached 和 adbe.pkcs7.sha1 的类型内容条目包含CMS签名容器。由于签名容器可以保存签名证书,因此不需要证书条目。
CMS签名容器可以包含“签名属性”的结构。如果是这样,则这些属性之一必须是签名的PDF字节的哈希值(请参见上文,除 Contents 值以外的所有内容)以及包装在容器中的实际签名字节对这些签名的属性进行签名。是否允许不带签名属性的变体以及另外需要哪些属性,取决于签名的确切类型。
对于 ETSI.CAdES.detached ,CMS容器必须包含签名的属性。此外,已签名属性之一必须是引用签名者证书的ESS签名证书或signing-certificate-v2属性。
在这种情况下,LTV信息可以在以后以增量更新的方式添加到PDF中,而不必出现在已签名的PDF中。
对于 adbe.pkcs7.detached 和 adbe.pkcs7.sha1 ,通常不需要签名属性。但是,根据确切的签名策略(由法律或合同规定),仍然可能需要签名属性,尤其是ESS签名证书的签名属性。
这些签名类型已在ISO 32000-1中定义。如果一个人的签名策略仅基于ISO 32000-1,则LTV信息必须存储在adbe-revocationInfoArchival属性中,该属性必须是一个签名属性。
在注释中,您引用了iText“ PDF和数字签名”电子书,该电子书似乎表明它足以与签名一起检索签名证书。
不过,根据上述背景,我们意识到
对于 adbe.x509.rsa_sha1 签名,签名证书必须位于签名值字典的 Cert 条目中。由于该条目不在 Contents 条目中,因此该证书是已签名数据的一部分。因此,必须在签名之前 知道。
对于 ETSI.CAdES.detached 签名,签名属性必须包含ESS签名证书或signing-certificate-v2属性。此属性引用签名者证书。因此,必须在签名之前 知道。
对于 adbe.pkcs7.detached 和 adbe.pkcs7.sha1 ,这取决于必须遵守的实际签名策略。 ESS signing-certificate或signing-certificate-v2属性是必需的。因此,这取决于在签名之前是否需要知道签名证书。
但是,如果仅基于ISO 32000-1进行签名,则LTV信息必须存储在完全签名的属性中,并且要检索LTV信息,很显然,人们需要知道一个试图对其进行认证的证书。检索它们,特别是签名者证书。
因此,为回答本主题标题中的问题,因此:仅在宽松的签名策略的情况下,只要不需要添加LTV信息,您就可以在不知道签名者身份的情况下逃脱签名。
在评论中,您提到您需要使用PAdES和LTV 。这是否意味着您在签名之前需要签名者证书?
嗯,这取决于。
如果使用PAdES 意味着使用PAdES基线配置文件或扩展的PAdES配置文件(BES / EPES),则必须创建 ETSI.CAdES.detached 签名。因此,您在签名之前确实需要签名者证书。
但是,如果只需要PAdES配置文件来进行PDF中的CMS数字签名(本质上是ISO 32000-1兼容性配置文件),则不需要签名者证书。
但是此配置文件特别暗示:如果存在,则任何吊销信息都应是PDF签名的签名属性。因此,对于“ PAdES和LTV”,您再次需要签名之前的签名者证书。
因此,在某些设置中,在计算实际签名之前不需要签署者证书。通常,尽管如此,安全API仍然需要尽早提供证书。
使用Bouncy Castle低级API,您可以执行以下操作。 (我假设您正在使用SHA256withRSA。)
首先准备PDF并确定哈希值
byte[] Hash = null;
using (PdfReader reader = new PdfReader("original.pdf"))
using (FileStream fout = new FileStream("prepared.pdf", FileMode.Create))
{
StampingProperties sp = new StampingProperties();
sp.UseAppendMode();
PdfSigner pdfSigner = new PdfSigner(reader, fout, sp);
pdfSigner.SetFieldName("Signature");
PdfSignatureAppearance appearance = pdfSigner.GetSignatureAppearance();
appearance.SetPageNumber(1);
int estimatedSize = 12000;
ExternalHashingSignatureContainer container = new ExternalHashingSignatureContainer(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
pdfSigner.SignExternalContainer(container, estimatedSize);
Hash = container.Hash;
}
现在要签名的PDF字节的哈希值位于Hash
中。
此处使用的ExternalHashingSignatureContainer
类是以下帮助程序类:
public class ExternalHashingSignatureContainer : ExternalBlankSignatureContainer
{
public ExternalHashingSignatureContainer(PdfName filter, PdfName subFilter) : base(filter, subFilter)
{ }
public override byte[] Sign(Stream data)
{
SHA256 sha = new SHA256CryptoServiceProvider();
Hash = sha.ComputeHash(data);
return new byte[0];
}
public byte[] Hash { get; private set; }
}
对于上面在Hash
变量中计算出的哈希,您现在可以请求PKCS#1签名和签名者证书。然后,您可以按以下方式构造CMS容器:
byte[] signatureBytes = THE_RETRIEVED_SIGNATURE_BYTES;
byte[] certificateBytes = THE_RETRIEVED_CERTIFICATE_BYTES;
X509Certificate x509Certificate = new X509CertificateParser().ReadCertificate(certificateBytes);
SignerIdentifier sid = new SignerIdentifier(new IssuerAndSerialNumber(x509Certificate.IssuerDN, x509Certificate.SerialNumber));
AlgorithmIdentifier digAlgorithm = new AlgorithmIdentifier(NistObjectIdentifiers.IdSha256);
Attributes authenticatedAttributes = null;
AlgorithmIdentifier digEncryptionAlgorithm = new AlgorithmIdentifier(Org.BouncyCastle.Asn1.Pkcs.PkcsObjectIdentifiers.Sha256WithRsaEncryption);
Asn1OctetString encryptedDigest = new DerOctetString(signatureBytes);
Attributes unauthenticatedAttributes = null;
SignerInfo signerInfo = new SignerInfo(sid, digAlgorithm, authenticatedAttributes, digEncryptionAlgorithm, encryptedDigest, unauthenticatedAttributes);
Asn1EncodableVector digestAlgs = new Asn1EncodableVector();
digestAlgs.Add(signerInfo.DigestAlgorithm);
Asn1Set digestAlgorithms = new DerSet(digestAlgs);
ContentInfo contentInfo = new ContentInfo(CmsObjectIdentifiers.Data, null);
Asn1EncodableVector certs = new Asn1EncodableVector();
certs.Add(x509Certificate.CertificateStructure.ToAsn1Object());
Asn1Set certificates = new DerSet(certs);
Asn1EncodableVector signerInfs = new Asn1EncodableVector();
signerInfs.Add(signerInfo);
Asn1Set signerInfos = new DerSet(signerInfs);
SignedData signedData = new SignedData(digestAlgorithms, contentInfo, certificates, null, signerInfos);
contentInfo = new ContentInfo(CmsObjectIdentifiers.SignedData, signedData);
byte[] Signature = contentInfo.GetDerEncoded();
现在CMS签名容器字节位于Signature
中。
对于上述内容,请使用这些BouncyCastle using
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Cms;
using Org.BouncyCastle.Asn1.Nist;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Signers;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.X509;
您现在可以像这样将签名容器字节嵌入到PDF中:
using (PdfReader reader = new PdfReader("prepared.pdf"))
using (PdfDocument document = new PdfDocument(reader))
using (FileStream fout = new FileStream("signed.pdf", FileMode.Create))
{
PdfSigner.SignDeferred(document, "Signature", fout, new ExternalPrecalculatedSignatureContainer(Signature));
}
此处使用的ExternalPrecalculatedSignatureContainer
类是以下帮助程序类:
public class ExternalPrecalculatedSignatureContainer : ExternalBlankSignatureContainer
{
public ExternalPrecalculatedSignatureContainer(byte[] cms) : base(new PdfDictionary())
{
Cms = cms;
}
public override byte[] Sign(Stream data)
{
return Cms;
}
public byte[] Cms { get; private set; }
}
但是,如上所述,此签名容器不是CAdES容器。因此,您的PDF签名将不是真正的PAdES签名(基准或扩展配置文件),但最多只能是ISO 32000-1兼容性的PAdES签名。
您的Client
方法createSignedData
如下所示:
public byte[] createSignedData(byte[] sh)
{
string dire = Directory.GetParent(Directory.GetParent(Directory.GetCurrentDirectory()).ToString()).ToString();
string PROPERTIES = dire + "\\resources\\signkey.properties";
Properties properties = new Properties();
properties.Load(new FileStream(PROPERTIES, FileMode.Open, FileAccess.Read));
String path = properties.GetProperty("PRIVATE");
char[] pass = properties.GetProperty("PASSWORD").ToCharArray();
string alias = null;
Pkcs12Store pk12;
pk12 = new Pkcs12Store(new FileStream(path, FileMode.Open, FileAccess.Read), pass);
foreach (var a in pk12.Aliases)
{
alias = ((string)a);
if (pk12.IsKeyEntry(alias))
break;
}
ICipherParameters pk = pk12.GetKey(alias).Key;
IExternalSignature pks = new PrivateKeySignature(pk, DigestAlgorithms.SHA256);
byte[] data = pks.Sign(sh);
return data;
}
不幸的是,PrivateKeySignature.Sign
希望消息登录sh
参数,尤其是首先对其进行哈希处理。另一方面,在您的用例中,sh
已经是要签名的消息的哈希。因此,您可以有效地对应该进行哈希处理的位置进行两次哈希处理,而只需一次。
您可以通过替换
来解决此问题IExternalSignature pks = new PrivateKeySignature(pk, DigestAlgorithms.SHA256);
byte[] data = pks.Sign(sh);
在上面的代码中
StaticDigest digest = new StaticDigest();
digest.AlgorithmName = "SHA-256";
digest.Digest = sh;
RsaDigestSigner signer = new RsaDigestSigner(digest);
signer.Init(true, pk);
byte[] data = signer.GenerateSignature();
StaticDigest
是以下帮助程序类:
public class StaticDigest : IDigest
{
public string AlgorithmName { get; set; }
public byte[] Digest { get; set; }
public void BlockUpdate(byte[] input, int inOff, int length)
{ }
public int DoFinal(byte[] output, int outOff)
{
Array.Copy(Digest, 0, output, outOff, Digest.Length);
return Digest.Length;
}
public int GetByteLength()
{
return 64;
}
public int GetDigestSize()
{
return Digest.Length;
}
public void Reset()
{ }
public void Update(byte input)
{ }
}
此更改后,您的测试项目将返回数学上有效的签名。