我有签名的PDF。我想在文档中显示此签名。我可以这样添加一个新的签名字段:
Stamper.addSignature("My Signature", 1, 20f, 10f, 100f, 100f);
但是我找不到将它与文档中已有的签名相关联的方法。
我如何关联它?
答案 0 :(得分:5)
OP希望将文档内签名可视化附加到现有签名。
首先,如果您的文件已经认证且不允许更改,则显然不允许这样做
然而,令人惊讶的是,似乎允许已签名但未经证明的文档(我使用的是示例文件)。
实际上,除非文件已经认证且不允许更改,否则您始终可以填写表格和(通过表格填写认证的文件除外) -in和数字签名允许)甚至修改注释, cf. this answer了解概述。
由于PDF签名是表单字段值,表单字段的可视化是特殊的注释,允许更改签名可视化作为表单填写或至少作为注释修改。
OP尝试通过添加新的签名字段来实现此目的:
Stamper.addSignature("My Signature", 1, 20f, 10f, 100f, 100f);
但这无济于事,因为必须更改现有的签名字段,而不是创建新的签名字段。
使用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();
(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();
(ChangeSignatureAppearance.java方法testChangeAppearancesWithName
)
如果样本文件BouncyCastle必须注册为安全提供者。
上面的警告仍然适用。
由于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());
}
}
}
(ChangeSignatureAppearance.java方法testChangeAppearances
)
此代码需要iText 7工件kernel
,forms
和sign
。
与上述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());
}
}
}
(ChangeSignatureAppearance.java方法testChangeAppearancesWithName
)
此代码另外使用iText 7工件layout
。此外,如果样本文件BouncyCastle必须注册为安全提供者,就像上面的iText 5代码一样。
上面的警告仍然适用。
我在Adobe Acrobat Reader DC中使用无形签名的空白文档BLANK-signed.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);
}
}
}
(ChangeSignatureAppearance.java方法testChangeAppearancesWithNameAllPages
)
首先删除签名字段的所有现有注释,然后将新的注释添加到所有字段。
就像上面一样,这会发出关于无符号更改的警告,这毕竟是真的。
顺便说一句,如果在一个页面上使用带有可视化的签名文档并更改上面的代码而不删除原始注释,则可以轻松地将该注释的副本添加到所有页面,并且当前的Adobe Acrobat Reader可以甚至没有显示警告!用于检查已签名文档中的更改的Reader代码确实很奇怪......