From c99256370beefce2c0780093adfaf618fa459069 Mon Sep 17 00:00:00 2001 From: exiledkingcc Date: Mon, 20 Jun 2022 11:36:00 +0800 Subject: [PATCH 1/6] support R6 decrypting --- PyPDF2/_encryption.py | 77 +++++++++++------- resources/encryption/v5-r6-empty-password.pdf | Bin 0 -> 16709 bytes resources/encryption/v5-r6-owner-password.pdf | Bin 0 -> 16709 bytes resources/encryption/v5-r6-user-password.pdf | Bin 0 -> 16709 bytes tests/test_encryption.py | 6 ++ 5 files changed, 55 insertions(+), 28 deletions(-) create mode 100644 resources/encryption/v5-r6-empty-password.pdf create mode 100644 resources/encryption/v5-r6-owner-password.pdf create mode 100644 resources/encryption/v5-r6-user-password.pdf diff --git a/PyPDF2/_encryption.py b/PyPDF2/_encryption.py index bc6870170..635bf56ac 100644 --- a/PyPDF2/_encryption.py +++ b/PyPDF2/_encryption.py @@ -235,7 +235,7 @@ def _bytes(value: Union[bytes, str]) -> bytes: return value.encode("utf-8") -class AlgR4: +class AlgV4: @staticmethod def compute_key( password: bytes, @@ -397,10 +397,10 @@ def verify_user_password( encryption dictionary’s U (user password) value (Security handlers of revision 3 or greater)") shall be used to decrypt the document. """ - key = AlgR4.compute_key( + key = AlgV4.compute_key( user_pwd, rev, key_size, o_entry, P, id1_entry, metadata_encrypted ) - u_value = AlgR4.compute_U_value(key, rev, id1_entry) + u_value = AlgV4.compute_U_value(key, rev, id1_entry) if rev >= 3: u_value = u_value[:16] u_entry = u_entry[:16] @@ -434,7 +434,7 @@ def verify_owner_password( c) The result of step (b) purports to be the user password. Authenticate this user password using "Algorithm 6: Authenticating the user password". If it is correct, the password supplied is the correct owner password. """ - rc4_key = AlgR4.compute_O_value_key(owner_pwd, rev, key_size) + rc4_key = AlgV4.compute_O_value_key(owner_pwd, rev, key_size) if rev <= 2: u_pwd = RC4_decrypt(rc4_key, o_entry) @@ -443,15 +443,15 @@ def verify_owner_password( for i in range(19, -1, -1): key = bytes(bytearray(x ^ i for x in rc4_key)) u_pwd = RC4_decrypt(key, u_pwd) - return AlgR4.verify_user_password( + return AlgV4.verify_user_password( u_pwd, rev, key_size, o_entry, u_entry, P, id1_entry, metadata_encrypted ) -class AlgR5: +class AlgV5: @staticmethod def verify_owner_password( - password: bytes, o_value: bytes, oe_value: bytes, u_value: bytes + R: int, password: bytes, o_value: bytes, oe_value: bytes, u_value: bytes ) -> bytes: """ Algorithm 3.2a Computing an encryption key @@ -483,23 +483,45 @@ def verify_owner_password( should match the value in the P key. """ password = password[:127] - if hashlib.sha256(password + o_value[32:40] + u_value).digest() != o_value[:32]: + if AlgV5.calculate_hash(R, password, o_value[32:40], u_value) != o_value[:32]: return b"" iv = bytes(0 for _ in range(16)) - tmp_key = hashlib.sha256(password + o_value[40:] + u_value).digest() + tmp_key = AlgV5.calculate_hash(R, password, o_value[40:], u_value) key = AES_CBC_decrypt(tmp_key, iv, oe_value) return key @staticmethod - def verify_user_password(password: bytes, u_value: bytes, ue_value: bytes) -> bytes: + def verify_user_password(R: int, password: bytes, u_value: bytes, ue_value: bytes) -> bytes: """see :func:`verify_owner_password`""" password = password[:127] - if hashlib.sha256(password + u_value[32:40]).digest() != u_value[:32]: + if AlgV5.calculate_hash(R, password, u_value[32:40], b"") != u_value[:32]: return b"" iv = bytes(0 for _ in range(16)) - tmp_key = hashlib.sha256(password + u_value[40:]).digest() + tmp_key = AlgV5.calculate_hash(R, password, u_value[40:], b"") return AES_CBC_decrypt(tmp_key, iv, ue_value) + @staticmethod + def calculate_hash(R: int, password: bytes, salt: bytes, udata: bytes) -> bytes: + # from https://github.com/qpdf/qpdf/blob/main/libqpdf/QPDF_encryption.cc + K = hashlib.sha256(password + salt + udata).digest() + if R < 6: + return K + count = 0 + while True: + count += 1 + K1 = password + K + udata + E = AES_CBC_encrypt(K[:16], K[16:32], K1 * 64) + hash_fn = ( + hashlib.sha256, + hashlib.sha384, + hashlib.sha512, + )[sum(E[:16]) % 3] + K = hash_fn(E).digest() + if count >= 64 and E[-1] <= count - 32: + print(count, E[-1]) + break + return K[:32] + @staticmethod def verify_perms( key: bytes, perms: bytes, p: int, metadata_encrypted: bool @@ -514,9 +536,9 @@ def verify_perms( def generate_values( user_pwd: bytes, owner_pwd: bytes, key: bytes, p: int, metadata_encrypted: bool ) -> dict: - u_value, ue_value = AlgR5.compute_U_value(user_pwd, key) - o_value, oe_value = AlgR5.compute_O_value(owner_pwd, key, u_value) - perms = AlgR5.compute_Perms_value(key, p, metadata_encrypted) + u_value, ue_value = AlgV5.compute_U_value(user_pwd, key) + o_value, oe_value = AlgV5.compute_O_value(owner_pwd, key, u_value) + perms = AlgV5.compute_Perms_value(key, p, metadata_encrypted) return { "/U": u_value, "/UE": ue_value, @@ -603,6 +625,7 @@ class Encryption: def __init__( self, algV: int, + algR: int, entry: DictionaryObject, first_id_entry: bytes, StmF: str, @@ -611,6 +634,7 @@ def __init__( ) -> None: # See TABLE 3.18 Entries common to all encryption dictionaries self.algV = algV + self.algR = algR self.entry = entry self.key_size = entry.get("/Length", 40) self.id1_entry = first_id_entry @@ -705,9 +729,9 @@ def verify(self, user_pwd: Union[bytes, str], owner_pwd: Union[bytes, str]) -> i rc = 0 if self.algV <= 4: - key, rc = self.verify_r4(up_bytes, op_bytes) + key, rc = self.verify_v4(up_bytes, op_bytes) else: - key, rc = self.verify_r5(up_bytes, op_bytes) + key, rc = self.verify_v5(up_bytes, op_bytes) if rc == 1: self._key = key @@ -718,7 +742,7 @@ def verify(self, user_pwd: Union[bytes, str], owner_pwd: Union[bytes, str]) -> i return rc - def verify_r4(self, user_pwd: bytes, owner_pwd: bytes) -> Tuple[bytes, int]: + def verify_v4(self, user_pwd: bytes, owner_pwd: bytes) -> Tuple[bytes, int]: R = cast(int, self.entry["/R"]) P = cast(int, self.entry["/P"]) P = (P + 0x100000000) % 0x100000000 # maybe < 0 @@ -726,7 +750,7 @@ def verify_r4(self, user_pwd: bytes, owner_pwd: bytes) -> Tuple[bytes, int]: o_entry = cast(ByteStringObject, self.entry["/O"].get_object()).original_bytes u_entry = cast(ByteStringObject, self.entry["/U"].get_object()).original_bytes - key = AlgR4.verify_user_password( + key = AlgV4.verify_user_password( user_pwd, R, self.key_size, @@ -738,7 +762,7 @@ def verify_r4(self, user_pwd: bytes, owner_pwd: bytes) -> Tuple[bytes, int]: ) if key: return key, 1 - key = AlgR4.verify_owner_password( + key = AlgV4.verify_owner_password( owner_pwd, R, self.key_size, @@ -752,7 +776,7 @@ def verify_r4(self, user_pwd: bytes, owner_pwd: bytes) -> Tuple[bytes, int]: return key, 2 return b"", 0 - def verify_r5(self, user_pwd: bytes, owner_pwd: bytes) -> Tuple[bytes, int]: + def verify_v5(self, user_pwd: bytes, owner_pwd: bytes) -> Tuple[bytes, int]: # TODO: use SASLprep process o_entry = cast(ByteStringObject, self.entry["/O"].get_object()).original_bytes u_entry = cast(ByteStringObject, self.entry["/U"].get_object()).original_bytes @@ -760,11 +784,11 @@ def verify_r5(self, user_pwd: bytes, owner_pwd: bytes) -> Tuple[bytes, int]: ue_entry = cast(ByteStringObject, self.entry["/UE"].get_object()).original_bytes rc = 0 - key = AlgR5.verify_user_password(user_pwd, u_entry, ue_entry) + key = AlgV5.verify_user_password(self.algR, user_pwd, u_entry, ue_entry) if key: rc = 1 else: - key = AlgR5.verify_owner_password(owner_pwd, o_entry, oe_entry, u_entry) + key = AlgV5.verify_owner_password(self.algR, owner_pwd, o_entry, oe_entry, u_entry) if key: rc = 2 if rc == 0: @@ -774,7 +798,7 @@ def verify_r5(self, user_pwd: bytes, owner_pwd: bytes) -> Tuple[bytes, int]: P = cast(int, self.entry["/P"]) P = (P + 0x100000000) % 0x100000000 # maybe < 0 metadata_encrypted = self.entry.get("/EncryptMetadata", True) - if not AlgR5.verify_perms(key, perms, P, metadata_encrypted): + if not AlgV5.verify_perms(key, perms, P, metadata_encrypted): return b"", 0 return key, rc @@ -818,7 +842,4 @@ def read(encryption_entry: DictionaryObject, first_id_entry: bytes) -> "Encrypti raise NotImplementedError(f"EFF Method {EFF} NOT supported!") R = cast(int, encryption_entry["/R"]) - if R > 5: - raise NotImplementedError(f"encryption R={R} NOT supported!") - - return Encryption(V, encryption_entry, first_id_entry, StmF, StrF, EFF) + return Encryption(V, R, encryption_entry, first_id_entry, StmF, StrF, EFF) diff --git a/resources/encryption/v5-r6-empty-password.pdf b/resources/encryption/v5-r6-empty-password.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7b22a0c26fd8ed0898a2554e94f9121a317e9ee8 GIT binary patch literal 16709 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 K3#;j=)BPVdGc^1F literal 0 HcmV?d00001 diff --git a/resources/encryption/v5-r6-owner-password.pdf b/resources/encryption/v5-r6-owner-password.pdf new file mode 100644 index 0000000000000000000000000000000000000000..432a5038799c87450818b1e3099d9aadba5ce546 GIT binary patch literal 16709 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 Dp>8n< literal 0 HcmV?d00001 diff --git a/tests/test_encryption.py b/tests/test_encryption.py index 06545d129..3e1036747 100644 --- a/tests/test_encryption.py +++ b/tests/test_encryption.py @@ -39,6 +39,12 @@ (os.path.join(RESOURCE_ROOT, "encryption", "enca.pdf")), # created by `qpdf --encrypt "1234" "asdfzxcv" 128 --use-aes=y -- enc0.pdf encb.pdf` (os.path.join(RESOURCE_ROOT, "encryption", "encb.pdf")), + # created by `qpdf --encrypt "" "" 256 -- unencrypted.pdf v5-r6-empty-password.pdf` + (os.path.join(RESOURCE_ROOT, "encryption", "v5-r6-empty-password.pdf")), + # created by `qpdf --encrypt "asdfzxcv" "" 256 -- unencrypted.pdf v5-r6-owner-password.pdf` + (os.path.join(RESOURCE_ROOT, "encryption", "v5-r6-owner-password.pdf")), + # created by `qpdf --encrypt "" "asdfzxcv" 256 -- unencrypted.pdf v5-r6-user-password.pdf` + (os.path.join(RESOURCE_ROOT, "encryption", "v5-r6-user-password.pdf")), ], ) def test_encryption(src): From b4b74a4406d94491ec983c87da1b5b696f2f7f8c Mon Sep 17 00:00:00 2001 From: exiledkingcc Date: Mon, 20 Jun 2022 15:51:27 +0800 Subject: [PATCH 2/6] make encryption test file names clear --- resources/encryption/enca.pdf | Bin 16395 -> 0 bytes resources/encryption/encb.pdf | Bin 16395 -> 0 bytes .../{enc1.pdf => r2-empty-password.pdf} | Bin .../{enc3.pdf => r2-user-password.pdf} | Bin .../{enc2.pdf => r3-empty-password.pdf} | Bin .../{enc4.pdf => r3-user-password.pdf} | Bin .../{enc6.pdf => r4-aes-user-password.pdf} | Bin .../{enc5.pdf => r4-user-password.pdf} | Bin .../{enc7.pdf => r5-empty-password.pdf} | Bin .../{enc9.pdf => r5-owner-password.pdf} | Bin .../{enc8.pdf => r5-user-password.pdf} | Bin ...pty-password.pdf => r6-empty-password.pdf} | Bin ...ser-password.pdf => r6-owner-password.pdf} | Bin ...wner-password.pdf => r6-user-password.pdf} | Bin .../encryption/{enc0.pdf => unencrypted.pdf} | Bin tests/test_encryption.py | 101 ++++++++---------- tests/test_reader.py | 4 +- 17 files changed, 48 insertions(+), 57 deletions(-) delete mode 100644 resources/encryption/enca.pdf delete mode 100644 resources/encryption/encb.pdf rename resources/encryption/{enc1.pdf => r2-empty-password.pdf} (100%) rename resources/encryption/{enc3.pdf => r2-user-password.pdf} (100%) rename resources/encryption/{enc2.pdf => r3-empty-password.pdf} (100%) rename resources/encryption/{enc4.pdf => r3-user-password.pdf} (100%) rename resources/encryption/{enc6.pdf => r4-aes-user-password.pdf} (100%) rename resources/encryption/{enc5.pdf => r4-user-password.pdf} (100%) rename resources/encryption/{enc7.pdf => r5-empty-password.pdf} (100%) rename resources/encryption/{enc9.pdf => r5-owner-password.pdf} (100%) rename resources/encryption/{enc8.pdf => r5-user-password.pdf} (100%) rename resources/encryption/{v5-r6-empty-password.pdf => r6-empty-password.pdf} (100%) rename resources/encryption/{v5-r6-user-password.pdf => r6-owner-password.pdf} (100%) rename resources/encryption/{v5-r6-owner-password.pdf => r6-user-password.pdf} (100%) rename resources/encryption/{enc0.pdf => unencrypted.pdf} (100%) diff --git a/resources/encryption/enca.pdf b/resources/encryption/enca.pdf deleted file mode 100644 index 1a9d32570beee3fa73c72fbd4f9aa781677d01b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16395 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 M>c$NvZDp$e2Yr;pF#rGn diff --git a/resources/encryption/encb.pdf b/resources/encryption/encb.pdf deleted file mode 100644 index 900c151d153b0d06330945f52a8abac0590b1410..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16395 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 Q5CjH{pcfHQH_)K}fAp2+1poj5 diff --git a/resources/encryption/enc1.pdf b/resources/encryption/r2-empty-password.pdf similarity index 100% rename from resources/encryption/enc1.pdf rename to resources/encryption/r2-empty-password.pdf diff --git a/resources/encryption/enc3.pdf b/resources/encryption/r2-user-password.pdf similarity index 100% rename from resources/encryption/enc3.pdf rename to resources/encryption/r2-user-password.pdf diff --git a/resources/encryption/enc2.pdf b/resources/encryption/r3-empty-password.pdf similarity index 100% rename from resources/encryption/enc2.pdf rename to resources/encryption/r3-empty-password.pdf diff --git a/resources/encryption/enc4.pdf b/resources/encryption/r3-user-password.pdf similarity index 100% rename from resources/encryption/enc4.pdf rename to resources/encryption/r3-user-password.pdf diff --git a/resources/encryption/enc6.pdf b/resources/encryption/r4-aes-user-password.pdf similarity index 100% rename from resources/encryption/enc6.pdf rename to resources/encryption/r4-aes-user-password.pdf diff --git a/resources/encryption/enc5.pdf b/resources/encryption/r4-user-password.pdf similarity index 100% rename from resources/encryption/enc5.pdf rename to resources/encryption/r4-user-password.pdf diff --git a/resources/encryption/enc7.pdf b/resources/encryption/r5-empty-password.pdf similarity index 100% rename from resources/encryption/enc7.pdf rename to resources/encryption/r5-empty-password.pdf diff --git a/resources/encryption/enc9.pdf b/resources/encryption/r5-owner-password.pdf similarity index 100% rename from resources/encryption/enc9.pdf rename to resources/encryption/r5-owner-password.pdf diff --git a/resources/encryption/enc8.pdf b/resources/encryption/r5-user-password.pdf similarity index 100% rename from resources/encryption/enc8.pdf rename to resources/encryption/r5-user-password.pdf diff --git a/resources/encryption/v5-r6-empty-password.pdf b/resources/encryption/r6-empty-password.pdf similarity index 100% rename from resources/encryption/v5-r6-empty-password.pdf rename to resources/encryption/r6-empty-password.pdf diff --git a/resources/encryption/v5-r6-user-password.pdf b/resources/encryption/r6-owner-password.pdf similarity index 100% rename from resources/encryption/v5-r6-user-password.pdf rename to resources/encryption/r6-owner-password.pdf diff --git a/resources/encryption/v5-r6-owner-password.pdf b/resources/encryption/r6-user-password.pdf similarity index 100% rename from resources/encryption/v5-r6-owner-password.pdf rename to resources/encryption/r6-user-password.pdf diff --git a/resources/encryption/enc0.pdf b/resources/encryption/unencrypted.pdf similarity index 100% rename from resources/encryption/enc0.pdf rename to resources/encryption/unencrypted.pdf diff --git a/tests/test_encryption.py b/tests/test_encryption.py index 3e1036747..5c3e75e97 100644 --- a/tests/test_encryption.py +++ b/tests/test_encryption.py @@ -10,69 +10,62 @@ @pytest.mark.parametrize( - "src", + "name", [ # unencrypted pdf - (os.path.join(RESOURCE_ROOT, "encryption", "enc0.pdf")), - # created by `qpdf --encrypt "" "" 40 -- enc0.pdf enc1.pdf` - (os.path.join(RESOURCE_ROOT, "encryption", "enc1.pdf")), - # created by `qpdf --encrypt "" "" 128 -- enc0.pdf enc2.pdf` - (os.path.join(RESOURCE_ROOT, "encryption", "enc2.pdf")), - # created by `qpdf --encrypt "asdfzxcv" "" 40 -- enc0.pdf enc3.pdf` - (os.path.join(RESOURCE_ROOT, "encryption", "enc3.pdf")), - # created by `qpdf --encrypt "asdfzxcv" "" 128 -- enc0.pdf enc4.pdf` - (os.path.join(RESOURCE_ROOT, "encryption", "enc4.pdf")), - # V=4 and AES128 - # created by `qpdf --encrypt "asdfzxcv" "" 128 --force-V4 -- enc0.pdf enc5.pdf` - (os.path.join(RESOURCE_ROOT, "encryption", "enc5.pdf")), - # created by `qpdf --encrypt "asdfzxcv" "" 128 --use-aes=y -- enc0.pdf enc6.pdf` - (os.path.join(RESOURCE_ROOT, "encryption", "enc6.pdf")), - # # V=5 and R=5 use AES-256 - # # created by `qpdf --encrypt "" "" 256 --force-R5 -- enc0.pdf enc7.pdf` - (os.path.join(RESOURCE_ROOT, "encryption", "enc7.pdf")), - # # created by `qpdf --encrypt "asdfzxcv" "" 256 --force-R5 -- enc0.pdf enc8.pdf` - (os.path.join(RESOURCE_ROOT, "encryption", "enc8.pdf")), - # # created by `qpdf --encrypt "" "asdfzxcv" 256 --force-R5 -- enc0.pdf enc9.pdf` - (os.path.join(RESOURCE_ROOT, "encryption", "enc9.pdf")), - # asdfzxcv is owner password - # created by `qpdf --encrypt "" "asdfzxcv" 128 --use-aes=y -- enc0.pdf enca.pdf` - (os.path.join(RESOURCE_ROOT, "encryption", "enca.pdf")), - # created by `qpdf --encrypt "1234" "asdfzxcv" 128 --use-aes=y -- enc0.pdf encb.pdf` - (os.path.join(RESOURCE_ROOT, "encryption", "encb.pdf")), - # created by `qpdf --encrypt "" "" 256 -- unencrypted.pdf v5-r6-empty-password.pdf` - (os.path.join(RESOURCE_ROOT, "encryption", "v5-r6-empty-password.pdf")), - # created by `qpdf --encrypt "asdfzxcv" "" 256 -- unencrypted.pdf v5-r6-owner-password.pdf` - (os.path.join(RESOURCE_ROOT, "encryption", "v5-r6-owner-password.pdf")), - # created by `qpdf --encrypt "" "asdfzxcv" 256 -- unencrypted.pdf v5-r6-user-password.pdf` - (os.path.join(RESOURCE_ROOT, "encryption", "v5-r6-user-password.pdf")), + "unencrypted.pdf", + # created by `qpdf --encrypt "" "" 40 -- unencrypted.pdf r2-empty-password.pdf` + "r2-empty-password.pdf", + # created by `qpdf --encrypt "" "" 128 -- unencrypted.pdf r3-empty-password.pdf` + "r3-empty-password.pdf", + # created by `qpdf --encrypt "asdfzxcv" "" 40 -- unencrypted.pdf r2-user-password.pdf` + "r2-user-password.pdf", + # created by `qpdf --encrypt "asdfzxcv" "" 128 -- unencrypted.pdf r3-user-password.pdf` + "r3-user-password.pdf", + # created by `qpdf --encrypt "asdfzxcv" "" 128 --force-V4 -- unencrypted.pdf r4-user-password.pdf` + "r4-user-password.pdf", + # created by `qpdf --encrypt "asdfzxcv" "" 128 --use-aes=y -- unencrypted.pdf r4-aes-user-password.pdf` + "r4-aes-user-password.pdf", + # # created by `qpdf --encrypt "" "" 256 --force-R5 -- unencrypted.pdf r5-empty-password.pdf` + "r5-empty-password.pdf", + # # created by `qpdf --encrypt "asdfzxcv" "" 256 --force-R5 -- unencrypted.pdf r5-user-password.pdf` + "r5-user-password.pdf", + # # created by `qpdf --encrypt "" "asdfzxcv" 256 --force-R5 -- unencrypted.pdf r5-owner-password.pdf` + "r5-owner-password.pdf", + # created by `qpdf --encrypt "" "" 256 -- unencrypted.pdf r6-empty-password.pdf` + "r6-empty-password.pdf", + # created by `qpdf --encrypt "asdfzxcv" "" 256 -- unencrypted.pdf r6-user-password.pdf` + "r6-user-password.pdf", + # created by `qpdf --encrypt "" "asdfzxcv" 256 -- unencrypted.pdf r6-owner-password.pdf` + "r6-owner-password.pdf", ], ) -def test_encryption(src): - with open(src, "rb") as inputfile: - ipdf = PyPDF2.PdfReader(inputfile) - if src.endswith("enc0.pdf"): - assert not ipdf.is_encrypted - else: - assert ipdf.is_encrypted - ipdf.decrypt("asdfzxcv") - assert len(ipdf.pages) == 1 - dd = dict(ipdf.metadata) - # remove empty value entry - dd = {x[0]: x[1] for x in dd.items() if x[1]} - assert dd == { - "/Author": "cheng", - "/CreationDate": "D:20220414132421+05'24'", - "/Creator": "WPS Writer", - "/ModDate": "D:20220414132421+05'24'", - "/SourceModified": "D:20220414132421+05'24'", - "/Trapped": "/False", - } +def test_encryption(name): + inputfile = os.path.join(RESOURCE_ROOT, "encryption", name) + ipdf = PyPDF2.PdfReader(inputfile) + if inputfile.endswith("unencrypted.pdf"): + assert not ipdf.is_encrypted + else: + assert ipdf.is_encrypted + ipdf.decrypt("asdfzxcv") + assert len(ipdf.pages) == 1 + dd = dict(ipdf.metadata) + # remove empty value entry + dd = {x[0]: x[1] for x in dd.items() if x[1]} + assert dd == { + "/Author": "cheng", + "/CreationDate": "D:20220414132421+05'24'", + "/Creator": "WPS Writer", + "/ModDate": "D:20220414132421+05'24'", + "/SourceModified": "D:20220414132421+05'24'", + "/Trapped": "/False", + } @pytest.mark.parametrize( "names", [ - (["enc0.pdf", "enc4.pdf", "enc5.pdf", "enc6.pdf"]), + (["unencrypted.pdf", "r3-user-password.pdf", "r4-aes-user-password.pdf", "r5-user-password.pdf"]), ], ) def test_encryption_merge(names): diff --git a/tests/test_reader.py b/tests/test_reader.py index c1dfa3fd2..eb280fcac 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -294,9 +294,7 @@ def test_get_page_of_encrypted_file_new_algorithm(pdffile, password): IndexError for get_page() of decrypted file """ path = os.path.join(RESOURCE_ROOT, pdffile) - with pytest.raises(NotImplementedError) as exc: - PdfReader(path, password=password).pages[0] - assert exc.value.args[0] == "encryption R=6 NOT supported!" + PdfReader(path, password=password).pages[0] @pytest.mark.parametrize( From 6cbf6e904ea320c7c660f34c995b17dbfe84e7ce Mon Sep 17 00:00:00 2001 From: exiledkingcc Date: Tue, 21 Jun 2022 07:28:06 +0800 Subject: [PATCH 3/6] make decrypt clear --- PyPDF2/_encryption.py | 88 +++++++++++++++++-------------------------- PyPDF2/_merger.py | 6 +-- PyPDF2/_reader.py | 75 +++++++++++++++--------------------- 3 files changed, 67 insertions(+), 102 deletions(-) diff --git a/PyPDF2/_encryption.py b/PyPDF2/_encryption.py index 635bf56ac..809208ad1 100644 --- a/PyPDF2/_encryption.py +++ b/PyPDF2/_encryption.py @@ -28,7 +28,7 @@ import hashlib import random import struct -from typing import Dict, Optional, Tuple, Union, cast +from typing import Optional, Tuple, Union, cast from PyPDF2.errors import DependencyError from PyPDF2.generic import ( @@ -226,15 +226,6 @@ def _padding(data: bytes) -> bytes: return (data + _PADDING)[:32] -def _bytes(value: Union[bytes, str]) -> bytes: - if isinstance(value, bytes): - return value - try: - return value.encode("latin-1") - except Exception: # noqa - return value.encode("utf-8") - - class AlgV4: @staticmethod def compute_key( @@ -642,10 +633,13 @@ def __init__( self.StrF = StrF self.EFF = EFF + # 1 => owner password + # 2 => user password + self._password_type = 0 self._key: Optional[bytes] = None - # keep key - self._user_keys: Dict = {} - self._owner_keys: Dict = {} + + def verified(self) -> bool: + return self._password_type != 0 def decrypt_object(self, obj: PdfObject, idnum: int, generation: int) -> PdfObject: """ @@ -690,7 +684,7 @@ def decrypt_object(self, obj: PdfObject, idnum: int, generation: int) -> PdfObje key_hash.update(b"sAlT") aes128_key = key_hash.digest()[: min(n + 5, 16)] - # for V=5 use AES-256 + # for AES-256 aes256_key = key stmCrypt = self._get_crypt(self.StmF, rc4_key, aes128_key, aes256_key) @@ -713,36 +707,22 @@ def _get_crypt( else: return CryptRC4(rc4_key) - def verify(self, user_pwd: Union[bytes, str], owner_pwd: Union[bytes, str]) -> int: - up_bytes = _bytes(user_pwd) - op_bytes = _bytes(owner_pwd) - - key = self._user_keys.get(up_bytes) - if key: - self._key = key - return 1 - - key = self._owner_keys.get(op_bytes) - if key: - self._key = key - return 2 - - rc = 0 - if self.algV <= 4: - key, rc = self.verify_v4(up_bytes, op_bytes) + def verify(self, password: Union[bytes, str]) -> int: + if isinstance(password, str): + try: + pwd = password.encode("latin-1") + except Exception: # noqa + pwd = password.encode("utf-8") else: - key, rc = self.verify_v5(up_bytes, op_bytes) + pwd = password - if rc == 1: - self._key = key - self._user_keys[up_bytes] = key - elif rc == 2: + key, rc = self.verify_v4(pwd) if self.algV <= 4 else self.verify_v5(pwd) + if rc != 0: + self._password_type = rc self._key = key - self._owner_keys[op_bytes] = key - return rc - def verify_v4(self, user_pwd: bytes, owner_pwd: bytes) -> Tuple[bytes, int]: + def verify_v4(self, password: bytes) -> Tuple[bytes, int]: R = cast(int, self.entry["/R"]) P = cast(int, self.entry["/P"]) P = (P + 0x100000000) % 0x100000000 # maybe < 0 @@ -750,8 +730,9 @@ def verify_v4(self, user_pwd: bytes, owner_pwd: bytes) -> Tuple[bytes, int]: o_entry = cast(ByteStringObject, self.entry["/O"].get_object()).original_bytes u_entry = cast(ByteStringObject, self.entry["/U"].get_object()).original_bytes - key = AlgV4.verify_user_password( - user_pwd, + # verify owner password first + key = AlgV4.verify_owner_password( + password, R, self.key_size, o_entry, @@ -761,9 +742,9 @@ def verify_v4(self, user_pwd: bytes, owner_pwd: bytes) -> Tuple[bytes, int]: metadata_encrypted, ) if key: - return key, 1 - key = AlgV4.verify_owner_password( - owner_pwd, + return key, 2 + key = AlgV4.verify_user_password( + password, R, self.key_size, o_entry, @@ -773,26 +754,25 @@ def verify_v4(self, user_pwd: bytes, owner_pwd: bytes) -> Tuple[bytes, int]: metadata_encrypted, ) if key: - return key, 2 + return key, 1 return b"", 0 - def verify_v5(self, user_pwd: bytes, owner_pwd: bytes) -> Tuple[bytes, int]: + def verify_v5(self, password: bytes) -> Tuple[bytes, int]: # TODO: use SASLprep process o_entry = cast(ByteStringObject, self.entry["/O"].get_object()).original_bytes u_entry = cast(ByteStringObject, self.entry["/U"].get_object()).original_bytes oe_entry = cast(ByteStringObject, self.entry["/OE"].get_object()).original_bytes ue_entry = cast(ByteStringObject, self.entry["/UE"].get_object()).original_bytes - rc = 0 - key = AlgV5.verify_user_password(self.algR, user_pwd, u_entry, ue_entry) - if key: + # verify owner password first + key = AlgV5.verify_owner_password(self.algR, password, o_entry, oe_entry, u_entry) + rc = 2 + if not key: + key = AlgV5.verify_user_password(self.algR, password, u_entry, ue_entry) rc = 1 - else: - key = AlgV5.verify_owner_password(self.algR, owner_pwd, o_entry, oe_entry, u_entry) - if key: - rc = 2 - if rc == 0: + if not key: return b"", 0 + # verify Perms perms = cast(ByteStringObject, self.entry["/Perms"].get_object()).original_bytes P = cast(int, self.entry["/P"]) diff --git a/PyPDF2/_merger.py b/PyPDF2/_merger.py index 4ce53fd8b..45c2bae1a 100644 --- a/PyPDF2/_merger.py +++ b/PyPDF2/_merger.py @@ -137,7 +137,7 @@ def merge( reader = PdfReader(stream, strict=self.strict) # type: ignore[arg-type] self.inputs.append((stream, reader, my_file)) if encryption_obj is not None: - reader._encryption = encryption_obj + reader.encryption = encryption_obj # Find the range of pages to merge. if pages is None: @@ -204,8 +204,8 @@ def _create_stream( stream = FileIO(fileobj, "rb") my_file = True elif isinstance(fileobj, PdfReader): - if hasattr(fileobj, "_encryption"): - encryption_obj = fileobj._encryption + if fileobj.encryption: + encryption_obj = fileobj.encryption orig_tell = fileobj.stream.tell() fileobj.stream.seek(0) stream = BytesIO(fileobj.stream.read()) diff --git a/PyPDF2/_reader.py b/PyPDF2/_reader.py index 669f2dbbd..df0ee5168 100644 --- a/PyPDF2/_reader.py +++ b/PyPDF2/_reader.py @@ -45,6 +45,7 @@ cast, ) +from ._encryption import Encryption from ._page import PageObject, _VirtualList from ._utils import ( StrByteType, @@ -66,7 +67,6 @@ from .constants import PagesAttributes as PA from .constants import TrailerKeys as TK from .errors import ( - DependencyError, PdfReadError, PdfReadWarning, PdfStreamError, @@ -254,8 +254,26 @@ def __init__( self.stream = stream self._override_encryption = False - if password is not None and self.decrypt(password) == 0: - raise PdfReadError("Wrong password") + self.encryption: Optional[Encryption] = None + if self.is_encrypted: + self._override_encryption = True + # Some documents may not have a /ID, use two empty + # byte strings instead. Solves + # https://github.com/mstamy2/PyPDF2/issues/608 + id_entry = self.trailer.get(TK.ID) + id1_entry = id_entry[0].get_object().original_bytes if id_entry else b"" + encryptEntry = cast(DictionaryObject, self.trailer[TK.ENCRYPT].get_object()) + self.encryption = Encryption.read(encryptEntry, id1_entry) + + # try empty password if no password provided + pwd = password if password is not None else b"" + if self.encryption.verify(pwd) == 0 and password is not None: + # raise if password provided + raise PdfReadError("Wrong password") + self._override_encryption = False + else: + if password is not None: + raise PdfReadError("Not encrypted file") @property def metadata(self) -> Optional[DocumentInformation]: @@ -344,17 +362,7 @@ def _get_num_pages(self) -> int: # the PDF file's page count is used in this case. Otherwise, # the original method (flattened page count) is used. if self.is_encrypted: - try: - self._override_encryption = True - self.decrypt("") - return self.trailer[TK.ROOT]["/Pages"]["/Count"] # type: ignore - except DependencyError as e: - # make dependency error clear to users - raise e - except Exception: - raise PdfReadError("File has not been decrypted") - finally: - self._override_encryption = False + return self.trailer[TK.ROOT]["/Pages"]["/Count"] # type: ignore else: if self.flattened_pages is None: self._flatten() @@ -1047,13 +1055,13 @@ def get_object(self, indirect_reference: IndirectObject) -> Optional[PdfObject]: retval = read_object(self.stream, self) # type: ignore # override encryption is used for the /Encrypt dictionary - if not self._override_encryption and self.is_encrypted: + if not self._override_encryption and self.encryption is not None: # if we don't have the encryption key: - if not hasattr(self, "_encryption"): - raise PdfReadError("file has not been decrypted") + if not self.encryption.verified(): + raise PdfReadError("File has not been decrypted") # otherwise, decrypt here... retval = cast(PdfObject, retval) - retval = self._encryption.decrypt_object( + retval = self.encryption.decrypt_object( retval, indirect_reference.idnum, indirect_reference.generation ) else: @@ -1562,15 +1570,12 @@ def decrypt(self, password: Union[str, bytes]) -> int: :return: ``0`` if the password failed, ``1`` if the password matched the user password, and ``2`` if the password matched the owner password. :rtype: int - :raises NotImplementedError: if document uses an unsupported encryption method. """ - - self._override_encryption = True - try: - return self._decrypt(password) - finally: - self._override_encryption = False + if not self.encryption: + raise PdfReadError("Not encrypted file") + # TODO: raise Exception for wrong password + return self.encryption.verify(password) def decode_permissions(self, permissions_code: int) -> Dict[str, bool]: # Takes the permissions as an integer, returns the allowed access @@ -1587,26 +1592,6 @@ def decode_permissions(self, permissions_code: int) -> Dict[str, bool]: ) # bit 12 return permissions - def _decrypt(self, password: Union[str, bytes]) -> int: - # already got the KEY - if hasattr(self, "_encryption"): - return 3 - from PyPDF2._encryption import Encryption - - # Some documents may not have a /ID, use two empty - # byte strings instead. Solves - # https://github.com/mstamy2/PyPDF2/issues/608 - id_entry = self.trailer.get(TK.ID) - id1_entry = id_entry[0].get_object().original_bytes if id_entry else b"" - encryptEntry = cast(DictionaryObject, self.trailer[TK.ENCRYPT].get_object()) - encryption = Encryption.read(encryptEntry, id1_entry) - # maybe password is owner password - # TODO: add/modify api to set owner password - rr = encryption.verify(password, password) - if rr > 0: - self._encryption = encryption - return rr - @property def is_encrypted(self) -> bool: """ From 532930b6e0ccd42b4c2e1b5c78ca4764392ad97b Mon Sep 17 00:00:00 2001 From: exiledkingcc Date: Sun, 26 Jun 2022 22:54:28 +0800 Subject: [PATCH 4/6] make Encryption private for PdfReader --- PyPDF2/__init__.py | 2 ++ PyPDF2/_encryption.py | 36 +++++++++++++++++++++--------------- PyPDF2/_merger.py | 6 +++--- PyPDF2/_reader.py | 25 ++++++++++++------------- 4 files changed, 38 insertions(+), 31 deletions(-) diff --git a/PyPDF2/__init__.py b/PyPDF2/__init__.py index f7fcd92b9..08b03d5ec 100644 --- a/PyPDF2/__init__.py +++ b/PyPDF2/__init__.py @@ -7,6 +7,7 @@ You can read the full docs at https://pypdf2.readthedocs.io/. """ +from ._encryption import PasswordType from ._merger import PdfFileMerger, PdfMerger from ._page import PageObject, Transformation from ._reader import DocumentInformation, PdfFileReader, PdfReader @@ -29,4 +30,5 @@ "PdfWriter", "Transformation", "PageObject", + "PasswordType", ] diff --git a/PyPDF2/_encryption.py b/PyPDF2/_encryption.py index 809208ad1..9ef5966d9 100644 --- a/PyPDF2/_encryption.py +++ b/PyPDF2/_encryption.py @@ -25,6 +25,7 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. +from enum import Enum import hashlib import random import struct @@ -509,7 +510,6 @@ def calculate_hash(R: int, password: bytes, salt: bytes, udata: bytes) -> bytes: )[sum(E[:16]) % 3] K = hash_fn(E).digest() if count >= 64 and E[-1] <= count - 32: - print(count, E[-1]) break return K[:32] @@ -612,6 +612,12 @@ def compute_Perms_value(key: bytes, p: int, metadata_encrypted: bool) -> bytes: return perms +class PasswordType(Enum): + NOT_DECRYPTED = 0 + USER_PASSWORD = 1 + OWNER_PASSWORD = 2 + + class Encryption: def __init__( self, @@ -635,11 +641,11 @@ def __init__( # 1 => owner password # 2 => user password - self._password_type = 0 + self._password_type = PasswordType.NOT_DECRYPTED self._key: Optional[bytes] = None - def verified(self) -> bool: - return self._password_type != 0 + def is_decrypted(self) -> bool: + return self._password_type != PasswordType.NOT_DECRYPTED def decrypt_object(self, obj: PdfObject, idnum: int, generation: int) -> PdfObject: """ @@ -707,7 +713,7 @@ def _get_crypt( else: return CryptRC4(rc4_key) - def verify(self, password: Union[bytes, str]) -> int: + def verify(self, password: Union[bytes, str]) -> PasswordType: if isinstance(password, str): try: pwd = password.encode("latin-1") @@ -717,12 +723,12 @@ def verify(self, password: Union[bytes, str]) -> int: pwd = password key, rc = self.verify_v4(pwd) if self.algV <= 4 else self.verify_v5(pwd) - if rc != 0: + if rc != PasswordType.NOT_DECRYPTED: self._password_type = rc self._key = key return rc - def verify_v4(self, password: bytes) -> Tuple[bytes, int]: + def verify_v4(self, password: bytes) -> Tuple[bytes, PasswordType]: R = cast(int, self.entry["/R"]) P = cast(int, self.entry["/P"]) P = (P + 0x100000000) % 0x100000000 # maybe < 0 @@ -742,7 +748,7 @@ def verify_v4(self, password: bytes) -> Tuple[bytes, int]: metadata_encrypted, ) if key: - return key, 2 + return key, PasswordType.OWNER_PASSWORD key = AlgV4.verify_user_password( password, R, @@ -754,10 +760,10 @@ def verify_v4(self, password: bytes) -> Tuple[bytes, int]: metadata_encrypted, ) if key: - return key, 1 - return b"", 0 + return key, PasswordType.USER_PASSWORD + return b"", PasswordType.NOT_DECRYPTED - def verify_v5(self, password: bytes) -> Tuple[bytes, int]: + def verify_v5(self, password: bytes) -> Tuple[bytes, PasswordType]: # TODO: use SASLprep process o_entry = cast(ByteStringObject, self.entry["/O"].get_object()).original_bytes u_entry = cast(ByteStringObject, self.entry["/U"].get_object()).original_bytes @@ -766,12 +772,12 @@ def verify_v5(self, password: bytes) -> Tuple[bytes, int]: # verify owner password first key = AlgV5.verify_owner_password(self.algR, password, o_entry, oe_entry, u_entry) - rc = 2 + rc = PasswordType.OWNER_PASSWORD if not key: key = AlgV5.verify_user_password(self.algR, password, u_entry, ue_entry) - rc = 1 + rc = PasswordType.USER_PASSWORD if not key: - return b"", 0 + return b"", PasswordType.NOT_DECRYPTED # verify Perms perms = cast(ByteStringObject, self.entry["/Perms"].get_object()).original_bytes @@ -779,7 +785,7 @@ def verify_v5(self, password: bytes) -> Tuple[bytes, int]: P = (P + 0x100000000) % 0x100000000 # maybe < 0 metadata_encrypted = self.entry.get("/EncryptMetadata", True) if not AlgV5.verify_perms(key, perms, P, metadata_encrypted): - return b"", 0 + return b"", PasswordType.NOT_DECRYPTED return key, rc @staticmethod diff --git a/PyPDF2/_merger.py b/PyPDF2/_merger.py index 45c2bae1a..49a218cb7 100644 --- a/PyPDF2/_merger.py +++ b/PyPDF2/_merger.py @@ -137,7 +137,7 @@ def merge( reader = PdfReader(stream, strict=self.strict) # type: ignore[arg-type] self.inputs.append((stream, reader, my_file)) if encryption_obj is not None: - reader.encryption = encryption_obj + reader._encryption = encryption_obj # Find the range of pages to merge. if pages is None: @@ -204,8 +204,8 @@ def _create_stream( stream = FileIO(fileobj, "rb") my_file = True elif isinstance(fileobj, PdfReader): - if fileobj.encryption: - encryption_obj = fileobj.encryption + if fileobj._encryption: + encryption_obj = fileobj._encryption orig_tell = fileobj.stream.tell() fileobj.stream.seek(0) stream = BytesIO(fileobj.stream.read()) diff --git a/PyPDF2/_reader.py b/PyPDF2/_reader.py index df0ee5168..ef0590047 100644 --- a/PyPDF2/_reader.py +++ b/PyPDF2/_reader.py @@ -45,7 +45,7 @@ cast, ) -from ._encryption import Encryption +from ._encryption import Encryption, PasswordType from ._page import PageObject, _VirtualList from ._utils import ( StrByteType, @@ -254,7 +254,7 @@ def __init__( self.stream = stream self._override_encryption = False - self.encryption: Optional[Encryption] = None + self._encryption: Optional[Encryption] = None if self.is_encrypted: self._override_encryption = True # Some documents may not have a /ID, use two empty @@ -262,12 +262,12 @@ def __init__( # https://github.com/mstamy2/PyPDF2/issues/608 id_entry = self.trailer.get(TK.ID) id1_entry = id_entry[0].get_object().original_bytes if id_entry else b"" - encryptEntry = cast(DictionaryObject, self.trailer[TK.ENCRYPT].get_object()) - self.encryption = Encryption.read(encryptEntry, id1_entry) + encrypt_entry = cast(DictionaryObject, self.trailer[TK.ENCRYPT].get_object()) + self._encryption = Encryption.read(encrypt_entry, id1_entry) # try empty password if no password provided pwd = password if password is not None else b"" - if self.encryption.verify(pwd) == 0 and password is not None: + if self._encryption.verify(pwd) == 0 and password is not None: # raise if password provided raise PdfReadError("Wrong password") self._override_encryption = False @@ -1055,13 +1055,13 @@ def get_object(self, indirect_reference: IndirectObject) -> Optional[PdfObject]: retval = read_object(self.stream, self) # type: ignore # override encryption is used for the /Encrypt dictionary - if not self._override_encryption and self.encryption is not None: + if not self._override_encryption and self._encryption is not None: # if we don't have the encryption key: - if not self.encryption.verified(): + if not self._encryption.is_decrypted(): raise PdfReadError("File has not been decrypted") # otherwise, decrypt here... retval = cast(PdfObject, retval) - retval = self.encryption.decrypt_object( + retval = self._encryption.decrypt_object( retval, indirect_reference.idnum, indirect_reference.generation ) else: @@ -1554,7 +1554,7 @@ def readNextEndLine( deprecate_no_replacement("readNextEndLine") return self.read_next_end_line(stream, limit_offset) - def decrypt(self, password: Union[str, bytes]) -> int: + def decrypt(self, password: Union[str, bytes]) -> PasswordType: """ When using an encrypted / secured PDF file with the PDF Standard encryption handler, this function will allow the file to be decrypted. @@ -1567,15 +1567,14 @@ def decrypt(self, password: Union[str, bytes]) -> int: this library. :param str password: The password to match. - :return: ``0`` if the password failed, ``1`` if the password matched the user - password, and ``2`` if the password matched the owner password. + :return: `PasswordType`. :rtype: int method. """ - if not self.encryption: + if not self._encryption: raise PdfReadError("Not encrypted file") # TODO: raise Exception for wrong password - return self.encryption.verify(password) + return self._encryption.verify(password) def decode_permissions(self, permissions_code: int) -> Dict[str, bool]: # Takes the permissions as an integer, returns the allowed access From 5d076fe1f59dad87817c83a5334e217b44cf0e4c Mon Sep 17 00:00:00 2001 From: exiledkingcc Date: Sun, 26 Jun 2022 23:17:41 +0800 Subject: [PATCH 5/6] add more tests --- resources/encryption/r6-both-passwords.pdf | Bin 0 -> 16709 bytes tests/test_encryption.py | 17 +++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 resources/encryption/r6-both-passwords.pdf diff --git a/resources/encryption/r6-both-passwords.pdf b/resources/encryption/r6-both-passwords.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8bd720622922ea62941c34f4cfae03547e803358 GIT binary patch literal 16709 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 HCF=hJK@CHr literal 0 HcmV?d00001 diff --git a/tests/test_encryption.py b/tests/test_encryption.py index 5c3e75e97..711cd6e5a 100644 --- a/tests/test_encryption.py +++ b/tests/test_encryption.py @@ -62,6 +62,23 @@ def test_encryption(name): } +@pytest.mark.parametrize( + ("name", "user_passwd", "owner_passwd"), + [ + # created by `qpdf --encrypt "foo" "bar" 256 -- unencrypted.pdf r6-both-passwords.pdf` + ("r6-both-passwords.pdf", "foo", "bar"), + ], +) +def test_both_password(name, user_passwd, owner_passwd): + from PyPDF2 import PasswordType + inputfile = os.path.join(RESOURCE_ROOT, "encryption", name) + ipdf = PyPDF2.PdfReader(inputfile) + assert ipdf.is_encrypted + assert ipdf.decrypt(user_passwd) == PasswordType.USER_PASSWORD + assert ipdf.decrypt(owner_passwd) == PasswordType.OWNER_PASSWORD + assert len(ipdf.pages) == 1 + + @pytest.mark.parametrize( "names", [ From dfaa735d228bc57f6ca7e594f9b0a2f9ed57cd48 Mon Sep 17 00:00:00 2001 From: exiledkingcc Date: Sun, 26 Jun 2022 23:41:35 +0800 Subject: [PATCH 6/6] fix test with issue 327 --- PyPDF2/_encryption.py | 4 ++-- PyPDF2/_reader.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/PyPDF2/_encryption.py b/PyPDF2/_encryption.py index 9ef5966d9..e343556c8 100644 --- a/PyPDF2/_encryption.py +++ b/PyPDF2/_encryption.py @@ -25,7 +25,7 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -from enum import Enum +from enum import IntEnum import hashlib import random import struct @@ -612,7 +612,7 @@ def compute_Perms_value(key: bytes, p: int, metadata_encrypted: bool) -> bytes: return perms -class PasswordType(Enum): +class PasswordType(IntEnum): NOT_DECRYPTED = 0 USER_PASSWORD = 1 OWNER_PASSWORD = 2 diff --git a/PyPDF2/_reader.py b/PyPDF2/_reader.py index 2db85777f..5f349be07 100644 --- a/PyPDF2/_reader.py +++ b/PyPDF2/_reader.py @@ -267,7 +267,7 @@ def __init__( # try empty password if no password provided pwd = password if password is not None else b"" - if self._encryption.verify(pwd) == 0 and password is not None: + if self._encryption.verify(pwd) == PasswordType.NOT_DECRYPTED and password is not None: # raise if password provided raise PdfReadError("Wrong password") self._override_encryption = False