I am working on an implementation where our system generates a PDF file for a user to download. The key of our process and system is that this PDF file should not be modifiable by the user or program on the users computer (at least, not without bad intent) as the file can be uploaded to the system later on where we need to make sure the file is in it`s original state by comparing its hash value.
We thought we accomplished this by first disabling all permissions (CanModify,CanAssembleDocument, etc.) and then encrypting the document with an owner`s password. This prevented the modification of the file by all readers we had access to. It now turns out that one of our users modifies a PDF as soon as he opens the file in Acrobat Reader and 'save as' the doc to a new pdf file. We cannot reproduce this with the same reader version (2015.006.30497) but he can, every time.
The alternative of signing the PDF document is not an option for us, at least not with a PKI or any visible signature that users can see in their reader. If there is some sort of invisible signing option that that would be great but I don't know how.
Below the code that we use to lock the PDF. For testing purposes we disabled ALL permissions, to no avail. We`re using PDFBox 2.0.11.
Any sugestions what options there are to better lock the file for modification?
public static byte[] SealFile(byte[] pdfFile, String password) throws IOException
{ PDDocument doc =PDDocument.load(pdfFile);
ByteArrayOutputStream bos= new ByteArrayOutputStream();
byte[] returnvalue =null;
int keyLength = 256;
AccessPermission ap = new AccessPermission();
//Disable all
ap.setCanModifyAnnotations(false);
ap.setCanAssembleDocument(false); .
ap.setCanFillInForm(false);
ap.setCanModify(false);
ap.setCanExtractContent(false);
ap.setCanExtractForAccessibility(false);
ap.setCanPrint(false);
//The user password is empty ("") so user can read without password. The admin password is
// set to lock/encrypt the document.
StandardProtectionPolicy spp = new StandardProtectionPolicy(password, "", ap);
spp.setEncryptionKeyLength(keyLength);
spp.setPermissions(ap);
doc.protect(spp);
doc.save(bos);
doc.close();
bos.flush();
return bos.toByteArray();
}
This results in Adobe properties:
Edit (solution):==========
As suggested by @mkl, (all credits to this person) we were able to solve the problem with the use of the appendOnly flag, which is part of the AcroForm functionality. Turned out that the signatureExists flag was not required for our problem to be solved. (and after reading the specs, was not applicable)
Below is the solution we implemented:
/*
* This method is used to add the 'appendOnly flag' to the PDF document. This flag is part of
* the AcroForm functionality that instructs a PDF reader that the file is signed and should not be
* modified during the 'saved as' function. For full description see PDF specification PDF 32000-1:2008
* (https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf)
* paragraph 12.7.2 Interactive Form Dictionary
*/
public static void addAcroFormSigFlags(PDDocument pdfDoc) {
PDDocumentCatalog catalog = pdfDoc.getDocumentCatalog();
PDAcroForm acroForm = catalog.getAcroForm();
if (acroForm == null) {
acroForm = new PDAcroForm(pdfDoc);
catalog.setAcroForm(acroForm);
}
// AppendOnly:
// If set, the document contains signatures that may be invalidated if the
// file is saved (wirtten) in a way that alters its previous contents, as
// opposed to an incremental update. Merely updating the file by appending
// new information to the end of the previous version is safe (see h.7,
// "Updating Example"). Conforming readers may use this flag to inform a
// user requesting a full save that signatures will be invalidated and
// require explicit confirmation before continuing with the operation
acroForm.setAppendOnly(true);
// SignatureExists: (Currently not used by us)
// If set, the document contains at least one signature field. This flag
// allows a conforming reader to enable user interface items (such as menu
// items or pushbuttons) related to signature processing without having to
// scan the entire document for the presence of signature fields.
// acroForm.setSignaturesExist(true);
// flag objects that changed (in case a 'saveIncremental' is done hereafter)
catalog.getCOSObject().setNeedToBeUpdated(true);
acroForm.getCOSObject().setNeedToBeUpdated(true);
}