Home > database >  Issue merging PDFs on iText7
Issue merging PDFs on iText7

Time:12-11

I have the below method that merges my PDF documents. However, in some cases where the document has a watermark, the method throws me an error.

public void doMergeUsingItext7(List<InputStream> list, OutputStream outputStream) throws SSException {
    try (com.itextpdf.kernel.pdf.PdfWriter writer = new com.itextpdf.kernel.pdf.PdfWriter(outputStream);) {
        writer.setSmartMode(Boolean.TRUE);
        try (com.itextpdf.kernel.pdf.PdfDocument pdfDoc = new com.itextpdf.kernel.pdf.PdfDocument(writer)) {
            pdfDoc.initializeOutlines();
            list.forEach((in) -> {
                try (com.itextpdf.kernel.pdf.PdfReader reader = new com.itextpdf.kernel.pdf.PdfReader(in);) {
                    reader.setUnethicalReading(Boolean.TRUE);
                    try (com.itextpdf.kernel.pdf.PdfDocument addedDoc = new com.itextpdf.kernel.pdf.PdfDocument(reader)) { //ERROR IS THROWN ON THIS LINE
                        addedDoc.copyPagesTo(1, addedDoc.getNumberOfPages(), pdfDoc);
                        logger.log(Level.INFO, "Successfully Added the Document to PDF");
                    } catch (Exception e) {
                        ExceptionUtils.printRootCauseStackTrace(e);
                    }
                } catch (IOException ex) {
                    ExceptionUtils.printRootCauseStackTrace(ex);
                } catch (Exception e) {
                    ExceptionUtils.printRootCauseStackTrace(e);
                }
            });
        }
    } catch (Exception ex) {
        throw new SSException(ex, "Print Version Failed");
    }
}

The following error is thrown when the document has a watermark...

com.itextpdf.kernel.PdfException: Illegal length value.
at com.itextpdf.kernel.pdf.PdfEncryption.readAndSetCryptoModeForStdHandler(PdfEncryption.java:523)
at com.itextpdf.kernel.pdf.PdfEncryption.<init>(PdfEncryption.java:229)
at com.itextpdf.kernel.pdf.PdfReader.readDecryptObj(PdfReader.java:1251)
at com.itextpdf.kernel.pdf.PdfReader.readPdf(PdfReader.java:685)
at com.itextpdf.kernel.pdf.PdfDocument.open(PdfDocument.java:1871)
at com.itextpdf.kernel.pdf.PdfDocument.<init>(PdfDocument.java:252)
at com.itextpdf.kernel.pdf.PdfDocument.<init>(PdfDocument.java:234)
at gov.ca.lc.util.PdfUtilFuntions.lambda$doMergeUsingItext7$0(PdfUtilFuntions.java:180)
at java.util.ArrayList.forEach(ArrayList.java:1257)
at gov.ca.lc.util.PdfUtilFuntions.doMergeUsingItext7(PdfUtilFuntions.java:176)

I am not sure what exactly within the document is failing to merge. Any help on fixing this issue is highly appreciated. Thanks.

CodePudding user response:

This is a bug in iText.

The PDF in question is encrypted. Its encryption dictionary has a V value of 1 ("RC4 or AES algorithms with an encryption key length of 40 bits") and an R value of 3 ("for files encrypted with a V value of 2 or 3, or with any “Security handlers of revision 3 or greater” access permissions set to 0").

A Length value for the key length is specified only for V 2 or 3, for V 1 the key length is fixed to 40. Nonetheless, iText in case of R 3 requires a Length value.

As there is no Length value in the encryption dictionary of your document, iText fails reading that file.


As a side note: 40 bit key length security nowadays is no security. So most likely that issue did not pop up earlier because using V 1 security (or 40 bit key length security in general) is useless and, therefore, no use case taken seriously.


A Quick Fix

If you don't shy away from recompiling iText, fixing the bug is easy. Simply edit the iText kernel class com.itextpdf.kernel.pdf.PdfEncryption. Its method readAndSetCryptoModeForStdHandler starts with

int cryptoMode;
int length = 0;

PdfNumber rValue = encDict.getAsNumber(PdfName.R);
if (rValue == null)
    throw new PdfException(KernelExceptionMessageConstant.ILLEGAL_R_VALUE);
int revision  = rValue.intValue();
boolean embeddedFilesOnlyMode = readEmbeddedFilesOnlyFromEncryptDictionary(encDict);
switch (revision) {
    case 2:
        cryptoMode = EncryptionConstants.STANDARD_ENCRYPTION_40;
        break;
    case 3:
        PdfNumber lengthValue = encDict.getAsNumber(PdfName.Length);
        if (lengthValue == null)
            throw new PdfException(KernelExceptionMessageConstant.ILLEGAL_LENGTH_VALUE);
        length = lengthValue.intValue();
        if (length > 128 || length < 40 || length % 8 != 0)
            throw new PdfException(KernelExceptionMessageConstant.ILLEGAL_LENGTH_VALUE);
        cryptoMode = EncryptionConstants.STANDARD_ENCRYPTION_128;
        break;

Here simply replace

if (lengthValue == null)
    throw new PdfException(KernelExceptionMessageConstant.ILLEGAL_LENGTH_VALUE);
length = lengthValue.intValue();

by

length = 40;
if (lengthValue != null)
    length = lengthValue.intValue();

(I used a replacement with an identical number of lines to make interpreting later stack traces easier.)

  • Related