如何在新的签名字段中关联以前的签名

use*_*204 1 sign itext

我有一个签名的PDF。我想在文档中显示此签名。我可以这样添加一个新的签名字段:

Stamper.addSignature("My Signature", 1, 20f, 10f, 100f, 100f);
Run Code Online (Sandbox Code Playgroud)

但是我找不到将其与文档中已经存在的签名关联的方法。

如何关联?

mkl*_*mkl 5

OP希望将文档内签名可视化附加到现有签名。

首先,如果您的文件已经过认证且不允许更改,则显然不允许这样做

但是,非常令人惊讶的是,似乎确实允许已签名但未经认证的文档(该示例文件是我使用的)。

实际上,除非文档经过认证且不允许进行任何更改,否则始终允许您填写表格,并且(除非使用表单填写和数字签名进行认证的文档除外)甚至可以修改注释,请参见。此答案为概述。

由于PDF签名是表单字段的值,而表单字段的可视化是特殊的注释,因此更改签名可视化可以作为表单填写或至少作为注释修改。

在iText 5中进行操作

OP尝试通过添加新的签名字段来实现此目的:

Stamper.addSignature("My Signature", 1, 20f, 10f, 100f, 100f);
Run Code Online (Sandbox Code Playgroud)

但是,这没有帮助,因为必须更改现有签名字段,而不是创建新的签名字段。

使用iText 5.x,可以使用通用表单字段操作API来完成:

PdfReader pdfReader = new PdfReader(resource);
PdfStamper pdfStamper = new PdfStamper(pdfReader, result, '\0', true);

AcroFields acroFields = pdfStamper.getAcroFields();
for (String signatureName : acroFields.getSignatureNames())
{
    Item field = acroFields.getFieldItem(signatureName);
    field.writeToAll(PdfName.RECT, new PdfArray(new int[]{100,100,200,200}), Item.WRITE_WIDGET);
    field.markUsed(acroFields, Item.WRITE_WIDGET);

    PdfAppearance appearance = PdfAppearance.createAppearance(pdfStamper.getWriter(), 100, 100);
    appearance.setColorStroke(BaseColor.RED);
    appearance.moveTo(0, 0);
    appearance.lineTo(99, 99);
    appearance.moveTo(0, 99);
    appearance.lineTo(99, 0);
    appearance.stroke();

    PdfDictionary appDict = new PdfDictionary();
    appDict.put(PdfName.N, appearance.getIndirectReference());
    field.writeToAll(PdfName.AP, appDict, Item.WRITE_WIDGET);
}

pdfStamper.close();
Run Code Online (Sandbox Code Playgroud)

ChangeSignatureAppearance.java方法testChangeAppearances

该代码为每个集成的PDF签名创建一个新的签名外观,在本例中为100、100和100x100大小的红叉,但是您可以在此处创建任何喜欢的外观。

当心:此代码假定不可见签名已经与某个文档页面相关联。对于尚未与页面关联的不可见签名,必须建立关联。可能这可能是不允许的更改,至少它不再仅仅是表单填写了,因为表单结构也发生了变化,不仅仅是其条目。


OP在注释中指示

但我想检索标志的名称并写下它而不是红叉

为此,您只需要稍微更改上面的代码即可:

PdfReader pdfReader = new PdfReader(resource);
PdfStamper pdfStamper = new PdfStamper(pdfReader, result, '\0', true);

AcroFields acroFields = pdfStamper.getAcroFields();
for (String signatureName : acroFields.getSignatureNames())
{
    PdfPKCS7 pkcs7 = acroFields.verifySignature(signatureName);
    X509Certificate signerCert = (X509Certificate) pkcs7.getSigningCertificate();
    String signerName = CertificateInfo.getSubjectFields(signerCert).getField("CN");

    Item field = acroFields.getFieldItem(signatureName);
    field.writeToAll(PdfName.RECT, new PdfArray(new int[]{100,100,200,200}), Item.WRITE_WIDGET);
    field.markUsed(acroFields, Item.WRITE_WIDGET);

    PdfAppearance appearance = PdfAppearance.createAppearance(pdfStamper.getWriter(), 100, 100);
    ColumnText columnText = new ColumnText(appearance);
    Chunk chunk = new Chunk();
    chunk.setSkew(0, 12);
    chunk.append("Signed by:");
    columnText.addElement(new Paragraph(chunk));
    chunk = new Chunk();
    chunk.setTextRenderMode(PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE, 1, BaseColor.BLACK);
    chunk.append(signerName);
    columnText.addElement(new Paragraph(chunk));
    columnText.setSimpleColumn(0, 0, 100, 100);
    columnText.go();

    PdfDictionary appDict = new PdfDictionary();
    appDict.put(PdfName.N, appearance.getIndirectReference());
    field.writeToAll(PdfName.AP, appDict, Item.WRITE_WIDGET);
}

pdfStamper.close();
Run Code Online (Sandbox Code Playgroud)

ChangeSignatureAppearance.java方法testChangeAppearancesWithName

对于样本文档,BouncyCastle必须注册为安全提供者。

并且上面的警告仍然适用。

在iText 7中进行操作

由于iText 7最近已经发布,因此上面的代码可以像这样移植到它:

try (   PdfReader pdfReader = new PdfReader(resource);
        PdfWriter pdfWriter = new PdfWriter(result);
        PdfDocument pdfDocument = new PdfDocument(pdfReader, pdfWriter, new StampingProperties().useAppendMode()))
{
    SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);
    PdfAcroForm acroForm = PdfAcroForm.getAcroForm(pdfDocument, false);

    for (String name : signatureUtil.getSignatureNames())
    {
        PdfFormField field = acroForm.getField(name);
        field.setModified();
        for (PdfWidgetAnnotation pdfWidgetAnnotation : field.getWidgets())
        {
            pdfWidgetAnnotation.setRectangle(new PdfArray(new int[]{100, 100, 200, 200}));

            PdfFormXObject form = new PdfFormXObject(new Rectangle(100, 100));
            PdfCanvas canvas = new PdfCanvas(form, pdfDocument);
            canvas.setStrokeColor(Color.RED);
            canvas.moveTo(0, 0);
            canvas.lineTo(99, 99);
            canvas.moveTo(0, 99);
            canvas.lineTo(99, 0);
            canvas.stroke();

            pdfWidgetAnnotation.setNormalAppearance(form.getPdfObject());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

ChangeSignatureAppearance.java方法testChangeAppearances

此代码要求的iText 7个伪影kernelformssign

与上面的iText 5代码相同的警告适用:

当心:此代码假定不可见签名已经与某个文档页面相关联。


具有主题名称的变体如下所示:

try (   PdfReader pdfReader = new PdfReader(resource);
        PdfWriter pdfWriter = new PdfWriter(result);
        PdfDocument pdfDocument = new PdfDocument(pdfReader, pdfWriter, new StampingProperties().useAppendMode()))
{
    SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);
    PdfAcroForm acroForm = PdfAcroForm.getAcroForm(pdfDocument, false);

    for (String name : signatureUtil.getSignatureNames())
    {
        PdfPKCS7 pkcs7 = signatureUtil.verifySignature(name);
        X509Certificate signerCert = (X509Certificate) pkcs7.getSigningCertificate();
        String signerName = CertificateInfo.getSubjectFields(signerCert).getField("CN");
        PdfFormField field = acroForm.getField(name);
        field.setModified();
        for (PdfWidgetAnnotation pdfWidgetAnnotation : field.getWidgets())
        {
            pdfWidgetAnnotation.setRectangle(new PdfArray(new int[]{100, 100, 200, 200}));

            PdfFormXObject form = new PdfFormXObject(new Rectangle(100, 100));
            Canvas canvas = new Canvas(form, pdfDocument);
            canvas.add(new Paragraph().setItalic().add("Signed by:"));
            canvas.add(new Paragraph().setBold().add(signerName));

            pdfWidgetAnnotation.setNormalAppearance(form.getPdfObject());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

ChangeSignatureAppearance.java方法testChangeAppearancesWithName

该代码还使用了iText 7工件layout。此外,对于示例文档,BouncyCastle必须像上面的iText 5代码一样注册为安全提供程序。

同样,上面的警告仍然适用。

在Adobe Acrobat Reader DC中查看

我在Adobe Acrobat Reader DC中用无形签名的空白文档BLANK-signed.pdf进行了测试

原始空白签名PDF的屏幕截图

使用上面的代码处理了文件之后,我得到了:

添加外观的空白签名PDF的屏幕截图

关于未签名更改的警告是正确的,但是再次签名后,即使该警告也消失了:

空白的带签名的PDF(具有添加的外观并再次签名)的屏幕截图


具有签名者名称的变体如下所示:

空白签名的PDF的屏幕截图,带有带有签名者名称的添加外观

附录:签名的多次出现

OP在评论中要求

此方法仅在文档的第一页上盖章。如何在文档的所有页面中加盖印章?

实际上,上述方法通常不会在首页上标记,而是在与签名相关页面上标记。但是,由于看不见的签名通常与首页相关联,因此为什么出现它是可以理解的。

此外,单个签名字段的多次出现不受普遍支持(尽管ISO 32000-1实际上并未禁止),而即将发布的ISO 32000-2 将禁止它们出现。因此,这不是最好的主意。

但是,如果没有解决办法,则可以在iText 7中尝试以下操作:

try (   PdfReader pdfReader = new PdfReader(resource);
        PdfWriter pdfWriter = new PdfWriter(result);
        PdfDocument pdfDocument = new PdfDocument(pdfReader, pdfWriter, new StampingProperties().useAppendMode()))
{
    SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);
    PdfAcroForm acroForm = PdfAcroForm.getAcroForm(pdfDocument, false);

    for (String name : signatureUtil.getSignatureNames())
    {
        PdfPKCS7 pkcs7 = signatureUtil.verifySignature(name);
        X509Certificate signerCert = (X509Certificate) pkcs7.getSigningCertificate();
        String signerName = CertificateInfo.getSubjectFields(signerCert).getField("CN");
        PdfFormField field = acroForm.getField(name);
        field.setModified();

        Rectangle rectangle = new Rectangle(100, 100);
        PdfFormXObject form = new PdfFormXObject(rectangle);
        Canvas canvas = new Canvas(form, pdfDocument);
        canvas.add(new Paragraph().setItalic().add("Signed by:"));
        canvas.add(new Paragraph().setBold().add(signerName));

        for (PdfWidgetAnnotation pdfWidgetAnnotation : field.getWidgets())
        {
            PdfDictionary pageObject = pdfWidgetAnnotation.getPageObject();
            PdfPage page = pdfDocument.getPage(pageObject);
            page.removeAnnotation(pdfWidgetAnnotation);

            pdfWidgetAnnotation.releaseFormFieldFromWidgetAnnotation();
        }

        for (int pageNumber = 1; pageNumber <= pdfDocument.getNumberOfPages(); pageNumber++)
        {
            PdfPage pdfPage = pdfDocument.getPage(pageNumber);
            PdfWidgetAnnotation pdfWidgetAnnotation = new PdfWidgetAnnotation(rectangle);
            pdfWidgetAnnotation.setNormalAppearance(form.getPdfObject());
            pdfWidgetAnnotation.setPage(pdfPage);
            field.addKid(pdfWidgetAnnotation);
            pdfPage.addAnnotation(pdfWidgetAnnotation);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

ChangeSignatureAppearance.java方法testChangeAppearancesWithNameAllPages

这首先删除签名字段的所有现有注释,然后将新注释添加到所有字段。

就像上面一样,这将发出有关未签名更改的警告,这毕竟是正确的。

顺便说一句,如果一个人在一页上看到带有可视化文件的签名文档,并更改上面的代码而不删除原始注释,则可以轻松地将该注释的副本添加到所有页面,并且当前的Adobe Acrobat Reader甚至不显示一个警告!用于检查已签名文档中的更改的Reader代码确实很奇怪...