Skip to content

Commit

Permalink
fix(parser): no longer decrypt Encrypt entry in trailer
Browse files Browse the repository at this point in the history
  • Loading branch information
aescarias committed May 5, 2024
1 parent 162f680 commit 9059e59
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 2 deletions.
4 changes: 4 additions & 0 deletions pdfnaut/parsers/pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,10 @@ def _get_decrypted(self, pdf_object: _WrapsEncryptable, reference: PdfIndirectRe
elif isinstance(pdf_object, list):
return [self._get_decrypted(obj, reference) for obj in pdf_object]
elif isinstance(pdf_object, dict):
# make a special exception if the dictionary is the Encrypt key in the trailer
# we do not need to decrypt them
if reference == self.trailer["Encrypt"]:
return pdf_object
return {name: self._get_decrypted(value, reference) for name, value in pdf_object.items()}

# Why would a number be encrypted?
Expand Down
4 changes: 2 additions & 2 deletions pdfnaut/security_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def compute_owner_password(self, owner_password: bytes, user_password: bytes) ->
"""Computes the O (owner password) value in the Encrypt dictionary
as defined in ``§ 7.6.3.3 Encryption Key Algorithm > Algorithm 3``"""

padded = pad_password(owner_password if owner_password else user_password)
padded = pad_password(owner_password or user_password)
owner_digest = md5(padded).digest()
if self.encryption["R"] >= 3:
for _ in range(50):
Expand All @@ -113,7 +113,7 @@ def compute_owner_password(self, owner_password: bytes, user_password: bytes) ->

def compute_user_password(self, password: bytes) -> bytes:
"""Computes the U (owner password) value in the Encrypt dictionary
as defined in ``§ 7.6.3.3 Encryption Key Algorithm > Algorithms 4 and 5"""
as defined in ``§ 7.6.3.3 Encryption Key Algorithm > Algorithms 4 and 5``"""

encr_key = self.compute_encryption_key(password)
arc4 = self._get_provider("ARC4")
Expand Down
23 changes: 23 additions & 0 deletions tests/test_encryption.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,26 @@ def test_rc4_aes_decryption():
parser.decrypt("nil")
metadata = cast("dict[str, Any]", parser.resolve_reference(parser.trailer["Info"]))
assert metadata["Producer"].value == b"pypdf"

def test_rc4_aes_password_values():
with open("tests/docs/encrypted-arc4.pdf", "rb") as fp:
parser = PdfParser(fp.read())
parser.parse()

encr_metadata = cast("dict[str, Any]", parser.resolve_reference(parser.trailer["Info"]))

encrypt_dict = parser.resolve_reference(parser.trailer["Encrypt"])
assert parser.security_handler is not None

# Passwords
o_value = parser.security_handler.compute_owner_password(b"null", b"nil")
assert o_value.hex().lower().encode() == encrypt_dict["O"].raw.lower()

u_value = parser.security_handler.compute_user_password(b"nil")
assert u_value.hex().lower().encode() == encrypt_dict["U"].raw.lower()

# Encryption with passwords
encr_key = parser.security_handler.compute_encryption_key(b"nil")

assert encr_metadata["Producer"].value == parser.security_handler.encrypt_object(
encr_key, b"pypdf", parser.trailer["Info"])

0 comments on commit 9059e59

Please sign in to comment.