Skip to content

Commit

Permalink
MAINT: Refactor Encryption class
Browse files Browse the repository at this point in the history
* Rename parameters of Encrytpion class
* Add EncryptMetadata attribute
* Rename attribute key_size to Length
* Add P attribute explicitly instead of using "entry"
  • Loading branch information
MartinThoma committed Apr 30, 2023
1 parent a2d1e5a commit 728543c
Showing 1 changed file with 39 additions and 30 deletions.
69 changes: 39 additions & 30 deletions pypdf/_encryption.py
Original file line number Diff line number Diff line change
Expand Up @@ -877,19 +877,24 @@ class PasswordType(IntEnum):
class Encryption:
def __init__(
self,
algV: int,
algR: int,
V: int,
R: int,
Length: int,
P: int,
entry: DictionaryObject,
EncryptMetadata: bool,
first_id_entry: bytes,
StmF: str,
StrF: str,
EFF: str,
) -> None:
# See TABLE 3.18 Entries common to all encryption dictionaries
self.algV = algV
self.algR = algR
self.V = V
self.R = R
self.Length = Length # key_size
self.P = (P + 0x100000000) % 0x100000000 # maybe P < 0
self.entry = entry
self.key_size = entry.get("/Length", 40)
self.EncryptMetadata = EncryptMetadata
self.id1_entry = first_id_entry
self.StmF = StmF
self.StrF = StrF
Expand Down Expand Up @@ -954,7 +959,7 @@ def decrypt_object(self, obj: PdfObject, idnum: int, generation: int) -> PdfObje

assert self._key
key = self._key
n = 5 if self.algV == 1 else self.key_size // 8
n = 5 if self.V == 1 else self.Length // 8
key_data = key[:n] + pack1 + pack2
key_hash = hashlib.md5(key_data)
rc4_key = key_hash.digest()[: min(n + 5, 16)]
Expand Down Expand Up @@ -994,44 +999,38 @@ def verify(self, password: Union[bytes, str]) -> PasswordType:
else:
pwd = password

key, rc = self.verify_v4(pwd) if self.algV <= 4 else self.verify_v5(pwd)
key, rc = self.verify_v4(pwd) if self.V <= 4 else self.verify_v5(pwd)
if rc != PasswordType.NOT_DECRYPTED:
self._password_type = rc
self._key = key
return rc

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
# make type(metadata_encrypted) == bool
em = self.entry.get("/EncryptMetadata")
metadata_encrypted = em.value if em else True
o_entry = cast(ByteStringObject, self.entry["/O"].get_object()).original_bytes
u_entry = cast(ByteStringObject, self.entry["/U"].get_object()).original_bytes

# verify owner password first
key = AlgV4.verify_owner_password(
password,
R,
self.key_size,
self.R,
self.Length,
o_entry,
u_entry,
P,
self.P,
self.id1_entry,
metadata_encrypted,
self.EncryptMetadata,
)
if key:
return key, PasswordType.OWNER_PASSWORD
key = AlgV4.verify_user_password(
password,
R,
self.key_size,
self.R,
self.Length,
o_entry,
u_entry,
P,
self.P,
self.id1_entry,
metadata_encrypted,
self.EncryptMetadata,
)
if key:
return key, PasswordType.USER_PASSWORD
Expand All @@ -1045,22 +1044,17 @@ def verify_v5(self, password: bytes) -> Tuple[bytes, PasswordType]:
ue_entry = cast(ByteStringObject, self.entry["/UE"].get_object()).original_bytes

# verify owner password first
key = AlgV5.verify_owner_password(
self.algR, password, o_entry, oe_entry, u_entry
)
key = AlgV5.verify_owner_password(self.R, password, o_entry, oe_entry, u_entry)
rc = PasswordType.OWNER_PASSWORD
if not key:
key = AlgV5.verify_user_password(self.algR, password, u_entry, ue_entry)
key = AlgV5.verify_user_password(self.R, password, u_entry, ue_entry)
rc = PasswordType.USER_PASSWORD
if not key:
return b"", PasswordType.NOT_DECRYPTED

# verify Perms
perms = cast(ByteStringObject, self.entry["/Perms"].get_object()).original_bytes
P = cast(int, self.entry["/P"])
P = (P + 0x100000000) % 0x100000000 # maybe < 0
metadata_encrypted = self.entry.get("/EncryptMetadata", True)
if not AlgV5.verify_perms(key, perms, P, metadata_encrypted):
if not AlgV5.verify_perms(key, perms, self.P, self.EncryptMetadata):
logger_warning("ignore '/Perms' verify failed", __name__)
return key, rc

Expand Down Expand Up @@ -1104,4 +1098,19 @@ def read(encryption_entry: DictionaryObject, first_id_entry: bytes) -> "Encrypti
raise NotImplementedError(f"EFF Method {EFF} NOT supported!")

R = cast(int, encryption_entry["/R"])
return Encryption(V, R, encryption_entry, first_id_entry, StmF, StrF, EFF)
Length = encryption_entry.get("/Length", 40)
P = cast(int, encryption_entry["/P"])
EncryptMetadata = encryption_entry.get("/EncryptMetadata")
EncryptMetadata = EncryptMetadata.value if EncryptMetadata is not None else True
return Encryption(
V=V,
R=R,
Length=Length,
P=P,
EncryptMetadata=EncryptMetadata,
entry=encryption_entry,
first_id_entry=first_id_entry,
StmF=StmF,
StrF=StrF,
EFF=EFF,
)

0 comments on commit 728543c

Please sign in to comment.