I created code that adds an image to an existing pdf document and then signs it, all using PDFBox (see code below).
The code nicely adds the image and the signature. However, in some documents, Acrobat Reader complains that "The signature byte range is invalid."
The problem seems to be the same as the problem described in this question. The answer to that question describes the problem in more detail: the problem is that my code leaves a mix of cross reference types in the document (streams and tables). Indeed, some documents won't even open because of the problems that this creates.
My question is: how do I prevent this? How do I add an image to an existing pdf document without creating multiple cross reference types?
public class TC3 implements SignatureInterface{
private char[] pin = "123456".toCharArray();
private BouncyCastleProvider provider = new BouncyCastleProvider();
private PrivateKey privKey;
private Certificate[] cert;
public TC3() throws Exception{
Security.addProvider(provider);
KeyStore keystore = KeyStore.getInstance("PKCS12", provider);
keystore.load(new FileInputStream(new File("resources/IIS_keystore.pfx")), pin.clone());
String alias = keystore.aliases().nextElement();
privKey = (PrivateKey) keystore.getKey(alias, pin);
cert = keystore.getCertificateChain(alias);
}
public void doSign() throws Exception{
byte inputBytes[] = IOUtils.toByteArray(new FileInputStream("resources/rooster.pdf"));
PDDocument pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));
PDJpeg ximage = new PDJpeg(pdDocument, ImageIO.read(new File("resources/logo.jpg")));
PDPage page = (PDPage)pdDocument.getDocumentCatalog().getAllPages().get(0);
PDPageContentStream contentStream = new PDPageContentStream(pdDocument, page, true, true);
contentStream.drawXObject(ximage, 50, 50, 356, 40);
contentStream.close();
ByteArrayOutputStream os = new ByteArrayOutputStream();
pdDocument.save(os);
os.flush();
pdDocument.close();
inputBytes = os.toByteArray();
pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));
PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signature.setName("signer name");
signature.setLocation("signer location");
signature.setReason("reason for signature");
signature.setSignDate(Calendar.getInstance());
pdDocument.addSignature(signature, this);
File outputDocument = new File("resources/signed.pdf");
ByteArrayInputStream fis = new ByteArrayInputStream(inputBytes);
FileOutputStream fos = new FileOutputStream(outputDocument);
byte[] buffer = new byte[8 * 1024];
int c;
while ((c = fis.read(buffer)) != -1)
{
fos.write(buffer, 0, c);
}
fis.close();
FileInputStream is = new FileInputStream(outputDocument);
pdDocument.saveIncremental(is, fos);
pdDocument.close();
}
public byte[] sign(InputStream content) {
CMSProcessableInputStream input = new CMSProcessableInputStream(content);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
List<Certificate> certList = Arrays.asList(cert);
CertStore certStore = null;
try{
certStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList), provider);
gen.addSigner(privKey, (X509Certificate) certList.get(0), CMSSignedGenerator.DIGEST_SHA256);
gen.addCertificatesAndCRLs(certStore);
CMSSignedData signedData = gen.generate(input, false, provider);
return signedData.getEncoded();
}catch (Exception e){}
return null;
}
public static void main(String[] args) throws Exception {
new TC3().doSign();
}
The issue
As had already been explained in this answer, the issue at work here is that
when non-incrementally storing the document with the added image, PDFBox 1.8.9 does so using a cross reference table no matter if the original file used a table or stream; if the original file used a stream, the cross reference stream dictionary entries are copied into the trailer dictionary;
...
0000033667 00000 n
0000033731 00000 n
trailer
<<
/DecodeParms <<
/Columns 4
/Predictor 12
>>
/Filter /FlateDecode
/ID [<5BD95916CAE5E84E9D964396022CBDCD> <6420B4547602C943AF37DD6C77496BE8>]
/Info 6 0 R
/Length 61
/Root 1 0 R
/Size 35
/Type /XRef
/W [1 2 1]
/Index [20 22]
>>
startxref
35917
%%EOF
(Most of these trailer entries here are useless or even misleading, see below.)
when incrementally saving the signature, COSWriter.doWriteXRefInc uses COSDocument.isXRefStream to determine whether the existing document (the one we stored as above) uses a cross reference stream. As mentioned above, it does not. Unfortunately, though, COSDocument.isXRefStream in PDFBox 1.8.9 is implemented as
public boolean isXRefStream()
{
if (trailer != null)
{
return COSName.XREF.equals(trailer.getItem(COSName.TYPE));
}
return false;
}
Thus, the misleading trailer entry Type shown above make PDFBox think it has to use a cross reference stream.
The result is a document whose initial revision ends with a cross reference table and weird trailer entries and whose second revision ends with a cross reference stream. This is not valid.
A work-around
Fortunately, though, understanding how the issue arises presents a work-around: Removing the troublesome trailer entry, e.g. like this:
inputBytes = os.toByteArray();
pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));
pdDocument.getDocument().getTrailer().removeItem(COSName.TYPE); // <<<<<<<<<< Remove misleading entry <<<<<<<<<<
With this work-around both revisions in the signed document use cross reference tables and the signature is valid.
Beware, if upcoming PDFBox versions change to save documents loaded from sources with cross reference streams using xref streams, too, the work-around must again be removed.
I would assume, though, that won't happen in the 1.x.x versions to come, and version 2.0.0 will introduce a fundamentally changed API, so the original code won't work out-of-the-box then anyhow.
Other ideas
I tried other ways, too, to circumvent this problem, trying to
store the first manipulation as incremental update, too, or
add the image during the same incremental update as the signature,
cf. SignLikeUnOriginalToo.java, but failed. PDFBox 1.8.9 incremental updates only seem to properly work for adding signatures.
Other ideas revisited
After looking into the creation of additional revisions using PDFBox some more, I tried the other ideas again and now succeeded!
The crucial part is to mark the added and changed objects as updated, including a path from the document catalog.
Applying the first idea (adding the image as an explicit intermediate revision) amounts to this change in doSign:
...
FileOutputStream fos = new FileOutputStream(intermediateDocument);
FileInputStream fis = new FileInputStream(intermediateDocument);
byte inputBytes[] = IOUtils.toByteArray(inputStream);
PDDocument pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));
PDJpeg ximage = new PDJpeg(pdDocument, ImageIO.read(logoStream));
PDPage page = (PDPage) pdDocument.getDocumentCatalog().getAllPages().get(0);
PDPageContentStream contentStream = new PDPageContentStream(pdDocument, page, true, true);
contentStream.drawXObject(ximage, 50, 50, 356, 40);
contentStream.close();
pdDocument.getDocumentCatalog().getCOSObject().setNeedToBeUpdate(true);
pdDocument.getDocumentCatalog().getPages().getCOSObject().setNeedToBeUpdate(true);
page.getCOSObject().setNeedToBeUpdate(true);
page.getResources().getCOSObject().setNeedToBeUpdate(true);
page.getResources().getCOSDictionary().getDictionaryObject(COSName.XOBJECT).setNeedToBeUpdate(true);
ximage.getCOSObject().setNeedToBeUpdate(true);
fos.write(inputBytes);
pdDocument.saveIncremental(fis, fos);
pdDocument.close();
pdDocument = PDDocument.load(intermediateDocument);
PDSignature signature = new PDSignature();
...
(as in SignLikeUnOriginalToo.java method doSignTwoRevisions)
Applying the second idea (adding the image as part of the signing revision) amounts to this change in doSign:
...
byte inputBytes[] = IOUtils.toByteArray(inputStream);
PDDocument pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));
PDJpeg ximage = new PDJpeg(pdDocument, ImageIO.read(logoStream));
PDPage page = (PDPage) pdDocument.getDocumentCatalog().getAllPages().get(0);
PDPageContentStream contentStream = new PDPageContentStream(pdDocument, page, true, true);
contentStream.drawXObject(ximage, 50, 50, 356, 40);
contentStream.close();
page.getResources().getCOSObject().setNeedToBeUpdate(true);
page.getResources().getCOSDictionary().getDictionaryObject(COSName.XOBJECT).setNeedToBeUpdate(true);
ximage.getCOSObject().setNeedToBeUpdate(true);
PDSignature signature = new PDSignature();
...
(as in SignLikeUnOriginalToo.java method doSignOneStep)
Both variants are clearly preferable to the original approach.
Related
I want to ask a question. I added digital paging seal to a multi-page PDF, each page has the same seal, add the digital signature once on the first page, and then the other pages only need to quote the appearance of the first seal. But using adobe Acrobat DC to open, there will be an extra "123" signature in the generated document. What causes it?
I wrote the following code based on this answer and it helped me a lot.
addAp(doc, doc.getPage(0), rect, signature, xz[0]);
for (int i = 0; i < doc.getNumberOfPages() - 1; i++) {
addAnnots(doc.getPage(i));
}
addAp(doc, doc.getPage(1), lerect, signature, xz[1]);
for (int i = 1; i < doc.getNumberOfPages(); i++) {
addAnnots(doc.getPage(i));
}
void addAp(PDDocument pdDocument, PDPage pdPage, PDRectangle rectangle, PDSignature signature, BufferedImage signatureImage) throws IOException {
PDAcroForm acroForm = pdDocument.getDocumentCatalog().getAcroForm();
List<PDField> acroFormFields = acroForm.getFields();
PDSignatureField signatureField = new PDSignatureField(acroForm);
signatureField.setValue(signature);
PDAnnotationWidget widget = signatureField.getWidgets().get(0);
acroFormFields.clear();
acroFormFields.add(signatureField);
widget.setRectangle(rectangle);
widget.setPage(pdPage);
// from PDVisualSigBuilder.createHolderForm()
PDStream stream = new PDStream(pdDocument);
PDFormXObject form = new PDFormXObject(stream);
PDResources res = new PDResources();
form.setResources(res);
form.setFormType(1);
PDRectangle bbox = new PDRectangle(rectangle.getWidth(), rectangle.getHeight());
form.setBBox(bbox);
// from PDVisualSigBuilder.createAppearanceDictionary()
PDAppearanceDictionary appearance = new PDAppearanceDictionary();
appearance.getCOSObject().setDirect(true);
PDAppearanceStream appearanceStream = new PDAppearanceStream(form.getCOSObject());
appearance.setNormalAppearance(appearanceStream);
widget.setAppearance(appearance);
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ImageIO.write(signatureImage, "png", bao);
bao.flush();
byte[] imageByte = bao.toByteArray();
bao.close();
PDImageXObject pdImage = PDImageXObject.createFromByteArray(pdDocument, imageByte, null);
try (PDPageContentStream cs = new PDPageContentStream(pdDocument, appearanceStream)) {
PDExtendedGraphicsState r0 = new PDExtendedGraphicsState();
r0.setBlendMode(BlendMode.DARKEN);
cs.setGraphicsStateParameters(r0);
cs.addComment("This is a comment");
cs.drawImage(pdImage, 0, 0, rectangle.getWidth(), rectangle.getHeight());
}
setPdAnnotationWidget(widget);
}
void addAnnots(PDPage pdPage) throws IOException {
pdPage.getAnnotations().add(getPdAnnotationWidget());
COSDictionary pageTreeObject = pdPage.getCOSObject();
while (pageTreeObject != null) {
pageTreeObject.setNeedToBeUpdated(true);
pageTreeObject = (COSDictionary) pageTreeObject.getDictionaryObject(COSName.PARENT);
}
}
PDFBOX version is 2.0.20.
After modification:
ArrayList<PDAnnotationWidget> listWidget = addAp1(doc, signature);
addAp2(doc, doc.getPage(0), rect, xz[0], listWidget.get(0));
for (int i = 0; i < doc.getNumberOfPages() - 1; i++) {
addAnnots(doc.getPage(i));
}
addAp2(doc, doc.getPage(1), lerect, xz[1], listWidget.get(1));
for (int i = 1; i < doc.getNumberOfPages(); i++) {
addAnnots(doc.getPage(i));
}
ArrayList<PDAnnotationWidget> addAp1(PDDocument pdDocument, PDSignature signature) throws IOException {
ArrayList<PDAnnotationWidget> widgetList = new ArrayList<>();
PDAcroForm acroForm = pdDocument.getDocumentCatalog().getAcroForm();
List<PDField> acroFormFields = acroForm.getFields();
PDAnnotationWidget widget1 = new PDAnnotationWidget();
PDAnnotationWidget widget2 = new PDAnnotationWidget();
widgetList.add(widget1);
widgetList.add(widget2);
PDSignatureField signatureField = new PDSignatureField(acroForm);
signatureField.setValue(signature);
signatureField.setWidgets(widgetList);
acroFormFields.clear();
acroFormFields.add(signatureField);
return widgetList;
}
void addAp2(PDDocument pdDocument, PDPage pdPage, PDRectangle rectangle, BufferedImage signatureImage, PDAnnotationWidget widget) throws IOException {
widget.setPage(pdPage);
widget.setRectangle(rectangle);
....
}
WPS:
Adobe Acrobat DC:
You add 2 signature fields to the document.
You call addAp twice. Each time that method creates a PDSignatureField, and in the loop immediately after the addAp call the single widget of that field is added to the pages. Thus, both signature fields are reachable in the resulting PDF.
The two signature fields share the signature value.
addAp sets the value of both signature fields to the same signature value. When eventually the signature bytes are written into this value, both signature fields become signed.
Only the second signature field is in the PDF form definition.
addAp removes any field from the PDF form definition before adding the newly generated one. In the end, therefore, the PDF form definition only contains the signature field from the last addAp call.
Adobe Acrobat opens the file...
Adobe Acrobat automatically only validates the signature field in the PDF form definition. But as soon as it displays the widget of the other signature field, it also displays it on the signature panel. As it wasn't there from the start, though, it is displayed as not-yet-validated.
By not clearing the PDF form definition field list in the second addAp call, you should get two automatically validated signature fields in the signature panel.
Alternatively, by creating only a single form field with two widget annotations, you should get only a single signature field in the signature panel.
As a warning: You reference the same widget annotation from multiple pages. This strictly speaking is forbidden by the PDF specification. Thus, any validator may warn about this issue and - as this issue occurs in the context of a signature - message doubts about the validity of that signature.
I'm trying to use Aspose pdf java to digitally sign a document. This is my code
public ByteArrayOutputStream signDocument(Document doc, String signedBy) throws Exception {
PdfFileSignature pdfSignSingle = new PdfFileSignature();
pdfSignSingle.bindPdf(doc);
pdfSignSingle.setCertificate(prop.getSigningKeyStorePath(), prop.getKeystorePassword());
PKCS7 signature = new PKCS7(prop.getSigningKeyStorePath(), prop.getKeystorePassword());
pdfSignSingle.setSignatureAppearance(prop.getSimploudLogo());
signature.setAuthority("Authority");
signature.setDate(new Date());
signature.setContactInfo("email");
signature.setLocation("Location");
signature.setReason("reason");
pdfSignSingle.sign(1, true, new java.awt.Rectangle(100, 100, 200, 200), signature);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
pdfSignSingle.save(baos);
pdfSignSingle.dispose();
doc.dispose();
return baos;
}
In picture is shown how the signature looks in adobeReader.
As you can see both image and Authority are not shown. I tried image to be both in pdf and png format. I've also tried to make it smaller then Rectangle area. As for authority i really need it to be customizable so that text in first line in picture can be
Signed by "customParameter"
The API offers another Class i.e. SignatureCustomAppearance which can further be used to set such properties for a signature such as DateSigned, Reason, Location, etc. Please check the following complete code snippet which can fulfill your requirements:
String inputFile = "doc.pdf";
String outSignedFile = "out_20.9.pdf";
// Create PdfFileSignature instance
com.aspose.pdf.facades.PdfFileSignature pdfSignSingle = new com.aspose.pdf.facades.PdfFileSignature();
// Bind the source PDF by reading contents of Stream
pdfSignSingle.bindPdf(inputFile);
PKCS7 pkcs = new PKCS7("mykey2.pfx", "pass");
pkcs.setAuthority("Authority");
pkcs.setDate(new Date());
pkcs.setContactInfo("email");
pkcs.setLocation("Location");
pkcs.setReason("reason");
pkcs.setImage(new FileInputStream("simpleLogo.png"));
SignatureCustomAppearance sca = new SignatureCustomAppearance();
sca.setDateSignedAtLabel(null);
sca.setDigitalSignedLabel(null);
sca.setShowReason(true);
sca.setShowLocation(true);
sca.setShowContactInfo(true);
pkcs.setCustomAppearance(sca);
pdfSignSingle.sign(1, true, new java.awt.Rectangle(100, 100, 200, 200), pkcs);
// Set image for signature appearance
//pdfSignSingle.setSignatureAppearance("simpleLogo.png");
// Save final output
pdfSignSingle.save(outSignedFile);
As mentioned in the comment below the question, the same inquiry was posted in Aspose.PDF official forum as well at "Aspose pdf java PdfFileSignature setAuthority not working" and a solution has also been provided there.
My project digitally signs PDF documents using a digital certificate and displays the signature at the bottom left of the document. It had always worked well until now, there are some documents that are digitally signed but it is not shown although it is recognized that the rectangle where it is visible is. Could someone help with this, I leave a snippet that handles the digital signature.
Here I leave a screenshot of how the digital signature is in a PDF document, I show it in the lower left corner of the document.
EXAMPLES
In this link I have shared example pdf documents of the problem and without it, I will detail them below:
ok_unsigned.pdf file: it is a document that when passing through my project is signed well, becoming the ok_signed.pdf file (this is the norm so far)
ok_signed.pdf file: it is the digitally signed ok_unsigned.pdf file, it is a case of success
bad_unsigned.pdf file: it is a blank document that when digitally signed (bad_signed.pdf) the issue in question appears
bad_signed.pdf file: it is a document with the issue in question, digitally signed but without visually seeing the signature field.
CODE
try {
BouncyCastleProvider providerBC = new BouncyCastleProvider();
Security.addProvider(providerBC);
KeyStore ks = KeyStore.getInstance("pkcs12");
ks.load(new FileInputStream(keystore), password);
String alias = ks.aliases().nextElement();
Certificate[] chain = ks.getCertificateChain(alias);
PrivateKey pk = (PrivateKey) ks.getKey(alias, password);
PdfReader reader = new PdfReader(src);
FileOutputStream fos = new FileOutputStream(new File(dest));
PdfSigner signer = new PdfSigner(reader, fos, new StampingProperties());
Rectangle rect = new Rectangle(10, 10, 150, 50);
PdfSignatureAppearance appearance = signer.getSignatureAppearance();
appearance.setPageRect(rect)
.setCertificate(chain[0])
.setReasonCaption("")
.setLocationCaption("")
.setSignatureCreator("SignerJAGC - iText 7.1.11")
.setPageNumber(1);
signer.setFieldName("Banca en Línea - Envío de Documentos");
signer.setSignDate(new GregorianCalendar());
signer.setCertificationLevel(PdfSigner.CERTIFIED_NO_CHANGES_ALLOWED);
IExternalDigest digest = new BouncyCastleDigest();
IExternalSignature signature = new PrivateKeySignature(pk, DigestAlgorithms.SHA256, providerBC.getName());
signer.signDetached(digest, signature, chain, null, null, null, 0, SUBFILTER);
System.out.println("SIGNED");
} catch (Exception ex) {
System.err.println(ex.getMessage());
}
}```
As #mkl said your coordinates might be outside of the page visible area as not all PDF pages have the bottom left corner at (0, 0).
Try creating the signature rectangle like this:
Rectangle rect = new Rectangle(
yourPageCropBoxLowerLeftX + 10,
yourPageCropBoxLowerLeftY + 10,
yourPageCropBoxLowerLeftX + 10 + yourSignatureWidth,
yourPageCropBoxLowerLeftY + 10 + yourSignatureHeight);
You just have to see how you can read the page's crop box coordinates, the lower left corner, as I'm not familiar with iText API.
I'm using iText's methods to sign a PDF with digital certificate, generating a signature visible in the document with PdfSignatureAppearance, but I'd like the visible signature not to come out in print. I saw that there is something similar in the PdfAnnotation class, where you can add a flag for this. Is there any way to do this with the digital signature?
My code:
PdfStamper stp = null;
try {
PdfReader reader = new PdfReader(pdfInputFileName);
stp = PdfStamper.createSignature(reader, fout, '\0');
PdfSignatureAppearance sap = stp.getSignatureAppearance();
sap.setCrypto(privateKey, certificateChain, null, PdfSignatureAppearance.WINCER_SIGNED);
sap.setReason(reason);
sap.setLocation(location);
sap.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);
sap.setVisibleSignature(new Rectangle(30, 830, 170, 770), 1, null);
stp.close();
} catch (DocumentException | IOException e) {
logger.error("An unknown error accoured while signing the PDF file: " + e.getMessage());
}
This is the link to a PDF signed by this code, when I print it, the signature stamp always comes out in the print:
https://s3.amazonaws.com/gxzadminlocal/anexo_28276.pdf
Current iText 5 versions automatically set the PRINT flag if the signature field does not yet exist and they have to create it. But if you fill an existing signature field, that flag is untouched. Thus, you might want to add an empty signature field without that PRINT flag in a first step and in a second step sign using that field, e.g. with the following code:
Preparing the PDF
You can prepare a PDF with an empty signature field without setting the PRINT flag like this:
try ( InputStream resource = SOURCE_STREAM;
OutputStream os = INTERMEDIATE_OUTPUT_STREAM) {
PdfReader reader = new PdfReader(resource);
PdfStamper stamper = new PdfStamper(reader, os);
PdfFormField field = PdfFormField.createSignature(stamper.getWriter());
field.setFieldName("Signature");
field.setWidget(new Rectangle(30, 830, 170, 770), PdfAnnotation.HIGHLIGHT_NONE);
stamper.addAnnotation(field, 1);
stamper.close();
}
(CreateSignature test signWidgetNoPrint pass 1)
In particular you don't do
field.setFlags(PdfAnnotation.FLAGS_PRINT);
here!
Signing the prepared PDF
Having created that intermediate PDF, you can sign it like this:
try ( InputStream resource = INTERMEDIATE_INPUT_STREAM;
OutputStream os = RESULT_STREAM) {
PdfReader reader = new PdfReader(resource);
PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setReason("reason");
appearance.setLocation("location");
appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);
appearance.setVisibleSignature("Signature");
ExternalSignature pks = new PrivateKeySignature(pk, "SHA512", "BC");
ExternalDigest digest = new BouncyCastleDigest();
MakeSignature.signDetached(appearance, digest, pks, chain, null, null, null, 0, CryptoStandard.CMS);
}
(CreateSignature test signWidgetNoPrint pass 2)
assuming you have prepared your private key in pk and your certificate chain in chain; and assuming you have registered Bouncy Castle as security provider.
In the result PDF the signature visualization appears on screen but not in print.
I would like to set attributes to pdf before uploading it into a server.
Document document = new Document();
try
{
OutputStream file = new FileOutputStream({Localpath});
PdfWriter.getInstance(document, file);
document.open();
//Set attributes here
document.addTitle("TITLE");
document.close();
file.close();
} catch (Exception e)
{
e.printStackTrace();
}
But its not working. The file is getting corrupted
In a comment to another answer the OP clarified:
I want to set attributes to an existing pdf(not to create new pdf)
Obviously, though, his code creates a new document from scratch (as is obvious from the fact that a mere FileOutputStream is used to access the file, no reading, only writing).
To manipulate an existing PDF, one has to use a PdfReader / PdfWriter couple. Bruno Lowagie provided an example for that in his answer to the stack overflow question "iText setting Creation Date & Modified Date in sandbox.stamper.SuperImpose.java":
public void manipulatePdf(String src, String dest) throws IOException, DocumentException {
PdfReader reader = new PdfReader(src);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
Map info = reader.getInfo();
info.put("Title", "New title");
info.put("CreationDate", new PdfDate().toString());
stamper.setMoreInfo(info);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
XmpWriter xmp = new XmpWriter(baos, info);
xmp.close();
stamper.setXmpMetadata(baos.toByteArray());
stamper.close();
reader.close();
}
(ChangeMetadata.java)
As you see the code sets the metadata both in the ol'fashioned PDF info dictionary (stamper.setMoreInfo) and in the XMP metadata (stamper.setXmpMetadata).
Obviously src and dest should not be the same here.
Without a second file
In yet another comment the OP clarified that he had already tried a similar solution but that he wants to prevent the
Temporary existence of second file
This can easily be prevented by first reading the original PDF into a byte[] and then stamping to it as the target file. E.g. if File singleFile references the original file which is also to be the target file, you can implement:
byte[] original = Files.readAllBytes(singleFile.toPath());
PdfReader reader = new PdfReader(original);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(singleFile));
Map<String, String> info = reader.getInfo();
info.put("Title", "New title");
info.put("CreationDate", new PdfDate().toString());
stamper.setMoreInfo(info);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
XmpWriter xmp = new XmpWriter(baos, info);
xmp.close();
stamper.setXmpMetadata(baos.toByteArray());
stamper.close();
reader.close();
(UpdateMetaData test testChangeTitleWithoutTempFile)