From e356a39f28331c172b7d2fc2c22a140d13da37ab Mon Sep 17 00:00:00 2001 From: Martin Thoma Date: Sat, 10 Dec 2022 15:01:19 +0100 Subject: [PATCH] MAINT: Consistent parameter/variable names (#1483) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * owner_pwd ➔ owner_password * user_pwd ➔ user_password See #1187 --- PyPDF2/_encryption.py | 41 +++++++++++++++++++++++++--------------- PyPDF2/_security.py | 10 ++++++---- PyPDF2/_writer.py | 33 +++++++++++++++++++++++--------- tests/test_encryption.py | 6 +++++- tests/test_writer.py | 10 +++++++--- 5 files changed, 68 insertions(+), 32 deletions(-) diff --git a/PyPDF2/_encryption.py b/PyPDF2/_encryption.py index b07211ea8..5c80e6003 100644 --- a/PyPDF2/_encryption.py +++ b/PyPDF2/_encryption.py @@ -304,7 +304,7 @@ def compute_key( return u_hash_digest[:length] @staticmethod - def compute_O_value_key(owner_pwd: bytes, rev: int, key_size: int) -> bytes: + def compute_O_value_key(owner_password: bytes, rev: int, key_size: int) -> bytes: """ Algorithm 3: Computing the encryption dictionary’s O (owner password) value. @@ -336,7 +336,7 @@ def compute_O_value_key(owner_pwd: bytes, rev: int, key_size: int) -> bytes: h) Store the output from the final invocation of the RC4 function as the value of the O entry in the encryption dictionary. """ - a = _padding(owner_pwd) + a = _padding(owner_password) o_hash_digest = hashlib.md5(a).digest() if rev >= 3: @@ -347,9 +347,9 @@ def compute_O_value_key(owner_pwd: bytes, rev: int, key_size: int) -> bytes: return rc4_key @staticmethod - def compute_O_value(rc4_key: bytes, user_pwd: bytes, rev: int) -> bytes: + def compute_O_value(rc4_key: bytes, user_password: bytes, rev: int) -> bytes: """See :func:`compute_O_value_key`.""" - a = _padding(user_pwd) + a = _padding(user_password) rc4_enc = RC4_encrypt(rc4_key, a) if rev >= 3: for i in range(1, 20): @@ -411,7 +411,7 @@ def compute_U_value(key: bytes, rev: int, id1_entry: bytes) -> bytes: @staticmethod def verify_user_password( - user_pwd: bytes, + user_password: bytes, rev: int, key_size: int, o_entry: bytes, @@ -434,7 +434,7 @@ def verify_user_password( to decrypt the document. """ key = AlgV4.compute_key( - user_pwd, rev, key_size, o_entry, P, id1_entry, metadata_encrypted + user_password, rev, key_size, o_entry, P, id1_entry, metadata_encrypted ) u_value = AlgV4.compute_U_value(key, rev, id1_entry) if rev >= 3: @@ -446,7 +446,7 @@ def verify_user_password( @staticmethod def verify_owner_password( - owner_pwd: bytes, + owner_password: bytes, rev: int, key_size: int, o_entry: bytes, @@ -470,17 +470,24 @@ 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 = AlgV4.compute_O_value_key(owner_pwd, rev, key_size) + rc4_key = AlgV4.compute_O_value_key(owner_password, rev, key_size) if rev <= 2: - u_pwd = RC4_decrypt(rc4_key, o_entry) + user_password = RC4_decrypt(rc4_key, o_entry) else: - u_pwd = o_entry + user_password = o_entry for i in range(19, -1, -1): key = bytes(bytearray(x ^ i for x in rc4_key)) - u_pwd = RC4_decrypt(key, u_pwd) + user_password = RC4_decrypt(key, user_password) return AlgV4.verify_user_password( - u_pwd, rev, key_size, o_entry, u_entry, P, id1_entry, metadata_encrypted + user_password, + rev, + key_size, + o_entry, + u_entry, + P, + id1_entry, + metadata_encrypted, ) @@ -574,10 +581,14 @@ def verify_perms( @staticmethod def generate_values( - user_pwd: bytes, owner_pwd: bytes, key: bytes, p: int, metadata_encrypted: bool + user_password: bytes, + owner_password: bytes, + key: bytes, + p: int, + metadata_encrypted: bool, ) -> Dict[Any, Any]: - u_value, ue_value = AlgV5.compute_U_value(user_pwd, key) - o_value, oe_value = AlgV5.compute_O_value(owner_pwd, key, u_value) + u_value, ue_value = AlgV5.compute_U_value(user_password, key) + o_value, oe_value = AlgV5.compute_O_value(owner_password, key, u_value) perms = AlgV5.compute_Perms_value(key, p, metadata_encrypted) return { "/U": u_value, diff --git a/PyPDF2/_security.py b/PyPDF2/_security.py index 1fc6d1e59..47e5c3734 100644 --- a/PyPDF2/_security.py +++ b/PyPDF2/_security.py @@ -105,19 +105,21 @@ def _alg32( return md5_hash[:keylen] -def _alg33(owner_pwd: str, user_pwd: str, rev: Literal[2, 3, 4], keylen: int) -> bytes: +def _alg33( + owner_password: str, user_password: str, rev: Literal[2, 3, 4], keylen: int +) -> bytes: """ Implementation of algorithm 3.3 of the PDF standard security handler, section 3.5.2 of the PDF 1.6 reference. """ # steps 1 - 4 - key = _alg33_1(owner_pwd, rev, keylen) + key = _alg33_1(owner_password, rev, keylen) # 5. Pad or truncate the user password string as described in step 1 of # algorithm 3.2. - user_pwd_bytes = b_((user_pwd + str_(_encryption_padding))[:32]) + user_password_bytes = b_((user_password + str_(_encryption_padding))[:32]) # 6. Encrypt the result of step 5, using an RC4 encryption function with # the encryption key obtained in step 4. - val = RC4_encrypt(key, user_pwd_bytes) + val = RC4_encrypt(key, user_password_bytes) # 7. (Revision 3 or greater) Do the following 19 times: Take the output # from the previous invocation of the RC4 function and pass it as input to # a new invocation of the function; use an encryption key generated by diff --git a/PyPDF2/_writer.py b/PyPDF2/_writer.py index 3a46dd1f9..b4833d19b 100644 --- a/PyPDF2/_writer.py +++ b/PyPDF2/_writer.py @@ -776,17 +776,19 @@ def cloneDocumentFromReader( def encrypt( self, - user_pwd: str, - owner_pwd: Optional[str] = None, + user_password: str, + owner_password: Optional[str] = None, use_128bit: bool = True, permissions_flag: UserAccessPermissions = ALL_DOCUMENT_PERMISSIONS, + user_pwd: Optional[str] = None, # deprecated + owner_pwd: Optional[str] = None, # deprecated ) -> None: """ Encrypt this PDF file with the PDF Standard encryption handler. - :param str user_pwd: The "user password", which allows for opening + :param str user_password: The "user password", which allows for opening and reading the PDF file with the restrictions provided. - :param str owner_pwd: The "owner password", which allows for + :param str owner_password: The "owner password", which allows for opening the PDF files without any restrictions. By default, the owner password is the same as the user password. :param bool use_128bit: flag as to whether to use 128bit @@ -800,8 +802,21 @@ def encrypt( control annotations, 9 for form fields, 10 for extraction of text and graphics. """ - if owner_pwd is None: - owner_pwd = user_pwd + if user_pwd is not None: + if user_password is not None: + raise ValueError( + "Please only set 'user_password'. " + "The 'user_pwd' argument is deprecated." + ) + else: + warnings.warn( + "Please use 'user_password' instead of 'user_pwd'. " + "The 'user_pwd' argument is deprecated and will be removed " + "in PyPDF2==3.0.0." + ) + user_password = user_pwd + if owner_password is None: + owner_password = user_password if use_128bit: V = 2 rev = 3 @@ -811,15 +826,15 @@ def encrypt( rev = 2 keylen = int(40 / 8) P = permissions_flag - O = ByteStringObject(_alg33(owner_pwd, user_pwd, rev, keylen)) # type: ignore[arg-type] + O = ByteStringObject(_alg33(owner_password, user_password, rev, keylen)) # type: ignore[arg-type] ID_1 = ByteStringObject(md5((repr(time.time())).encode("utf8")).digest()) ID_2 = ByteStringObject(md5((repr(random.random())).encode("utf8")).digest()) self._ID = ArrayObject((ID_1, ID_2)) if rev == 2: - U, key = _alg34(user_pwd, O, P, ID_1) + U, key = _alg34(user_password, O, P, ID_1) else: assert rev == 3 - U, key = _alg35(user_pwd, rev, keylen, O, P, ID_1, False) # type: ignore[arg-type] + U, key = _alg35(user_password, rev, keylen, O, P, ID_1, False) # type: ignore[arg-type] encrypt = DictionaryObject() encrypt[NameObject(SA.FILTER)] = NameObject("/Standard") encrypt[NameObject("/V")] = NumberObject(V) diff --git a/tests/test_encryption.py b/tests/test_encryption.py index 4534dfd34..eb20582a1 100644 --- a/tests/test_encryption.py +++ b/tests/test_encryption.py @@ -176,7 +176,11 @@ def test_generate_values(): return key = b"0123456789123451" values = AlgV5.generate_values( - user_pwd=b"foo", owner_pwd=b"bar", key=key, p=0, metadata_encrypted=True + user_password=b"foo", + owner_password=b"bar", + key=key, + p=0, + metadata_encrypted=True, ) assert values == { "/U": values["/U"], diff --git a/tests/test_writer.py b/tests/test_writer.py index 1c4e64db5..1edc1aa5e 100644 --- a/tests/test_writer.py +++ b/tests/test_writer.py @@ -398,10 +398,10 @@ def test_fill_form(): @pytest.mark.parametrize( - ("use_128bit", "user_pwd", "owner_pwd"), + ("use_128bit", "user_password", "owner_password"), [(True, "userpwd", "ownerpwd"), (False, "userpwd", "ownerpwd")], ) -def test_encrypt(use_128bit, user_pwd, owner_pwd): +def test_encrypt(use_128bit, user_password, owner_password): reader = PdfReader(RESOURCE_ROOT / "form.pdf") writer = PdfWriter() @@ -409,7 +409,11 @@ def test_encrypt(use_128bit, user_pwd, owner_pwd): orig_text = page.extract_text() writer.add_page(page) - writer.encrypt(user_pwd=user_pwd, owner_pwd=owner_pwd, use_128bit=use_128bit) + writer.encrypt( + user_password=user_password, + owner_password=owner_password, + use_128bit=use_128bit, + ) # write "output" to PyPDF2-output.pdf tmp_filename = "dont_commit_encrypted.pdf"