From e7cfd10118a45a325488573d88ba88576a34f406 Mon Sep 17 00:00:00 2001 From: "valery.bokov" Date: Sat, 21 Feb 2026 11:52:15 +0100 Subject: [PATCH 1/2] extract additional methods in SecurityHandler to reduce checks number --- .../pdmodel/encryption/SecurityHandler.java | 65 ++++++++++++++----- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SecurityHandler.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SecurityHandler.java index 7adecd6a484..ce5d9147a73 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SecurityHandler.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SecurityHandler.java @@ -441,6 +441,28 @@ private SecureRandom getSecureRandom() return new SecureRandom(); } + /** + * This will dispatch to the correct method. + * + * @param string The string to decrypt. + * @param objNum The object number. + * @param genNum The object generation Number. + * + * @return the encrypted/decrypted COS object + */ + public COSBase decrypt(COSString string, long objNum, long genNum) + { + // PDFBOX-4477: only cache strings and streams, this improves speed and memory footprint + if (objects.contains(string)) + { + return string; + } + // replace the given COSString object with the encrypted/decrypted version + COSBase decryptedString = decryptString(string, objNum, genNum); + objects.add(decryptedString); + return decryptedString; + } + /** * This will dispatch to the correct method. * @@ -457,16 +479,9 @@ public COSBase decrypt(COSBase obj, long objNum, long genNum) throws IOException // PDFBOX-4477: only cache strings and streams, this improves speed and memory footprint if (obj instanceof COSString) { - if (objects.contains(obj)) - { - return obj; - } - // replace the given COSString object with the encrypted/decrypted version - COSBase decryptedString = decryptString((COSString) obj, objNum, genNum); - objects.add(decryptedString); - return decryptedString; + return decrypt((COSString)obj, objNum, genNum); } - if (obj instanceof COSStream) + else if (obj instanceof COSStream) { if (objects.contains(obj)) { @@ -477,11 +492,11 @@ public COSBase decrypt(COSBase obj, long objNum, long genNum) throws IOException } else if (obj instanceof COSDictionary) { - decryptDictionary((COSDictionary) obj, objNum, genNum); + return decrypt((COSDictionary) obj, objNum, genNum); } else if (obj instanceof COSArray) { - decryptArray((COSArray) obj, objNum, genNum); + return decrypt((COSArray) obj, objNum, genNum); } return obj; } @@ -536,7 +551,7 @@ public void decryptStream(COSStream stream, long objNum, long genNum) throws IOE return; } } - decryptDictionary(stream, objNum, genNum); + decrypt(stream, objNum, genNum); // the input and the output stream of a still encrypted COSStream aren't no longer based // on the same object so that it is safe to omit the intermediate ByteArrayStream try (InputStream encryptedStream = stream.createRawInputStream(); // @@ -589,14 +604,16 @@ public void encryptStream(COSStream stream, long objNum, int genNum) throws IOEx * @param objNum The object number. * @param genNum The object generation number. * + * @return the encrypted/decrypted COS object + * * @throws IOException If there is an error creating a new string. */ - private void decryptDictionary(COSDictionary dictionary, long objNum, long genNum) throws IOException + private COSBase decrypt(COSDictionary dictionary, long objNum, long genNum) throws IOException { if (dictionary.getItem(COSName.CF) != null) { // PDFBOX-2936: avoid orphan /CF dictionaries found in US govt "I-" files - return; + return dictionary; } COSName type = dictionary.getCOSName(COSName.TYPE); boolean isSignature = COSName.SIG.equals(type) || COSName.DOC_TIME_STAMP.equals(type) || @@ -613,11 +630,21 @@ private void decryptDictionary(COSDictionary dictionary, long objNum, long genNu } COSBase value = entry.getValue(); // within a dictionary only the following kind of COS objects have to be decrypted - if (value instanceof COSString || value instanceof COSArray || value instanceof COSDictionary) + if (value instanceof COSString) { - entry.setValue(decrypt(value, objNum, genNum)); + entry.setValue(decrypt((COSString)value, objNum, genNum)); + } + else if (value instanceof COSArray) + { + entry.setValue(decrypt((COSArray) value, objNum, genNum)); + } + else if (value instanceof COSDictionary) + { + entry.setValue(decrypt((COSDictionary)value, objNum, genNum)); } } + + return dictionary; } /** @@ -678,14 +705,18 @@ public COSBase encryptString(COSString string, long objNum, int genNum) throws I * @param objNum The object number. * @param genNum The object generation number. * + * @return the encrypted/decrypted COS object + * * @throws IOException If there is an error accessing the data. */ - private void decryptArray(COSArray array, long objNum, long genNum) throws IOException + private COSBase decrypt(COSArray array, long objNum, long genNum) throws IOException { for (int i = 0; i < array.size(); i++) { array.set(i, decrypt(array.get(i), objNum, genNum)); } + + return array; } /** From f7387f6d68116659bfdf8ec381b374e75a5f98f9 Mon Sep 17 00:00:00 2001 From: "valery.bokov" Date: Wed, 4 Mar 2026 23:15:16 +0100 Subject: [PATCH 2/2] add SecurityHandler.decryptStreamIfAbsent method, change decrypt methods names --- .../pdmodel/encryption/SecurityHandler.java | 53 +++++++++++++------ 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SecurityHandler.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SecurityHandler.java index ce5d9147a73..ec36761ebcd 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SecurityHandler.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SecurityHandler.java @@ -442,7 +442,7 @@ private SecureRandom getSecureRandom() } /** - * This will dispatch to the correct method. + * This will decrypt a string if it is not in set of the objects. * * @param string The string to decrypt. * @param objNum The object number. @@ -450,7 +450,7 @@ private SecureRandom getSecureRandom() * * @return the encrypted/decrypted COS object */ - public COSBase decrypt(COSString string, long objNum, long genNum) + private COSBase decryptStringIfAbsent(COSString string, long objNum, long genNum) { // PDFBOX-4477: only cache strings and streams, this improves speed and memory footprint if (objects.contains(string)) @@ -479,28 +479,43 @@ public COSBase decrypt(COSBase obj, long objNum, long genNum) throws IOException // PDFBOX-4477: only cache strings and streams, this improves speed and memory footprint if (obj instanceof COSString) { - return decrypt((COSString)obj, objNum, genNum); + return decryptStringIfAbsent((COSString)obj, objNum, genNum); } else if (obj instanceof COSStream) { - if (objects.contains(obj)) - { - return obj; - } - objects.add(obj); - decryptStream((COSStream) obj, objNum, genNum); + return decryptStreamIfAbsent((COSStream)obj, objNum, genNum); } else if (obj instanceof COSDictionary) { - return decrypt((COSDictionary) obj, objNum, genNum); + return decryptDictionary((COSDictionary) obj, objNum, genNum); } else if (obj instanceof COSArray) { - return decrypt((COSArray) obj, objNum, genNum); + return decryptArray((COSArray) obj, objNum, genNum); } return obj; } + /** + * This will decrypt a stream if it is not in set of the objects. + * + * @param stream The stream to decrypt. + * @param objNum The object number. + * @param genNum The object generation Number. + * + * @return the encrypted/decrypted COS object + */ + private COSBase decryptStreamIfAbsent(COSStream stream, long objNum, long genNum) throws IOException + { + if (!objects.contains(stream)) + { + objects.add(stream); + decryptStream(stream, objNum, genNum); + } + + return stream; + } + /** * This will decrypt a stream. * @@ -551,7 +566,7 @@ public void decryptStream(COSStream stream, long objNum, long genNum) throws IOE return; } } - decrypt(stream, objNum, genNum); + decryptDictionary(stream, objNum, genNum); // the input and the output stream of a still encrypted COSStream aren't no longer based // on the same object so that it is safe to omit the intermediate ByteArrayStream try (InputStream encryptedStream = stream.createRawInputStream(); // @@ -608,7 +623,7 @@ public void encryptStream(COSStream stream, long objNum, int genNum) throws IOEx * * @throws IOException If there is an error creating a new string. */ - private COSBase decrypt(COSDictionary dictionary, long objNum, long genNum) throws IOException + private COSBase decryptDictionary(COSDictionary dictionary, long objNum, long genNum) throws IOException { if (dictionary.getItem(COSName.CF) != null) { @@ -632,15 +647,19 @@ private COSBase decrypt(COSDictionary dictionary, long objNum, long genNum) thro // within a dictionary only the following kind of COS objects have to be decrypted if (value instanceof COSString) { - entry.setValue(decrypt((COSString)value, objNum, genNum)); + entry.setValue(decryptStringIfAbsent((COSString)value, objNum, genNum)); } else if (value instanceof COSArray) { - entry.setValue(decrypt((COSArray) value, objNum, genNum)); + entry.setValue(decryptArray((COSArray) value, objNum, genNum)); + } + else if (value instanceof COSStream) + { + entry.setValue(decryptStreamIfAbsent((COSStream)value, objNum, genNum)); } else if (value instanceof COSDictionary) { - entry.setValue(decrypt((COSDictionary)value, objNum, genNum)); + entry.setValue(decryptDictionary((COSDictionary)value, objNum, genNum)); } } @@ -709,7 +728,7 @@ public COSBase encryptString(COSString string, long objNum, int genNum) throws I * * @throws IOException If there is an error accessing the data. */ - private COSBase decrypt(COSArray array, long objNum, long genNum) throws IOException + private COSBase decryptArray(COSArray array, long objNum, long genNum) throws IOException { for (int i = 0; i < array.size(); i++) {