Skip to content

Commit

Permalink
MAINT: Consistent parameter/variable names (#1483)
Browse files Browse the repository at this point in the history
* owner_pwd ➔ owner_password
* user_pwd ➔ user_password

See #1187
  • Loading branch information
MartinThoma committed Dec 10, 2022
1 parent fe461a1 commit e356a39
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 32 deletions.
41 changes: 26 additions & 15 deletions PyPDF2/_encryption.py
Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand All @@ -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):
Expand Down Expand Up @@ -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,
Expand All @@ -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:
Expand All @@ -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,
Expand All @@ -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,
)


Expand Down Expand Up @@ -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,
Expand Down
10 changes: 6 additions & 4 deletions PyPDF2/_security.py
Expand Up @@ -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
Expand Down
33 changes: 24 additions & 9 deletions PyPDF2/_writer.py
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand Down
6 changes: 5 additions & 1 deletion tests/test_encryption.py
Expand Up @@ -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"],
Expand Down
10 changes: 7 additions & 3 deletions tests/test_writer.py
Expand Up @@ -398,18 +398,22 @@ 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()

page = reader.pages[0]
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"
Expand Down

0 comments on commit e356a39

Please sign in to comment.