Skip to content

Commit 27eeba4

Browse files
committed
BUG: Fix AESV2 decryption when /Length missing in encrypt dict
For V=4 encryption with AESV2, read key length from the Crypt Filter dict instead of the main encrypt dict. Some PDFs omit /Length in the main dict (defaulting to 40 bits), but AESV2 requires 128 bits as specified in the CF /Length field. Fixes #3628
1 parent aab6585 commit 27eeba4

3 files changed

Lines changed: 23 additions & 0 deletions

File tree

pypdf/_encryption.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,6 +1126,12 @@ def read(encryption_entry: DictionaryObject, first_id_entry: bytes) -> "Encrypti
11261126
alg_rev = cast(int, encryption_entry["/R"])
11271127
perm_flags = cast(int, encryption_entry["/P"])
11281128
key_bits = encryption_entry.get("/Length", 40)
1129+
if alg_ver == 4:
1130+
stm_filter_name = encryption_entry.get("/StmF", "/Identity")
1131+
if stm_filter_name != "/Identity":
1132+
cf_dict = filters[stm_filter_name] # type: ignore
1133+
if cf_dict.get("/CFM") == "/AESV2":
1134+
key_bits = cf_dict.get("/Length", 16) * 8
11291135
encrypt_metadata = encryption_entry.get("/EncryptMetadata")
11301136
encrypt_metadata = (
11311137
encrypt_metadata.value if encrypt_metadata is not None else True
15 KB
Binary file not shown.

tests/test_encryption.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,23 @@ def test_pdf_with_both_passwords(name, user_passwd, owner_passwd):
130130
assert len(ipdf.pages) == 1
131131

132132

133+
@pytest.mark.skipif(not HAS_AES, reason="No AES implementation")
134+
def test_aesv2_without_length_in_encrypt_dict():
135+
"""
136+
AESV2-encrypted PDF without /Length in encrypt dict decrypts correctly.
137+
138+
Some PDFs omit /Length in the main encrypt dict (defaulting to 40 bits),
139+
but AESV2 requires 128 bits. The key length should be read from the
140+
crypt filter dict instead.
141+
"""
142+
inputfile = RESOURCE_ROOT / "encryption" / "r4-aes-v2-no-key-length.pdf"
143+
reader = PdfReader(inputfile)
144+
assert reader.is_encrypted
145+
result = reader.decrypt("")
146+
assert result in (PasswordType.USER_PASSWORD, PasswordType.OWNER_PASSWORD)
147+
assert len(reader.pages) == 1
148+
149+
133150
@pytest.mark.parametrize(
134151
("pdffile", "password"),
135152
[

0 commit comments

Comments
 (0)