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.
--- 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 :heavy_check_mark:
--- 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 :heavy_check_mark:
--- 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 :heavy_check_mark:
--- 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 :heavy_check_mark:
--- 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
Mutant 2383 is ok for sure, that is the S-Box size. We could introduce a constant for it which would kill the mutant, but that makes the code harder to read. Hence I prefer leaving it like that.
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 :heavy_check_mark:
Mutant 2341 :heavy_check_mark:
Mutant 2342 :heavy_check_mark:
Mutant 2343
Mutant 2383 :heavy_check_mark: