如何使用iText在所有文档页面中显示数字PDF签名?

我已经做了几天的数字签名function,现在我已经完成了所有工作,现在是时候尝试在所有页面上打印邮票,但我做得不好……

试图给出一个简短的简历,以显示我做的印章是创建PdfStamper,PdfSignatureAppearance和一个矩形,然后调用

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, '\0'); // 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, '\0'); // 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 crlList = new ArrayList(); 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); } } 

这实际上是对可用选项的讨论……

在所有页面中 打印签名图章的方式根本不同:

  • 创建具有多个可视化的单个签名字段,每页一个。
  • 在单个页面上创建单个可视化字段,例如最后一个文档页面,并在所有其他页面上创建具有相同内容的图像。
  • 每页创建一个签名字段,每个字段在其页面上具有单个可视化。

PDF签名字段的“可视化”是与该字段直接相关的小部件; 特别是可以点击它们打开一个签名validation对话框。 与这些小部件相比,第二选项中的“图像”仅仅是没有这种相关动作的图像。

单一签名,多个可视化

这很可能是OP在他心目中的选择。 特别是这是一个更好的选择,至少乍一看:

  • 在每个页面上,签名图像处于活动状态,并允许打开签名validation对话框;
  • 但仍然只需要创建一个数字签名,这意味着
    • Adobe Reader左侧的签名面板上只有一个条目,
    • 只需要validation一个签名容器(因此,没有明确的validation结果),以及
    • 只使用一次私钥,因此不需要多次输入PIN,并且在每次签名付费签名服务的情况下,只需要支付一个签名事件。

但是有许多缺点:

  • 同一签名的多个可视化可能会对该签名的合法价值产生负面影响。

    因此,Adobe多年前决定不在他们的软件中创建具有多个可视化的签名字段,参见 例如

    签名在文档中的位置可能与其法律含义有关。 因此,签名字段从不引用多个注释。 如果多个位置与签名相关联,则含义可能变得模糊。

    (2008年5月5日Adobe Acrobat版本9的数字签名外观白皮书)

    例如,在德国,书面签名的管辖权垂直限制签名者合法签署的文件部分,他通常不受签名下面的任何内容的法律约束。 其他法律制度也可能存在类似的管辖权。

    如果签名文件中有可视化的电子签名,则此类管辖权可能类似地持有(或者至少必须努力解释差异)。 在多个可视化相同签名的情况下,这可能意味着只有第一个可视化的所有内容都被视为已签名。

    (我不是律师,所以请不要考虑这个法律咨询。)

  • 由于存在这样的潜在法律问题,即将发布的PDF 2.0标准中的签名字段将仅允许具有单个小部件。 因此,根据该标准,具有多个小部件的签名可能被认为是无效的。

  • 现在,Adobe Reader的签名面板已经包含“签名所在的页面”,参见 这个屏幕截图的最后一行:

    样本面板条目

    在该面板上没有相关条目(具有正确的页码)的活动签名字段可能会被完全怀疑。

最后一页上的单一签名,单一可视化,其他页面上的非活动图像

在使用该选项的情况下,先前选项的缺点不适用或至少仅在较小程度上适用。 特别是如果仅仅通过提示指示它们是副本的图像略微不同于可视化,则最终的真实可视化将可能被认为是绑定签名位置。

但是,此选项的主要缺点是,对于已签名的文档,不允许仅向内容添加图像。 因此,此选项不能用于文档的第二个或第三个签名者,但OP已指示解决方案最终必须允许将文档签名给多个人

可以考虑将这些图像添加为注释,而不是内容; 对于某些类型的集成PDF签名,在签名后添加和删除注释是允许的操作。 但是,如果允许添加这些注释,通常也允许在签名后再次删除它们,使这些签名图像非常不稳定。

多个签名(每页一个),每个都有一个可视化

此选项没有其他选项的缺点,因为每个可视化对应于不同的数字签名。 因此,最后一个保证签名者在整个文档中受法律约束。

但它确实有自己的缺点:

  • 在validation期间,将validation所有这些签名。 这可能意味着高资源需求,甚至更糟糕的结果(如果某些validation失败而某些validation成功)。
  • Adobe Reader签名面板上充满了条目。
  • 私钥被多次使用,在签名付费签名服务的情况下是昂贵的,并且在SSCD(特别是智能卡或令牌)的情况下可能需要多次输入PIN并且还需要相当长的时间

实施选项

iText允许以开箱即用的相当直接的方式实现第二和第三选项。

使用iText可以实现第一个选项,但需要使用低级API和Javareflection,或者稍微修补iText。


但是,考虑到每个选项的问题,我建议不要这样做,在签名内容末尾的一个签名是最不模糊的签名方式。

@mkl关于法律问题的要点非常可观。 但是如果你仍然希望使用Itext api(版本5.5。*)对所有页面进行签名,那么你应该对PdfSignatureAppearance类的preClose(HashMap exclusionSizes)方法进行一些PdfSignatureAppearance ,其中签名外观包含在其中页面。

搜索writer.addAnnotation(sigField, pagen);PdfSignatureAppearance类中的行并替换为

 for (int p = 1; p <= writer.reader.getNumberOfPages(); p++) { writer.addAnnotation(sigField, p); } 

它将签名的引用添加到所有页面。