如何使用 iText 在所有文档的页面中显示数字 PDF 签名?
How to show digital PDF signature in all document's Page using iText?
几天来我一直在研究数字签名功能,现在一切正常,是时候尝试在所有页面上打印图章了,但我做的不是很好...
尝试提供一份快速简历,以显示图章我所做的是创建 PdfStamper、PdfSignatureAppearance 和一个 Rectangle,然后调用
appearance.setVisibleSignature(rectangle, 1, "SIGNATURE")
上面的第二个参数“1”是我要显示图章的页码,现在可以是1,因为我试图在其他页面显示图章时正在创建其他实例PdfStamper、PdfSignatureAppearance 和一个矩形,但将其设置为第 2 页。如果它有效,我会把它放在一个循环中并不断更改页面参数。
但是为什么没有用呢???好吧,在接近尾声时,我调用了 MakeSignature 的方法,并且在我必须传递我创建的外观之一的参数中,如果我多次调用它,签名只出现在与我传递给它的最后一次外观相关的页面上。
例如:
MakeSignature.signDetached(appearance2, digest, pks, chain, null, null, null, 0, CryptoStandard.CMS);
MakeSignature.signDetached(appearance, digest, pks, chain, null, null, null, 0, CryptoStandard.CMS);
邮票只会出现在第一页。
也许我可以在这里得到一些帮助???
这就是全部:
public String signPdfFirstTime(String src, String dest, PrivateKey pk, Certificate[] chain, String providerName, String conteudoBase64, X509Certificate cert, String alias) throws IOException, DocumentException, GeneralSecurityException
{
byte[] conteudoBinario = Base64.decode(conteudoBase64);
FileOutputStream fos = new FileOutputStream(path + File.separator + src);
fos.write(conteudoBinario);
fos.close();
File f = new File(path + File.separator + src);
FileInputStream in = new FileInputStream(f);
PdfReader reader = new PdfReader(in);
int qtypages = reader.getNumberOfPages();
FileOutputStream os = new FileOutputStream(path + File.separator + dest);
PdfStamper stamper = PdfStamper.createSignature(reader, os, '[=12=]');
// Creating the appearance
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
Rectangle rectangle = new Rectangle(550, 50, 610, 500);// funciona vertical
appearance.setVisibleSignature(rectangle, 1, "SIGNATURE");
//Here I build a custom message...nothing relevant
StringBuilder stampMessage = new StringBuilder();
stampMessage.append("...");
stampMessage.append(alias);
stampMessage.append(" - ");
// customize appearance layer 2 to display text vertically
PdfTemplate layer2 = appearance.getLayer(2);
layer2.transform(new AffineTransform(0, 1, -1, 0, rectangle.getWidth(), 0));
Font font = new Font();
font.setColor(BaseColor.BLACK);
ColumnText ct2 = new ColumnText(layer2);
ct2.setRunDirection(PdfWriter.RUN_DIRECTION_NO_BIDI);
ct2.setSimpleColumn(new Phrase(stampMessage.toString(), font), 0, 0, rectangle.getHeight(), rectangle.getWidth(), 15, Element.ALIGN_LEFT);
ct2.go();
appearance.setCertificate(cert);
//Here starts where I tried to make a second stamp to show in the page 2
FileOutputStream fos2 = new FileOutputStream(path + File.separator + src);
fos2.write(conteudoBinario);
fos2.close();
File f2 = new File(path + File.separator + src);
FileInputStream in2 = new FileInputStream(f2);
PdfReader reader2 = new PdfReader(in2);
FileOutputStream os2 = new FileOutputStream(path + File.separator + dest);
PdfStamper stamper2 = PdfStamper.createSignature(reader2, os2, '[=12=]');
// Creating the appearance
PdfSignatureAppearance appearance2 = stamper2.getSignatureAppearance();
Rectangle rectangle2 = new Rectangle(550, 50, 610, 500);// funciona vertical
appearance2.setVisibleSignature(rectangle2, 3, "ASSINATURA2");
//Cria a msg que aparece na estampa
StringBuilder stampMessage2 = new StringBuilder();
stampMessage2.append(" - ");
PdfTemplate layer22 = appearance.getLayer(2);
layer22.transform(new AffineTransform(0, 1, -1, 0, rectangle2.getWidth(), 0));
Font font2 = new Font();
font2.setColor(BaseColor.BLACK);
ColumnText ct22 = new ColumnText(layer22);
ct22.setRunDirection(PdfWriter.RUN_DIRECTION_NO_BIDI);
ct22.setSimpleColumn(new Phrase(stampMessage2.toString(), font2), 0, 0, rectangle2.getHeight(), rectangle2.getWidth(), 15, Element.ALIGN_LEFT);
ct22.go();
appearance2.setCertificate(cert);
// Creating the signature
ExternalSignature pks = new PrivateKeySignature(pk, DigestAlgorithms.SHA256, providerName);
ExternalDigest digest = new BouncyCastleDigest();
List<CrlClient> crlList = new ArrayList<CrlClient>();
crlList.add(new CrlClientOnline());
LtvVerification v = stamper.getLtvVerification();
LtvVerification v2 = stamper2.getLtvVerification();
OcspClient ocspClient = new OcspClientBouncyCastle();
String url = CertificateUtil.getCRLURL(cert);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509CRL crl = (X509CRL) cf.generateCRL(new URL(url).openStream());
System.out.println("CRL valid until: " + crl.getNextUpdate());
System.out.println("Certificate revoked: " + crl.isRevoked(chain[0]));
if (crl.isRevoked(chain[0])) {
throw new GeneralSecurityException("CERTIFICADO REVOGADO!");
}
else {
MakeSignature.processCrl(cert, crlList);
MakeSignature.signDetached(appearance2, digest, pks, chain, null, null, null, 0, CryptoStandard.CMS);
MakeSignature.signDetached(appearance, digest, pks, chain, null, null, null, 0, CryptoStandard.CMS);
os.close();
byte[] b = this.read(f);
return Base64.encodeBytes(b);
}
}
这实际上是对可用选项的讨论...
在所有页面上打印签名戳记有根本不同的方法:
- 创建具有多个可视化效果的单个签名字段,每页一个。
- 在单个页面上创建具有单个可视化效果的单个签名字段,例如最后一个文档页面,并进一步在所有其他页面上创建具有相同内容的图像。
- 为每个页面创建一个签名字段,每个页面上都有一个可视化效果。
"Visualizations" 的 PDF 签名字段是与该字段直接关联的小部件;特别是可以单击它们以打开签名验证对话框。与这些小部件相比,第二个选项中的 "images" 只是没有此类关联操作的图像。
单一签名,多重可视化
这很可能是 OP 想到的选项。特别是这是更可取的选择,至少乍一看:
- 在每个页面上,签名图像都处于活动状态并允许打开签名验证对话框;
- 但仍然只需要创建一个数字签名,这意味着
- 在 Adobe Reader、
左侧的签名面板上只有一个条目
- 只验证一个签名容器(因此,没有明确的验证结果),并且
- 私钥只能使用一次,因此不需要多次输入 PIN,如果是按签名付费签名服务,只需支付一次签名事件。
不过也有一些缺点:
同一签名的多次可视化可能对该签名的法律价值产生负面影响。
因此,Adobe 几年前决定不在其软件中创建具有多个可视化效果的签名字段,参见。例如
The location of a signature within a document can have a bearing on its legal meaning. For this reason,
signature fields never refer to more than one annotation. If more than one location is associated with a
signature, the meaning may become ambiguous.
(Digital Signature Appearances whitepaper for Adobe Acrobat version 9 dated May 5, 2008)
例如在德国,关于书面签名的管辖权垂直地限制了签名者合法签署的文件部分,他通常不受签名下方所写的任何内容的法律约束。其他法律体系中也可能存在类似的管辖权。
如果电子签名在签名文件中具有可视化效果,则此类管辖权可能类似(或者至少必须付出相当大的努力来解释差异)。在同一签名的多个可视化的情况下,这可能意味着只有第一个可视化之前的所有内容都被视为已签名。
(本人不是律师,请不要考虑本次法律咨询。)
由于这些潜在的法律问题,即将推出的 PDF 2.0 标准中的签名字段将只允许有一个小部件。因此,根据该标准,具有多个小部件的签名可能会被视为无效。
Adobe Reader 的签名面板现在已经包含了 "the page the signature is on",cf.此屏幕截图的最后一行:
该面板上没有关联条目(具有正确页码)的活动签名字段可能会被彻底怀疑。
单个签名,最后一页上的单个可视化,其他页面上的非活动图像
在使用此选项的情况下,先前选项的缺点不适用或至少仅在较小程度上适用。特别是如果单纯的图像与可视化略有不同,提示表明它们是副本,则最终的真实可视化可能会被视为绑定签名位置。
但是,此选项的主要缺点是,对于已签名的文档,不允许仅向内容中添加图像。因此,此选项不能用于文档的第二个或第三个签名者,但 OP 表示解决方案最终必须 允许文档被签名给多个人 .
可以考虑将这些图像添加为注释,而不是内容;对于某些类型的集成 PDF 签名,在签名后添加和删除注释是允许的操作。但是,如果允许添加这些注释,通常也允许在签名后再次删除它们,从而使这些签名图像非常不稳定。
多个签名(每页一个),每个签名有一个可视化
此选项没有其他选项的缺点,因为每个可视化对应不同的数字签名。因此,最后一个保证签名者受整个文档的法律约束。
它确实有其自身的缺点,但是:
- 在验证期间将验证所有这些签名。这可能意味着高资源需求,甚至更糟糕的结果不明确(如果某些验证失败而某些验证成功)。
- Adobe Reader 签名面板充满了条目。
- 私钥被多次使用,如果按签名付费签名服务很昂贵,如果是 SSCD(特别是智能卡或令牌)可能需要多次输入 PIN 并且也需要相当多的时间时间
实施选项
iText 允许以开箱即用的相当直接的方式实施第二个和第三个选项。
使用 iText 可以实现第一个选项,但需要使用低级 API 和 Java 反射或者稍微修补 iText。
不过,考虑到每个选项的问题,我建议完全不要这样做,在要签名的内容末尾签名是最明确的签名方式。
@mkl 在法律问题上的观点非常可观。但是如果你仍然想用 Itext api (version 5.5.*) 签署所有页面,你应该在 PdfSignatureAppearance
class 的 preClose(HashMap<PdfName, Integer> exclusionSizes)
方法中做一些 hack签名外观包含在页面中。
在 PdfSignatureAppearance
class 中搜索 writer.addAnnotation(sigField, pagen);
行并替换为
for (int p = 1; p <= writer.reader.getNumberOfPages(); p++) {
writer.addAnnotation(sigField, p);
}
将签名引用添加到所有页面。
几天来我一直在研究数字签名功能,现在一切正常,是时候尝试在所有页面上打印图章了,但我做的不是很好...
尝试提供一份快速简历,以显示图章我所做的是创建 PdfStamper、PdfSignatureAppearance 和一个 Rectangle,然后调用
appearance.setVisibleSignature(rectangle, 1, "SIGNATURE")
上面的第二个参数“1”是我要显示图章的页码,现在可以是1,因为我试图在其他页面显示图章时正在创建其他实例PdfStamper、PdfSignatureAppearance 和一个矩形,但将其设置为第 2 页。如果它有效,我会把它放在一个循环中并不断更改页面参数。
但是为什么没有用呢???好吧,在接近尾声时,我调用了 MakeSignature 的方法,并且在我必须传递我创建的外观之一的参数中,如果我多次调用它,签名只出现在与我传递给它的最后一次外观相关的页面上。
例如:
MakeSignature.signDetached(appearance2, digest, pks, chain, null, null, null, 0, CryptoStandard.CMS);
MakeSignature.signDetached(appearance, digest, pks, chain, null, null, null, 0, CryptoStandard.CMS);
邮票只会出现在第一页。
也许我可以在这里得到一些帮助???
这就是全部:
public String signPdfFirstTime(String src, String dest, PrivateKey pk, Certificate[] chain, String providerName, String conteudoBase64, X509Certificate cert, String alias) throws IOException, DocumentException, GeneralSecurityException
{
byte[] conteudoBinario = Base64.decode(conteudoBase64);
FileOutputStream fos = new FileOutputStream(path + File.separator + src);
fos.write(conteudoBinario);
fos.close();
File f = new File(path + File.separator + src);
FileInputStream in = new FileInputStream(f);
PdfReader reader = new PdfReader(in);
int qtypages = reader.getNumberOfPages();
FileOutputStream os = new FileOutputStream(path + File.separator + dest);
PdfStamper stamper = PdfStamper.createSignature(reader, os, '[=12=]');
// Creating the appearance
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
Rectangle rectangle = new Rectangle(550, 50, 610, 500);// funciona vertical
appearance.setVisibleSignature(rectangle, 1, "SIGNATURE");
//Here I build a custom message...nothing relevant
StringBuilder stampMessage = new StringBuilder();
stampMessage.append("...");
stampMessage.append(alias);
stampMessage.append(" - ");
// customize appearance layer 2 to display text vertically
PdfTemplate layer2 = appearance.getLayer(2);
layer2.transform(new AffineTransform(0, 1, -1, 0, rectangle.getWidth(), 0));
Font font = new Font();
font.setColor(BaseColor.BLACK);
ColumnText ct2 = new ColumnText(layer2);
ct2.setRunDirection(PdfWriter.RUN_DIRECTION_NO_BIDI);
ct2.setSimpleColumn(new Phrase(stampMessage.toString(), font), 0, 0, rectangle.getHeight(), rectangle.getWidth(), 15, Element.ALIGN_LEFT);
ct2.go();
appearance.setCertificate(cert);
//Here starts where I tried to make a second stamp to show in the page 2
FileOutputStream fos2 = new FileOutputStream(path + File.separator + src);
fos2.write(conteudoBinario);
fos2.close();
File f2 = new File(path + File.separator + src);
FileInputStream in2 = new FileInputStream(f2);
PdfReader reader2 = new PdfReader(in2);
FileOutputStream os2 = new FileOutputStream(path + File.separator + dest);
PdfStamper stamper2 = PdfStamper.createSignature(reader2, os2, '[=12=]');
// Creating the appearance
PdfSignatureAppearance appearance2 = stamper2.getSignatureAppearance();
Rectangle rectangle2 = new Rectangle(550, 50, 610, 500);// funciona vertical
appearance2.setVisibleSignature(rectangle2, 3, "ASSINATURA2");
//Cria a msg que aparece na estampa
StringBuilder stampMessage2 = new StringBuilder();
stampMessage2.append(" - ");
PdfTemplate layer22 = appearance.getLayer(2);
layer22.transform(new AffineTransform(0, 1, -1, 0, rectangle2.getWidth(), 0));
Font font2 = new Font();
font2.setColor(BaseColor.BLACK);
ColumnText ct22 = new ColumnText(layer22);
ct22.setRunDirection(PdfWriter.RUN_DIRECTION_NO_BIDI);
ct22.setSimpleColumn(new Phrase(stampMessage2.toString(), font2), 0, 0, rectangle2.getHeight(), rectangle2.getWidth(), 15, Element.ALIGN_LEFT);
ct22.go();
appearance2.setCertificate(cert);
// Creating the signature
ExternalSignature pks = new PrivateKeySignature(pk, DigestAlgorithms.SHA256, providerName);
ExternalDigest digest = new BouncyCastleDigest();
List<CrlClient> crlList = new ArrayList<CrlClient>();
crlList.add(new CrlClientOnline());
LtvVerification v = stamper.getLtvVerification();
LtvVerification v2 = stamper2.getLtvVerification();
OcspClient ocspClient = new OcspClientBouncyCastle();
String url = CertificateUtil.getCRLURL(cert);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509CRL crl = (X509CRL) cf.generateCRL(new URL(url).openStream());
System.out.println("CRL valid until: " + crl.getNextUpdate());
System.out.println("Certificate revoked: " + crl.isRevoked(chain[0]));
if (crl.isRevoked(chain[0])) {
throw new GeneralSecurityException("CERTIFICADO REVOGADO!");
}
else {
MakeSignature.processCrl(cert, crlList);
MakeSignature.signDetached(appearance2, digest, pks, chain, null, null, null, 0, CryptoStandard.CMS);
MakeSignature.signDetached(appearance, digest, pks, chain, null, null, null, 0, CryptoStandard.CMS);
os.close();
byte[] b = this.read(f);
return Base64.encodeBytes(b);
}
}
这实际上是对可用选项的讨论...
在所有页面上打印签名戳记有根本不同的方法:
- 创建具有多个可视化效果的单个签名字段,每页一个。
- 在单个页面上创建具有单个可视化效果的单个签名字段,例如最后一个文档页面,并进一步在所有其他页面上创建具有相同内容的图像。
- 为每个页面创建一个签名字段,每个页面上都有一个可视化效果。
"Visualizations" 的 PDF 签名字段是与该字段直接关联的小部件;特别是可以单击它们以打开签名验证对话框。与这些小部件相比,第二个选项中的 "images" 只是没有此类关联操作的图像。
单一签名,多重可视化
这很可能是 OP 想到的选项。特别是这是更可取的选择,至少乍一看:
- 在每个页面上,签名图像都处于活动状态并允许打开签名验证对话框;
- 但仍然只需要创建一个数字签名,这意味着
- 在 Adobe Reader、 左侧的签名面板上只有一个条目
- 只验证一个签名容器(因此,没有明确的验证结果),并且
- 私钥只能使用一次,因此不需要多次输入 PIN,如果是按签名付费签名服务,只需支付一次签名事件。
不过也有一些缺点:
同一签名的多次可视化可能对该签名的法律价值产生负面影响。
因此,Adobe 几年前决定不在其软件中创建具有多个可视化效果的签名字段,参见。例如
The location of a signature within a document can have a bearing on its legal meaning. For this reason, signature fields never refer to more than one annotation. If more than one location is associated with a signature, the meaning may become ambiguous.
(Digital Signature Appearances whitepaper for Adobe Acrobat version 9 dated May 5, 2008)
例如在德国,关于书面签名的管辖权垂直地限制了签名者合法签署的文件部分,他通常不受签名下方所写的任何内容的法律约束。其他法律体系中也可能存在类似的管辖权。
如果电子签名在签名文件中具有可视化效果,则此类管辖权可能类似(或者至少必须付出相当大的努力来解释差异)。在同一签名的多个可视化的情况下,这可能意味着只有第一个可视化之前的所有内容都被视为已签名。
(本人不是律师,请不要考虑本次法律咨询。)
由于这些潜在的法律问题,即将推出的 PDF 2.0 标准中的签名字段将只允许有一个小部件。因此,根据该标准,具有多个小部件的签名可能会被视为无效。
Adobe Reader 的签名面板现在已经包含了 "the page the signature is on",cf.此屏幕截图的最后一行:
该面板上没有关联条目(具有正确页码)的活动签名字段可能会被彻底怀疑。
单个签名,最后一页上的单个可视化,其他页面上的非活动图像
在使用此选项的情况下,先前选项的缺点不适用或至少仅在较小程度上适用。特别是如果单纯的图像与可视化略有不同,提示表明它们是副本,则最终的真实可视化可能会被视为绑定签名位置。
但是,此选项的主要缺点是,对于已签名的文档,不允许仅向内容中添加图像。因此,此选项不能用于文档的第二个或第三个签名者,但 OP 表示解决方案最终必须 允许文档被签名给多个人 .
可以考虑将这些图像添加为注释,而不是内容;对于某些类型的集成 PDF 签名,在签名后添加和删除注释是允许的操作。但是,如果允许添加这些注释,通常也允许在签名后再次删除它们,从而使这些签名图像非常不稳定。
多个签名(每页一个),每个签名有一个可视化
此选项没有其他选项的缺点,因为每个可视化对应不同的数字签名。因此,最后一个保证签名者受整个文档的法律约束。
它确实有其自身的缺点,但是:
- 在验证期间将验证所有这些签名。这可能意味着高资源需求,甚至更糟糕的结果不明确(如果某些验证失败而某些验证成功)。
- Adobe Reader 签名面板充满了条目。
- 私钥被多次使用,如果按签名付费签名服务很昂贵,如果是 SSCD(特别是智能卡或令牌)可能需要多次输入 PIN 并且也需要相当多的时间时间
实施选项
iText 允许以开箱即用的相当直接的方式实施第二个和第三个选项。
使用 iText 可以实现第一个选项,但需要使用低级 API 和 Java 反射或者稍微修补 iText。
不过,考虑到每个选项的问题,我建议完全不要这样做,在要签名的内容末尾签名是最明确的签名方式。
@mkl 在法律问题上的观点非常可观。但是如果你仍然想用 Itext api (version 5.5.*) 签署所有页面,你应该在 PdfSignatureAppearance
class 的 preClose(HashMap<PdfName, Integer> exclusionSizes)
方法中做一些 hack签名外观包含在页面中。
在 PdfSignatureAppearance
class 中搜索 writer.addAnnotation(sigField, pagen);
行并替换为
for (int p = 1; p <= writer.reader.getNumberOfPages(); p++) {
writer.addAnnotation(sigField, p);
}
将签名引用添加到所有页面。