Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pypdf/_encryption.py: 71%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Copyright (c) 2022, exiledkingcc
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are
6# met:
7#
8# * Redistributions of source code must retain the above copyright notice,
9# this list of conditions and the following disclaimer.
10# * Redistributions in binary form must reproduce the above copyright notice,
11# this list of conditions and the following disclaimer in the documentation
12# and/or other materials provided with the distribution.
13# * The name of the author may not be used to endorse or promote products
14# derived from this software without specific prior written permission.
15#
16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26# POSSIBILITY OF SUCH DAMAGE.
27import hashlib
28import secrets
29import stringprep
30import struct
31import unicodedata
32from enum import Enum, IntEnum
33from typing import Any, Optional, Union, cast
35from pypdf._crypt_providers import (
36 CryptAES,
37 CryptBase,
38 CryptIdentity,
39 CryptRC4,
40 aes_cbc_decrypt,
41 aes_cbc_encrypt,
42 aes_ecb_decrypt,
43 aes_ecb_encrypt,
44 rc4_decrypt,
45 rc4_encrypt,
46)
48from ._utils import logger_warning
49from .generic import (
50 ArrayObject,
51 ByteStringObject,
52 DictionaryObject,
53 NameObject,
54 NumberObject,
55 PdfObject,
56 StreamObject,
57 TextStringObject,
58 create_string_object,
59)
62class CryptFilter:
63 def __init__(
64 self,
65 stm_crypt: CryptBase,
66 str_crypt: CryptBase,
67 ef_crypt: CryptBase,
68 ) -> None:
69 self.stm_crypt = stm_crypt
70 self.str_crypt = str_crypt
71 self.ef_crypt = ef_crypt
73 def encrypt_object(self, obj: PdfObject) -> PdfObject:
74 if isinstance(obj, ByteStringObject):
75 data = self.str_crypt.encrypt(obj.original_bytes)
76 obj = ByteStringObject(data)
77 elif isinstance(obj, TextStringObject):
78 data = self.str_crypt.encrypt(obj.get_encoded_bytes())
79 obj = ByteStringObject(data)
80 elif isinstance(obj, StreamObject):
81 obj2 = StreamObject()
82 obj2.update(obj)
83 obj2.set_data(self.stm_crypt.encrypt(obj._data))
84 for key, value in obj.items(): # Don't forget the Stream dict.
85 obj2[key] = self.encrypt_object(value)
86 obj = obj2
87 elif isinstance(obj, DictionaryObject):
88 obj2 = DictionaryObject() # type: ignore[assignment]
89 for key, value in obj.items():
90 obj2[key] = self.encrypt_object(value)
91 obj = obj2
92 elif isinstance(obj, ArrayObject):
93 obj = ArrayObject(self.encrypt_object(x) for x in obj)
94 return obj
96 def decrypt_object(self, obj: PdfObject, *, strict: bool = True) -> PdfObject:
97 if isinstance(obj, (ByteStringObject, TextStringObject)):
98 data = self.str_crypt.decrypt(obj.original_bytes, strict=strict)
99 obj = create_string_object(data)
100 elif isinstance(obj, StreamObject):
101 obj._data = self.stm_crypt.decrypt(obj._data, strict=strict)
102 for key, value in obj.items(): # Don't forget the Stream dict.
103 obj[key] = self.decrypt_object(value, strict=strict)
104 elif isinstance(obj, DictionaryObject):
105 for key, value in obj.items():
106 obj[key] = self.decrypt_object(value, strict=strict)
107 elif isinstance(obj, ArrayObject):
108 for i in range(len(obj)):
109 obj[i] = self.decrypt_object(obj[i], strict=strict)
110 return obj
113_PADDING = (
114 b"\x28\xbf\x4e\x5e\x4e\x75\x8a\x41\x64\x00\x4e\x56\xff\xfa\x01\x08"
115 b"\x2e\x2e\x00\xb6\xd0\x68\x3e\x80\x2f\x0c\xa9\xfe\x64\x53\x69\x7a"
116)
119def _padding(data: bytes) -> bytes:
120 return (data + _PADDING)[:32]
123# Module-level constant for SASLprep prohibited character checks (RFC 4013 §2.3)
124_SASLPREP_PROHIBITED_CHECKS = (
125 (stringprep.in_table_c12, "non-ASCII space character"),
126 (stringprep.in_table_c21, "ASCII control character"),
127 (stringprep.in_table_c22, "non-ASCII control character"),
128 (stringprep.in_table_c3, "private use character"),
129 (stringprep.in_table_c4, "non-character code point"),
130 (stringprep.in_table_c5, "surrogate code point"),
131 (stringprep.in_table_c6, "inappropriate for plain text"),
132 (stringprep.in_table_c7, "inappropriate for canonical representation"),
133 (stringprep.in_table_c8, "change display properties/deprecated"),
134 (stringprep.in_table_c9, "tagging character"),
135 (stringprep.in_table_a1, "unassigned code point"),
136)
139class AlgV4:
140 @staticmethod
141 def compute_key(
142 password: bytes,
143 rev: int,
144 key_size: int,
145 o_entry: bytes,
146 P: int,
147 id1_entry: bytes,
148 metadata_encrypted: bool,
149 ) -> bytes:
150 """
151 Algorithm 2: Computing an encryption key.
153 a) Pad or truncate the password string to exactly 32 bytes. If the
154 password string is more than 32 bytes long,
155 use only its first 32 bytes; if it is less than 32 bytes long, pad it
156 by appending the required number of
157 additional bytes from the beginning of the following padding string:
158 < 28 BF 4E 5E 4E 75 8A 41 64 00 4E 56 FF FA 01 08
159 2E 2E 00 B6 D0 68 3E 80 2F 0C A9 FE 64 53 69 7A >
160 That is, if the password string is n bytes long, append
161 the first 32 - n bytes of the padding string to the end
162 of the password string. If the password string is empty
163 (zero-length), meaning there is no user password,
164 substitute the entire padding string in its place.
165 b) Initialize the MD5 hash function and pass the result of step (a)
166 as input to this function.
167 c) Pass the value of the encryption dictionary’s O entry to the
168 MD5 hash function. ("Algorithm 3: Computing
169 the encryption dictionary’s O (owner password) value" shows how the
170 O value is computed.)
171 d) Convert the integer value of the P entry to a 32-bit unsigned binary
172 number and pass these bytes to the
173 MD5 hash function, low-order byte first.
174 e) Pass the first element of the file’s file identifier array (the value
175 of the ID entry in the document’s trailer
176 dictionary; see Table 15) to the MD5 hash function.
177 f) (Security handlers of revision 4 or greater) If document metadata is
178 not being encrypted, pass 4 bytes with
179 the value 0xFFFFFFFF to the MD5 hash function.
180 g) Finish the hash.
181 h) (Security handlers of revision 3 or greater) Do the following
182 50 times: Take the output from the previous
183 MD5 hash and pass the first n bytes of the output as input into a new
184 MD5 hash, where n is the number of
185 bytes of the encryption key as defined by the value of the encryption
186 dictionary’s Length entry.
187 i) Set the encryption key to the first n bytes of the output from the
188 final MD5 hash, where n shall always be 5
189 for security handlers of revision 2 but, for security handlers of
190 revision 3 or greater, shall depend on the
191 value of the encryption dictionary’s Length entry.
193 Args:
194 password: The encryption secret as a bytes-string
195 rev: The encryption revision (see PDF standard)
196 key_size: The size of the key in bytes
197 o_entry: The owner entry
198 P: A set of flags specifying which operations shall be permitted
199 when the document is opened with user access. If bit 2 is set to 1,
200 all other bits are ignored and all operations are permitted.
201 If bit 2 is set to 0, permission for operations are based on the
202 values of the remaining flags defined in Table 24.
203 id1_entry:
204 metadata_encrypted: A boolean indicating if the metadata is encrypted.
206 Returns:
207 The u_hash digest of length key_size
209 """
210 a = _padding(password)
211 u_hash = hashlib.md5(a)
212 u_hash.update(o_entry)
213 u_hash.update(struct.pack("<I", P))
214 u_hash.update(id1_entry)
215 if rev >= 4 and not metadata_encrypted:
216 u_hash.update(b"\xff\xff\xff\xff")
217 u_hash_digest = u_hash.digest()
218 length = key_size // 8
219 if rev >= 3:
220 for _ in range(50):
221 u_hash_digest = hashlib.md5(u_hash_digest[:length]).digest()
222 return u_hash_digest[:length]
224 @staticmethod
225 def compute_O_value_key(owner_password: bytes, rev: int, key_size: int) -> bytes:
226 """
227 Algorithm 3: Computing the encryption dictionary’s O (owner password) value.
229 a) Pad or truncate the owner password string as described in step (a)
230 of "Algorithm 2: Computing an encryption key".
231 If there is no owner password, use the user password instead.
232 b) Initialize the MD5 hash function and pass the result of step (a) as
233 input to this function.
234 c) (Security handlers of revision 3 or greater) Do the following 50 times:
235 Take the output from the previous
236 MD5 hash and pass it as input into a new MD5 hash.
237 d) Create an RC4 encryption key using the first n bytes of the output
238 from the final MD5 hash, where n shall
239 always be 5 for security handlers of revision 2 but, for security
240 handlers of revision 3 or greater, shall
241 depend on the value of the encryption dictionary’s Length entry.
242 e) Pad or truncate the user password string as described in step (a) of
243 "Algorithm 2: Computing an encryption key".
244 f) Encrypt the result of step (e), using an RC4 encryption function with
245 the encryption key obtained in step (d).
246 g) (Security handlers of revision 3 or greater) Do the following 19 times:
247 Take the output from the previous
248 invocation of the RC4 function and pass it as input to a new
249 invocation of the function; use an encryption
250 key generated by taking each byte of the encryption key obtained in
251 step (d) and performing an XOR
252 (exclusive or) operation between that byte and the single-byte value
253 of the iteration counter (from 1 to 19).
254 h) Store the output from the final invocation of the RC4 function as
255 the value of the O entry in the encryption dictionary.
257 Args:
258 owner_password:
259 rev: The encryption revision (see PDF standard)
260 key_size: The size of the key in bytes
262 Returns:
263 The RC4 key
265 """
266 a = _padding(owner_password)
267 o_hash_digest = hashlib.md5(a).digest()
269 if rev >= 3:
270 for _ in range(50):
271 o_hash_digest = hashlib.md5(o_hash_digest).digest()
273 return o_hash_digest[: key_size // 8]
275 @staticmethod
276 def compute_O_value(rc4_key: bytes, user_password: bytes, rev: int) -> bytes:
277 """
278 See :func:`compute_O_value_key`.
280 Args:
281 rc4_key:
282 user_password:
283 rev: The encryption revision (see PDF standard)
285 Returns:
286 The RC4 encrypted
288 """
289 a = _padding(user_password)
290 rc4_enc = rc4_encrypt(rc4_key, a)
291 if rev >= 3:
292 for i in range(1, 20):
293 key = bytes(x ^ i for x in rc4_key)
294 rc4_enc = rc4_encrypt(key, rc4_enc)
295 return rc4_enc
297 @staticmethod
298 def compute_U_value(key: bytes, rev: int, id1_entry: bytes) -> bytes:
299 """
300 Algorithm 4: Computing the encryption dictionary’s U (user password) value.
302 (Security handlers of revision 2)
304 a) Create an encryption key based on the user password string, as
305 described in "Algorithm 2: Computing an encryption key".
306 b) Encrypt the 32-byte padding string shown in step (a) of
307 "Algorithm 2: Computing an encryption key", using an RC4 encryption
308 function with the encryption key from the preceding step.
309 c) Store the result of step (b) as the value of the U entry in the
310 encryption dictionary.
312 Args:
313 key:
314 rev: The encryption revision (see PDF standard)
315 id1_entry:
317 Returns:
318 The value
320 """
321 if rev <= 2:
322 return rc4_encrypt(key, _PADDING)
324 """
325 Algorithm 5: Computing the encryption dictionary’s U (user password) value.
327 (Security handlers of revision 3 or greater)
329 a) Create an encryption key based on the user password string, as
330 described in "Algorithm 2: Computing an encryption key".
331 b) Initialize the MD5 hash function and pass the 32-byte padding string
332 shown in step (a) of "Algorithm 2:
333 Computing an encryption key" as input to this function.
334 c) Pass the first element of the file’s file identifier array (the value
335 of the ID entry in the document’s trailer
336 dictionary; see Table 15) to the hash function and finish the hash.
337 d) Encrypt the 16-byte result of the hash, using an RC4 encryption
338 function with the encryption key from step (a).
339 e) Do the following 19 times: Take the output from the previous
340 invocation of the RC4 function and pass it as input to a new
341 invocation of the function; use an encryption key generated by
342 taking each byte of the original encryption key obtained in
343 step (a) and performing an XOR (exclusive or) operation between that
344 byte and the single-byte value of the iteration counter (from 1 to 19).
345 f) Append 16 bytes of arbitrary padding to the output from the final
346 invocation of the RC4 function and store the 32-byte result as the
347 value of the U entry in the encryption dictionary.
348 """
349 u_hash = hashlib.md5(_PADDING)
350 u_hash.update(id1_entry)
351 rc4_enc = rc4_encrypt(key, u_hash.digest())
352 for i in range(1, 20):
353 rc4_key = bytes(x ^ i for x in key)
354 rc4_enc = rc4_encrypt(rc4_key, rc4_enc)
355 return _padding(rc4_enc)
357 @staticmethod
358 def verify_user_password(
359 user_password: bytes,
360 rev: int,
361 key_size: int,
362 o_entry: bytes,
363 u_entry: bytes,
364 P: int,
365 id1_entry: bytes,
366 metadata_encrypted: bool,
367 ) -> bytes:
368 """
369 Algorithm 6: Authenticating the user password.
371 a) Perform all but the last step of "Algorithm 4: Computing the
372 encryption dictionary’s U (user password) value (Security handlers of
373 revision 2)" or "Algorithm 5: Computing the encryption dictionary’s U
374 (user password) value (Security handlers of revision 3 or greater)"
375 using the supplied password string.
376 b) If the result of step (a) is equal to the value of the encryption
377 dictionary’s U entry (comparing on the first 16 bytes in the case of
378 security handlers of revision 3 or greater), the password supplied is
379 the correct user password. The key obtained in step (a) (that is, in
380 the first step of "Algorithm 4: Computing the encryption
381 dictionary’s U (user password) value
382 (Security handlers of revision 2)" or
383 "Algorithm 5: Computing the encryption dictionary’s U (user password)
384 value (Security handlers of revision 3 or greater)") shall be used
385 to decrypt the document.
387 Args:
388 user_password: The user password as a bytes stream
389 rev: The encryption revision (see PDF standard)
390 key_size: The size of the key in bytes
391 o_entry: The owner entry
392 u_entry: The user entry
393 P: A set of flags specifying which operations shall be permitted
394 when the document is opened with user access. If bit 2 is set to 1,
395 all other bits are ignored and all operations are permitted.
396 If bit 2 is set to 0, permission for operations are based on the
397 values of the remaining flags defined in Table 24.
398 id1_entry:
399 metadata_encrypted: A boolean indicating if the metadata is encrypted.
401 Returns:
402 The key
404 """
405 key = AlgV4.compute_key(
406 user_password, rev, key_size, o_entry, P, id1_entry, metadata_encrypted
407 )
408 u_value = AlgV4.compute_U_value(key, rev, id1_entry)
409 if rev >= 3:
410 u_value = u_value[:16]
411 u_entry = u_entry[:16]
412 if u_value != u_entry:
413 key = b""
414 return key
416 @staticmethod
417 def verify_owner_password(
418 owner_password: bytes,
419 rev: int,
420 key_size: int,
421 o_entry: bytes,
422 u_entry: bytes,
423 P: int,
424 id1_entry: bytes,
425 metadata_encrypted: bool,
426 ) -> bytes:
427 """
428 Algorithm 7: Authenticating the owner password.
430 a) Compute an encryption key from the supplied password string, as
431 described in steps (a) to (d) of
432 "Algorithm 3: Computing the encryption dictionary’s O (owner password)
433 value".
434 b) (Security handlers of revision 2 only) Decrypt the value of the
435 encryption dictionary’s O entry, using an RC4
436 encryption function with the encryption key computed in step (a).
437 (Security handlers of revision 3 or greater) Do the following 20 times:
438 Decrypt the value of the encryption dictionary’s O entry (first iteration)
439 or the output from the previous iteration (all subsequent iterations),
440 using an RC4 encryption function with a different encryption key at
441 each iteration. The key shall be generated by taking the original key
442 (obtained in step (a)) and performing an XOR (exclusive or) operation
443 between each byte of the key and the single-byte value of the
444 iteration counter (from 19 to 0).
445 c) The result of step (b) purports to be the user password.
446 Authenticate this user password using
447 "Algorithm 6: Authenticating the user password".
448 If it is correct, the password supplied is the correct owner password.
450 Args:
451 owner_password:
452 rev: The encryption revision (see PDF standard)
453 key_size: The size of the key in bytes
454 o_entry: The owner entry
455 u_entry: The user entry
456 P: A set of flags specifying which operations shall be permitted
457 when the document is opened with user access. If bit 2 is set to 1,
458 all other bits are ignored and all operations are permitted.
459 If bit 2 is set to 0, permission for operations are based on the
460 values of the remaining flags defined in Table 24.
461 id1_entry:
462 metadata_encrypted: A boolean indicating if the metadata is encrypted.
464 Returns:
465 bytes
467 """
468 rc4_key = AlgV4.compute_O_value_key(owner_password, rev, key_size)
470 if rev <= 2:
471 user_password = rc4_decrypt(rc4_key, o_entry)
472 else:
473 user_password = o_entry
474 for i in range(19, -1, -1):
475 key = bytes(x ^ i for x in rc4_key)
476 user_password = rc4_decrypt(key, user_password)
477 return AlgV4.verify_user_password(
478 user_password,
479 rev,
480 key_size,
481 o_entry,
482 u_entry,
483 P,
484 id1_entry,
485 metadata_encrypted,
486 )
489class AlgV5:
490 @staticmethod
491 def verify_owner_password(
492 R: int, password: bytes, o_value: bytes, oe_value: bytes, u_value: bytes
493 ) -> bytes:
494 """
495 Algorithm 3.2a Computing an encryption key.
497 To understand the algorithm below, it is necessary to treat the O and U
498 strings in the Encrypt dictionary as made up of three sections.
499 The first 32 bytes are a hash value (explained below). The next 8 bytes
500 are called the Validation Salt. The final 8 bytes are called the Key Salt.
502 1. The password string is generated from Unicode input by processing the
503 input string with the SASLprep (IETF RFC 4013) profile of
504 stringprep (IETF RFC 3454), and then converting to a UTF-8
505 representation.
506 2. Truncate the UTF-8 representation to 127 bytes if it is longer than
507 127 bytes.
508 3. Test the password against the owner key by computing the SHA-256 hash
509 of the UTF-8 password concatenated with the 8 bytes of owner
510 Validation Salt, concatenated with the 48-byte U string. If the
511 32-byte result matches the first 32 bytes of the O string, this is
512 the owner password.
513 Compute an intermediate owner key by computing the SHA-256 hash of
514 the UTF-8 password concatenated with the 8 bytes of owner Key Salt,
515 concatenated with the 48-byte U string. The 32-byte result is the
516 key used to decrypt the 32-byte OE string using AES-256 in CBC mode
517 with no padding and an initialization vector of zero.
518 The 32-byte result is the file encryption key.
519 4. Test the password against the user key by computing the SHA-256 hash
520 of the UTF-8 password concatenated with the 8 bytes of user
521 Validation Salt. If the 32 byte result matches the first 32 bytes of
522 the U string, this is the user password.
523 Compute an intermediate user key by computing the SHA-256 hash of the
524 UTF-8 password concatenated with the 8 bytes of user Key Salt.
525 The 32-byte result is the key used to decrypt the 32-byte
526 UE string using AES-256 in CBC mode with no padding and an
527 initialization vector of zero. The 32-byte result is the file
528 encryption key.
529 5. Decrypt the 16-byte Perms string using AES-256 in ECB mode with an
530 initialization vector of zero and the file encryption key as the key.
531 Verify that bytes 9-11 of the result are the characters ‘a’, ‘d’, ‘b’.
532 Bytes 0-3 of the decrypted Perms entry, treated as a little-endian
533 integer, are the user permissions.
534 They should match the value in the P key.
536 Args:
537 R: A number specifying which revision of the standard security
538 handler shall be used to interpret this dictionary
539 password: The owner password
540 o_value: A 32-byte string, based on both the owner and user passwords,
541 that shall be used in computing the encryption key and in
542 determining whether a valid owner password was entered
543 oe_value:
544 u_value: A 32-byte string, based on the user password, that shall be
545 used in determining whether to prompt the user for a password and,
546 if so, whether a valid user or owner password was entered.
548 Returns:
549 The key
551 """
552 password = password[:127]
553 if (
554 AlgV5.calculate_hash(R, password, o_value[32:40], u_value[:48])
555 != o_value[:32]
556 ):
557 return b""
558 iv = bytes(0 for _ in range(16))
559 tmp_key = AlgV5.calculate_hash(R, password, o_value[40:48], u_value[:48])
560 return aes_cbc_decrypt(tmp_key, iv, oe_value)
562 @staticmethod
563 def verify_user_password(
564 R: int, password: bytes, u_value: bytes, ue_value: bytes
565 ) -> bytes:
566 """
567 See :func:`verify_owner_password`.
569 Args:
570 R: A number specifying which revision of the standard security
571 handler shall be used to interpret this dictionary
572 password: The user password
573 u_value: A 32-byte string, based on the user password, that shall be
574 used in determining whether to prompt the user for a password
575 and, if so, whether a valid user or owner password was entered.
576 ue_value:
578 Returns:
579 bytes
581 """
582 password = password[:127]
583 if AlgV5.calculate_hash(R, password, u_value[32:40], b"") != u_value[:32]:
584 return b""
585 iv = bytes(0 for _ in range(16))
586 tmp_key = AlgV5.calculate_hash(R, password, u_value[40:48], b"")
587 return aes_cbc_decrypt(tmp_key, iv, ue_value)
589 @staticmethod
590 def calculate_hash(R: int, password: bytes, salt: bytes, udata: bytes) -> bytes:
591 # https://github.com/qpdf/qpdf/blob/main/libqpdf/QPDF_encryption.cc
592 k = hashlib.sha256(password + salt + udata).digest()
593 if R < 6:
594 return k
595 count = 0
596 while True:
597 count += 1
598 k1 = password + k + udata
599 e = aes_cbc_encrypt(k[:16], k[16:32], k1 * 64)
600 hash_fn = (
601 hashlib.sha256,
602 hashlib.sha384,
603 hashlib.sha512,
604 )[sum(e[:16]) % 3]
605 k = hash_fn(e).digest()
606 if count >= 64 and e[-1] <= count - 32:
607 break
608 return k[:32]
610 @staticmethod
611 def verify_perms(
612 key: bytes, perms: bytes, p: int, metadata_encrypted: bool
613 ) -> bool:
614 """
615 See :func:`verify_owner_password` and :func:`compute_perms_value`.
617 Args:
618 key: The owner password
619 perms:
620 p: A set of flags specifying which operations shall be permitted
621 when the document is opened with user access.
622 If bit 2 is set to 1, all other bits are ignored and all
623 operations are permitted.
624 If bit 2 is set to 0, permission for operations are based on
625 the values of the remaining flags defined in Table 24.
626 metadata_encrypted:
628 Returns:
629 A boolean
631 """
632 b8 = b"T" if metadata_encrypted else b"F"
633 p1 = struct.pack("<I", p) + b"\xff\xff\xff\xff" + b8 + b"adb"
634 p2 = aes_ecb_decrypt(key, perms)
635 return p1 == p2[:12]
637 @staticmethod
638 def generate_values(
639 R: int,
640 user_password: bytes,
641 owner_password: bytes,
642 key: bytes,
643 p: int,
644 metadata_encrypted: bool,
645 ) -> dict[Any, Any]:
646 user_password = user_password[:127]
647 owner_password = owner_password[:127]
648 u_value, ue_value = AlgV5.compute_U_value(R, user_password, key)
649 o_value, oe_value = AlgV5.compute_O_value(R, owner_password, key, u_value)
650 perms = AlgV5.compute_Perms_value(key, p, metadata_encrypted)
651 return {
652 "/U": u_value,
653 "/UE": ue_value,
654 "/O": o_value,
655 "/OE": oe_value,
656 "/Perms": perms,
657 }
659 @staticmethod
660 def compute_U_value(R: int, password: bytes, key: bytes) -> tuple[bytes, bytes]:
661 """
662 Algorithm 3.8 Computing the encryption dictionary’s U (user password)
663 and UE (user encryption key) values.
665 1. Generate 16 random bytes of data using a strong random number generator.
666 The first 8 bytes are the User Validation Salt. The second 8 bytes
667 are the User Key Salt. Compute the 32-byte SHA-256 hash of the
668 password concatenated with the User Validation Salt. The 48-byte
669 string consisting of the 32-byte hash followed by the User
670 Validation Salt followed by the User Key Salt is stored as the U key.
671 2. Compute the 32-byte SHA-256 hash of the password concatenated with
672 the User Key Salt. Using this hash as the key, encrypt the file
673 encryption key using AES-256 in CBC mode with no padding and an
674 initialization vector of zero. The resulting 32-byte string is stored
675 as the UE key.
677 Args:
678 R:
679 password:
680 key:
682 Returns:
683 A tuple (u-value, ue value)
685 """
686 random_bytes = secrets.token_bytes(16)
687 val_salt = random_bytes[:8]
688 key_salt = random_bytes[8:]
689 u_value = AlgV5.calculate_hash(R, password, val_salt, b"") + val_salt + key_salt
691 tmp_key = AlgV5.calculate_hash(R, password, key_salt, b"")
692 iv = bytes(0 for _ in range(16))
693 ue_value = aes_cbc_encrypt(tmp_key, iv, key)
694 return u_value, ue_value
696 @staticmethod
697 def compute_O_value(
698 R: int, password: bytes, key: bytes, u_value: bytes
699 ) -> tuple[bytes, bytes]:
700 """
701 Algorithm 3.9 Computing the encryption dictionary’s O (owner password)
702 and OE (owner encryption key) values.
704 1. Generate 16 random bytes of data using a strong random number
705 generator. The first 8 bytes are the Owner Validation Salt. The
706 second 8 bytes are the Owner Key Salt. Compute the 32-byte SHA-256
707 hash of the password concatenated with the Owner Validation Salt and
708 then concatenated with the 48-byte U string as generated in
709 Algorithm 3.8. The 48-byte string consisting of the 32-byte hash
710 followed by the Owner Validation Salt followed by the Owner Key Salt
711 is stored as the O key.
712 2. Compute the 32-byte SHA-256 hash of the password concatenated with
713 the Owner Key Salt and then concatenated with the 48-byte U string as
714 generated in Algorithm 3.8. Using this hash as the key,
715 encrypt the file encryption key using AES-256 in CBC mode with
716 no padding and an initialization vector of zero.
717 The resulting 32-byte string is stored as the OE key.
719 Args:
720 R:
721 password:
722 key:
723 u_value: A 32-byte string, based on the user password, that shall be
724 used in determining whether to prompt the user for a password
725 and, if so, whether a valid user or owner password was entered.
727 Returns:
728 A tuple (O value, OE value)
730 """
731 random_bytes = secrets.token_bytes(16)
732 val_salt = random_bytes[:8]
733 key_salt = random_bytes[8:]
734 o_value = (
735 AlgV5.calculate_hash(R, password, val_salt, u_value) + val_salt + key_salt
736 )
737 tmp_key = AlgV5.calculate_hash(R, password, key_salt, u_value[:48])
738 iv = bytes(0 for _ in range(16))
739 oe_value = aes_cbc_encrypt(tmp_key, iv, key)
740 return o_value, oe_value
742 @staticmethod
743 def compute_Perms_value(key: bytes, p: int, metadata_encrypted: bool) -> bytes:
744 """
745 Algorithm 3.10 Computing the encryption dictionary’s Perms
746 (permissions) value.
748 1. Extend the permissions (contents of the P integer) to 64 bits by
749 setting the upper 32 bits to all 1’s.
750 (This allows for future extension without changing the format.)
751 2. Record the 8 bytes of permission in the bytes 0-7 of the block,
752 low order byte first.
753 3. Set byte 8 to the ASCII value ' T ' or ' F ' according to the
754 EncryptMetadata Boolean.
755 4. Set bytes 9-11 to the ASCII characters ' a ', ' d ', ' b '.
756 5. Set bytes 12-15 to 4 bytes of random data, which will be ignored.
757 6. Encrypt the 16-byte block using AES-256 in ECB mode with an
758 initialization vector of zero, using the file encryption key as the
759 key. The result (16 bytes) is stored as the Perms string, and checked
760 for validity when the file is opened.
762 Args:
763 key:
764 p: A set of flags specifying which operations shall be permitted
765 when the document is opened with user access. If bit 2 is set to 1,
766 all other bits are ignored and all operations are permitted.
767 If bit 2 is set to 0, permission for operations are based on the
768 values of the remaining flags defined in Table 24.
769 metadata_encrypted: A boolean indicating if the metadata is encrypted.
771 Returns:
772 The perms value
774 """
775 b8 = b"T" if metadata_encrypted else b"F"
776 rr = secrets.token_bytes(4)
777 data = struct.pack("<I", p) + b"\xff\xff\xff\xff" + b8 + b"adb" + rr
778 return aes_ecb_encrypt(key, data)
781class PasswordType(IntEnum):
782 NOT_DECRYPTED = 0
783 USER_PASSWORD = 1
784 OWNER_PASSWORD = 2
787def _saslprep(password: str) -> str:
788 """
789 Apply the SASLprep profile (RFC 4013) of stringprep (RFC 3454).
791 This normalizes Unicode passwords for PDF 2.0 (AES-256, revision 5/6)
792 encryption as required by the PDF specification.
794 Args:
795 password: The Unicode password string to normalize.
797 Returns:
798 The SASLprep-normalized string.
800 Raises:
801 ValueError: If the password contains prohibited characters
802 or fails the bidirectional character check.
804 """
805 # Mapping (RFC 4013 §2.1)
806 # - Map characters in table B.1 to nothing
807 # - Map characters in table C.1.2 (non-ASCII spaces) to U+0020 (SPACE)
808 mapped: list[str] = []
809 for character in password:
810 if stringprep.in_table_b1(character):
811 continue # map to nothing
812 if stringprep.in_table_c12(character):
813 mapped.append(" ") # map to SPACE
814 else:
815 mapped.append(character)
816 password = "".join(mapped)
818 # Normalization (RFC 4013 §2.2) — Unicode NFKC
819 password = unicodedata.normalize("NFKC", password)
821 for character in password:
822 for check, description in _SASLPREP_PROHIBITED_CHECKS:
823 if check(character):
824 raise ValueError(
825 f"SASLprep: {description} U+{ord(character):04X}"
826 )
828 # Bidirectional check (RFC 4013 §2.4, RFC 3454 §6)
829 has_r_and_al = False
830 has_l = False
831 for character in password:
832 if stringprep.in_table_d1(character):
833 has_r_and_al = True
834 if stringprep.in_table_d2(character):
835 has_l = True
837 if has_r_and_al:
838 if has_l:
839 raise ValueError(
840 "SASLprep: string with RandALCat characters must not "
841 "contain LCat characters"
842 )
843 if not stringprep.in_table_d1(password[0]) or not stringprep.in_table_d1(
844 password[-1]
845 ):
846 raise ValueError(
847 "SASLprep: string with RandALCat characters must start "
848 "and end with RandALCat characters"
849 )
851 return password
854class EncryptAlgorithm(tuple, Enum): # type: ignore # noqa: SLOT001
855 # V, R, Length
856 RC4_40 = (1, 2, 40)
857 RC4_128 = (2, 3, 128)
858 AES_128 = (4, 4, 128)
859 AES_256_R5 = (5, 5, 256)
860 AES_256 = (5, 6, 256)
863class EncryptionValues:
864 O: bytes # noqa: E741
865 U: bytes
866 OE: bytes
867 UE: bytes
868 Perms: bytes
871class Encryption:
872 """
873 Collects and manages parameters for PDF document encryption and decryption.
875 Args:
876 V: A code specifying the algorithm to be used in encrypting and
877 decrypting the document.
878 R: The revision of the standard security handler.
879 Length: The length of the encryption key in bits.
880 P: A set of flags specifying which operations shall be permitted
881 when the document is opened with user access
882 entry: The encryption dictionary object.
883 EncryptMetadata: Whether to encrypt metadata in the document.
884 first_id_entry: The first 16 bytes of the file's original ID.
885 StmF: The name of the crypt filter that shall be used by default
886 when decrypting streams.
887 StrF: The name of the crypt filter that shall be used when decrypting
888 all strings in the document.
889 EFF: The name of the crypt filter that shall be used when
890 encrypting embedded file streams that do not have their own
891 crypt filter specifier.
892 values: Additional encryption parameters.
894 """
896 def __init__(
897 self,
898 V: int,
899 R: int,
900 Length: int,
901 P: int,
902 entry: DictionaryObject,
903 EncryptMetadata: bool,
904 first_id_entry: bytes,
905 StmF: str,
906 StrF: str,
907 EFF: str,
908 values: Optional[EncryptionValues],
909 ) -> None:
910 # §7.6.2, entries common to all encryption dictionaries
911 # use same name as keys of encryption dictionaries entries
912 self.V = V
913 self.R = R
914 self.Length = Length # key_size
915 self.P = (P + 0x100000000) % 0x100000000 # maybe P < 0
916 self.EncryptMetadata = EncryptMetadata
917 self.id1_entry = first_id_entry
918 self.StmF = StmF
919 self.StrF = StrF
920 self.EFF = EFF
921 self.values: EncryptionValues = values or EncryptionValues()
923 self._password_type = PasswordType.NOT_DECRYPTED
924 self._key: Optional[bytes] = None
925 self._are_permissions_valid: bool = True
927 def is_decrypted(self) -> bool:
928 return self._password_type != PasswordType.NOT_DECRYPTED
930 def encrypt_object(self, obj: PdfObject, idnum: int, generation: int) -> PdfObject:
931 # skip calculate key
932 if not self._is_encryption_object(obj):
933 return obj
935 cf = self._make_crypt_filter(idnum, generation)
936 return cf.encrypt_object(obj)
938 def decrypt_object(self, obj: PdfObject, idnum: int, generation: int, *, strict: bool = True) -> PdfObject:
939 # skip calculate key
940 if not self._is_encryption_object(obj):
941 return obj
943 cf = self._make_crypt_filter(idnum, generation)
944 return cf.decrypt_object(obj, strict=strict)
946 @staticmethod
947 def _is_encryption_object(obj: PdfObject) -> bool:
948 return isinstance(
949 obj,
950 (
951 ByteStringObject,
952 TextStringObject,
953 StreamObject,
954 ArrayObject,
955 DictionaryObject,
956 ),
957 )
959 def _make_crypt_filter(self, idnum: int, generation: int) -> CryptFilter:
960 """
961 Algorithm 1: Encryption of data using the RC4 or AES algorithms.
963 a) Obtain the object number and generation number from the object
964 identifier of the string or stream to be encrypted
965 (see 7.3.10, "Indirect Objects"). If the string is a direct object,
966 use the identifier of the indirect object containing it.
967 b) For all strings and streams without crypt filter specifier; treating
968 the object number and generation number as binary integers, extend
969 the original n-byte encryption key to n + 5 bytes by appending the
970 low-order 3 bytes of the object number and the low-order 2 bytes of
971 the generation number in that order, low-order byte first.
972 (n is 5 unless the value of V in the encryption dictionary is greater
973 than 1, in which case n is the value of Length divided by 8.)
974 If using the AES algorithm, extend the encryption key an additional
975 4 bytes by adding the value “sAlT”, which corresponds to the
976 hexadecimal values 0x73, 0x41, 0x6C, 0x54. (This addition is done for
977 backward compatibility and is not intended to provide additional
978 security.)
979 c) Initialize the MD5 hash function and pass the result of step (b) as
980 input to this function.
981 d) Use the first (n + 5) bytes, up to a maximum of 16, of the output
982 from the MD5 hash as the key for the RC4 or AES symmetric key
983 algorithms, along with the string or stream data to be encrypted.
984 If using the AES algorithm, the Cipher Block Chaining (CBC) mode,
985 which requires an initialization vector, is used. The block size
986 parameter is set to 16 bytes, and the initialization vector is a
987 16-byte random number that is stored as the first 16 bytes of the
988 encrypted stream or string.
990 Algorithm 3.1a: Encryption of data using the AES-256 algorithm.
992 Note: Algorithm 3.1a does not use MD5 key derivation, so AES-256
993 encrypted files can be read on FIPS-enabled systems where MD5 is blocked.
995 1. Use the 32-byte file encryption key for the AES-256 symmetric key
996 algorithm, along with the string or stream data to be encrypted.
997 Use the AES algorithm in Cipher Block Chaining (CBC) mode, which
998 requires an initialization vector. The block size parameter is set to
999 16 bytes, and the initialization vector is a 16-byte random number
1000 that is stored as the first 16 bytes of the encrypted stream or string.
1001 The output is the encrypted data to be stored in the PDF file.
1002 """
1003 pack1 = struct.pack("<i", idnum)[:3]
1004 pack2 = struct.pack("<i", generation)[:2]
1006 assert self._key
1007 key = self._key
1009 # Algorithm 1 (V <= 4): MD5 key derivation. Algorithm 3.1a (V >= 5): key used directly.
1010 if self.V <= 4:
1011 n = 5 if self.V == 1 else self.Length // 8
1012 key_data = key[:n] + pack1 + pack2
1013 key_hash = hashlib.md5(key_data)
1014 rc4_key = key_hash.digest()[: min(n + 5, 16)]
1016 # for AES-128
1017 key_hash.update(b"sAlT")
1018 aes128_key = key_hash.digest()[: min(n + 5, 16)]
1019 else:
1020 rc4_key = b""
1021 aes128_key = b""
1023 # for AES-256
1024 aes256_key = key
1026 stm_crypt = self._get_crypt(self.StmF, rc4_key, aes128_key, aes256_key)
1027 str_crypt = self._get_crypt(self.StrF, rc4_key, aes128_key, aes256_key)
1028 ef_crypt = self._get_crypt(self.EFF, rc4_key, aes128_key, aes256_key)
1030 return CryptFilter(stm_crypt, str_crypt, ef_crypt)
1032 @staticmethod
1033 def _get_crypt(
1034 method: str, rc4_key: bytes, aes128_key: bytes, aes256_key: bytes
1035 ) -> CryptBase:
1036 if method == "/AESV2":
1037 return CryptAES(aes128_key)
1038 if method == "/AESV3":
1039 return CryptAES(aes256_key)
1040 if method == "/Identity":
1041 return CryptIdentity()
1043 return CryptRC4(rc4_key)
1045 def _encode_password(
1046 self, password: Union[bytes, str], *, strict: bool = False
1047 ) -> bytes:
1048 if isinstance(password, str):
1049 if self.V >= 5:
1050 # PDF 2.0 §7.6.4.3.3: AES-256 (R5/R6) requires SASLprep
1051 # normalization followed by UTF-8 encoding.
1052 try:
1053 password = _saslprep(password)
1054 except (ValueError, IndexError) as e:
1055 if strict:
1056 raise ValueError(
1057 f"Password SASLprep normalization failed: {e}"
1058 ) from e
1059 logger_warning(
1060 "SASLprep normalization failed, using plain UTF-8: %(err)s",
1061 source=__name__,
1062 err=str(e),
1063 )
1064 pwd = password.encode("utf-8")
1065 else:
1066 try:
1067 pwd = password.encode("latin-1")
1068 except Exception:
1069 pwd = password.encode("utf-8")
1070 else:
1071 pwd = password
1072 return pwd
1074 def verify(
1075 self, password: Union[bytes, str], *, strict: bool = False
1076 ) -> PasswordType:
1077 pwd = self._encode_password(password, strict=strict)
1078 key, rc = self.verify_v4(pwd) if self.V <= 4 else self.verify_v5(pwd)
1079 if rc != PasswordType.NOT_DECRYPTED:
1080 self._password_type = rc
1081 self._key = key
1082 return rc
1084 def verify_v4(self, password: bytes) -> tuple[bytes, PasswordType]:
1085 # verify owner password first
1086 key = AlgV4.verify_owner_password(
1087 password,
1088 self.R,
1089 self.Length,
1090 self.values.O,
1091 self.values.U,
1092 self.P,
1093 self.id1_entry,
1094 self.EncryptMetadata,
1095 )
1096 if key:
1097 return key, PasswordType.OWNER_PASSWORD
1098 key = AlgV4.verify_user_password(
1099 password,
1100 self.R,
1101 self.Length,
1102 self.values.O,
1103 self.values.U,
1104 self.P,
1105 self.id1_entry,
1106 self.EncryptMetadata,
1107 )
1108 if key:
1109 return key, PasswordType.USER_PASSWORD
1110 return b"", PasswordType.NOT_DECRYPTED
1112 def verify_v5(self, password: bytes) -> tuple[bytes, PasswordType]:
1113 # verify owner password first
1114 key = AlgV5.verify_owner_password(
1115 self.R, password, self.values.O, self.values.OE, self.values.U
1116 )
1117 rc = PasswordType.OWNER_PASSWORD
1118 if not key:
1119 key = AlgV5.verify_user_password(
1120 self.R, password, self.values.U, self.values.UE
1121 )
1122 rc = PasswordType.USER_PASSWORD
1123 if not key:
1124 return b"", PasswordType.NOT_DECRYPTED
1126 # verify Perms
1127 self._are_permissions_valid = AlgV5.verify_perms(key, self.values.Perms, self.P, self.EncryptMetadata)
1128 if not self._are_permissions_valid:
1129 logger_warning("ignore '/Perms' verify failed", source=__name__)
1130 return key, rc
1132 def write_entry(
1133 self,
1134 user_password: str,
1135 owner_password: Optional[str],
1136 *,
1137 strict: bool = False,
1138 ) -> DictionaryObject:
1139 user_pwd = self._encode_password(user_password, strict=strict)
1140 owner_pwd = (
1141 self._encode_password(owner_password, strict=strict)
1142 if owner_password
1143 else None
1144 )
1145 if owner_pwd is None:
1146 owner_pwd = user_pwd
1148 if self.V <= 4:
1149 self.compute_values_v4(user_pwd, owner_pwd)
1150 else:
1151 self._key = secrets.token_bytes(self.Length // 8)
1152 values = AlgV5.generate_values(
1153 self.R, user_pwd, owner_pwd, self._key, self.P, self.EncryptMetadata
1154 )
1155 self.values.O = values["/O"]
1156 self.values.U = values["/U"]
1157 self.values.OE = values["/OE"]
1158 self.values.UE = values["/UE"]
1159 self.values.Perms = values["/Perms"]
1161 dict_obj = DictionaryObject()
1162 dict_obj[NameObject("/V")] = NumberObject(self.V)
1163 dict_obj[NameObject("/R")] = NumberObject(self.R)
1164 dict_obj[NameObject("/Length")] = NumberObject(self.Length)
1165 dict_obj[NameObject("/P")] = NumberObject(self.P)
1166 dict_obj[NameObject("/Filter")] = NameObject("/Standard")
1167 # ignore /EncryptMetadata
1169 dict_obj[NameObject("/O")] = ByteStringObject(self.values.O)
1170 dict_obj[NameObject("/U")] = ByteStringObject(self.values.U)
1172 if self.V >= 4:
1173 # TODO: allow different method
1174 std_cf = DictionaryObject()
1175 std_cf[NameObject("/AuthEvent")] = NameObject("/DocOpen")
1176 std_cf[NameObject("/CFM")] = NameObject(self.StmF)
1177 std_cf[NameObject("/Length")] = NumberObject(self.Length // 8)
1178 cf = DictionaryObject()
1179 cf[NameObject("/StdCF")] = std_cf
1180 dict_obj[NameObject("/CF")] = cf
1181 dict_obj[NameObject("/StmF")] = NameObject("/StdCF")
1182 dict_obj[NameObject("/StrF")] = NameObject("/StdCF")
1183 # ignore EFF
1184 # dict_obj[NameObject("/EFF")] = NameObject("/StdCF")
1186 if self.V >= 5:
1187 dict_obj[NameObject("/OE")] = ByteStringObject(self.values.OE)
1188 dict_obj[NameObject("/UE")] = ByteStringObject(self.values.UE)
1189 dict_obj[NameObject("/Perms")] = ByteStringObject(self.values.Perms)
1190 return dict_obj
1192 def compute_values_v4(self, user_password: bytes, owner_password: bytes) -> None:
1193 rc4_key = AlgV4.compute_O_value_key(owner_password, self.R, self.Length)
1194 o_value = AlgV4.compute_O_value(rc4_key, user_password, self.R)
1196 key = AlgV4.compute_key(
1197 user_password,
1198 self.R,
1199 self.Length,
1200 o_value,
1201 self.P,
1202 self.id1_entry,
1203 self.EncryptMetadata,
1204 )
1205 u_value = AlgV4.compute_U_value(key, self.R, self.id1_entry)
1207 self._key = key
1208 self.values.O = o_value
1209 self.values.U = u_value
1211 @staticmethod
1212 def read(encryption_entry: DictionaryObject, first_id_entry: bytes) -> "Encryption":
1213 if encryption_entry.get("/Filter") != "/Standard":
1214 raise NotImplementedError(
1215 "only Standard PDF encryption handler is available"
1216 )
1217 if "/SubFilter" in encryption_entry:
1218 raise NotImplementedError("/SubFilter NOT supported")
1220 stm_filter = "/V2"
1221 str_filter = "/V2"
1222 ef_filter = "/V2"
1224 alg_ver = encryption_entry.get("/V", 0)
1225 if alg_ver not in (1, 2, 3, 4, 5):
1226 raise NotImplementedError(f"Encryption V={alg_ver} NOT supported")
1227 if alg_ver >= 4:
1228 filters = encryption_entry["/CF"]
1230 stm_filter = encryption_entry.get("/StmF", "/Identity")
1231 str_filter = encryption_entry.get("/StrF", "/Identity")
1232 ef_filter = encryption_entry.get("/EFF", stm_filter)
1234 if stm_filter != "/Identity":
1235 stm_filter = filters[stm_filter]["/CFM"] # type: ignore[index]
1236 if str_filter != "/Identity":
1237 str_filter = filters[str_filter]["/CFM"] # type: ignore[index]
1238 if ef_filter != "/Identity":
1239 ef_filter = filters[ef_filter]["/CFM"] # type: ignore[index]
1241 allowed_methods = ("/Identity", "/V2", "/AESV2", "/AESV3")
1242 if stm_filter not in allowed_methods:
1243 raise NotImplementedError(f"StmF Method {stm_filter} NOT supported!")
1244 if str_filter not in allowed_methods:
1245 raise NotImplementedError(f"StrF Method {str_filter} NOT supported!")
1246 if ef_filter not in allowed_methods:
1247 raise NotImplementedError(f"EFF Method {ef_filter} NOT supported!")
1249 alg_rev = cast(int, encryption_entry["/R"])
1250 perm_flags = cast(int, encryption_entry["/P"])
1251 key_bits = encryption_entry.get("/Length", 40)
1252 if alg_ver == 4 and stm_filter == "/AESV2":
1253 cf_dict = cast(DictionaryObject, filters[encryption_entry["/StmF"]]) # type: ignore[index]
1254 # CF /Length is in bytes (default 16 for AES-128), convert to bits
1255 key_bits = cast(int, cf_dict.get("/Length", 16)) * 8
1256 encrypt_metadata = encryption_entry.get("/EncryptMetadata")
1257 encrypt_metadata = (
1258 encrypt_metadata.value if encrypt_metadata is not None else True
1259 )
1260 values = EncryptionValues()
1261 values.O = cast(ByteStringObject, encryption_entry["/O"]).original_bytes
1262 values.U = cast(ByteStringObject, encryption_entry["/U"]).original_bytes
1263 values.OE = encryption_entry.get("/OE", ByteStringObject()).original_bytes
1264 values.UE = encryption_entry.get("/UE", ByteStringObject()).original_bytes
1265 values.Perms = encryption_entry.get("/Perms", ByteStringObject()).original_bytes
1266 return Encryption(
1267 V=alg_ver,
1268 R=alg_rev,
1269 Length=key_bits,
1270 P=perm_flags,
1271 EncryptMetadata=encrypt_metadata,
1272 first_id_entry=first_id_entry,
1273 values=values,
1274 StrF=str_filter,
1275 StmF=stm_filter,
1276 EFF=ef_filter,
1277 entry=encryption_entry, # Dummy entry for the moment; will get removed
1278 )
1280 @staticmethod
1281 def make(
1282 alg: EncryptAlgorithm, permissions: int, first_id_entry: bytes
1283 ) -> "Encryption":
1284 alg_ver, alg_rev, key_bits = alg
1286 stm_filter, str_filter, ef_filter = "/V2", "/V2", "/V2"
1288 if alg == EncryptAlgorithm.AES_128:
1289 stm_filter, str_filter, ef_filter = "/AESV2", "/AESV2", "/AESV2"
1290 elif alg in (EncryptAlgorithm.AES_256_R5, EncryptAlgorithm.AES_256):
1291 stm_filter, str_filter, ef_filter = "/AESV3", "/AESV3", "/AESV3"
1293 return Encryption(
1294 V=alg_ver,
1295 R=alg_rev,
1296 Length=key_bits,
1297 P=permissions,
1298 EncryptMetadata=True,
1299 first_id_entry=first_id_entry,
1300 values=None,
1301 StrF=str_filter,
1302 StmF=stm_filter,
1303 EFF=ef_filter,
1304 entry=DictionaryObject(), # Dummy entry for the moment; will get removed
1305 )