Mutation testing applies common mistakes to the codebase and checks if the tests capture them. Those changes are called "mutations" (or mutants). Killing a mutant means to add a test which would fail if the mutation is applied.
_writer.py::encrypt (V2)
--- PyPDF2/_writer.py
+++ PyPDF2/_writer.py
@@ -709,7 +709,7 @@
encrypt[NameObject(SA.FILTER)] = NameObject("/Standard")
encrypt[NameObject("/V")] = NumberObject(V)
if V == 2:
- encrypt[NameObject(SA.LENGTH)] = NumberObject(keylen * 8)
+ encrypt[NameObject(SA.LENGTH)] = NumberObject(keylen * 9)
encrypt[NameObject(ED.R)] = NumberObject(rev)
encrypt[NameObject(ED.O)] = ByteStringObject(O)
encrypt[NameObject(ED.U)] = ByteStringObject(U)
--- PyPDF2/_security.py
+++ PyPDF2/_security.py
@@ -78,7 +78,7 @@
m.update(id1_entry.original_bytes)
# 6. (Revision 3 or greater) If document metadata is not being encrypted,
# pass 4 bytes with the value 0xFFFFFFFF to the MD5 hash function.
- if rev >= 3 and not metadata_encrypt:
+ if rev > 3 and not metadata_encrypt:
m.update(b"\xff\xff\xff\xff")
# 7. Finish the hash.
md5_hash = m.digest()
Mutant 2326
--- PyPDF2/_security.py
+++ PyPDF2/_security.py
@@ -78,7 +78,7 @@
m.update(id1_entry.original_bytes)
# 6. (Revision 3 or greater) If document metadata is not being encrypted,
# pass 4 bytes with the value 0xFFFFFFFF to the MD5 hash function.
- if rev >= 3 and not metadata_encrypt:
+ if rev >= 4 and not metadata_encrypt:
m.update(b"\xff\xff\xff\xff")
# 7. Finish the hash.
md5_hash = m.digest()
Mutant 2337
--- PyPDF2/_security.py
+++ PyPDF2/_security.py
@@ -106,7 +106,7 @@
key = _alg33_1(owner_pwd, 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_pwd_bytes = b_((user_pwd + str_(_encryption_padding))[:33])
# 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)
Mutant 2340 ✔️
--- PyPDF2/_security.py
+++ PyPDF2/_security.py
@@ -116,7 +116,7 @@
# taking each byte of the encryption key obtained in step 4 and performing
# an XOR operation between that byte and the single-byte value of the
# iteration counter (from 1 to 19).
- if rev >= 3:
+ if rev > 3:
for i in range(1, 20):
new_key = ""
for key_char in key:
Mutant 2341 ✔️
--- PyPDF2/_security.py
+++ PyPDF2/_security.py
@@ -116,7 +116,7 @@
# taking each byte of the encryption key obtained in step 4 and performing
# an XOR operation between that byte and the single-byte value of the
# iteration counter (from 1 to 19).
- if rev >= 3:
+ if rev >= 4:
for i in range(1, 20):
new_key = ""
for key_char in key:
Mutant 2342 ✔️
--- PyPDF2/_security.py
+++ PyPDF2/_security.py
@@ -117,7 +117,7 @@
# an XOR operation between that byte and the single-byte value of the
# iteration counter (from 1 to 19).
if rev >= 3:
- for i in range(1, 20):
+ for i in range(2, 20):
new_key = ""
for key_char in key:
new_key += chr(ord_(key_char) ^ i)
Mutant 2343
--- PyPDF2/_security.py
+++ PyPDF2/_security.py
@@ -117,7 +117,7 @@
# an XOR operation between that byte and the single-byte value of the
# iteration counter (from 1 to 19).
if rev >= 3:
- for i in range(1, 20):
+ for i in range(1, 21):
new_key = ""
for key_char in key:
new_key += chr(ord_(key_char) ^ i)
Mutant 2383 ✔️
--- PyPDF2/_security.py
+++ PyPDF2/_security.py
@@ -226,7 +226,7 @@
def RC4_encrypt(key: Union[str, bytes], plaintext: bytes) -> bytes: # TODO
- S = list(range(256))
+ S = list(range(257))
j = 0
for i in range(256):
j = (j + S[i] + ord_(key[i % len(key)])) % 256
Mutation testing applies common mistakes to the codebase and checks if the tests capture them. Those changes are called "mutations" (or mutants). Killing a mutant means to add a test which would fail if the mutation is applied.
_writer.py::encrypt (V2)
Mutant #2325
Mutant 2326
Mutant 2337
Mutant 2340 ✔️
Mutant 2341 ✔️
Mutant 2342 ✔️
Mutant 2343
Mutant 2383 ✔️