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

437 statements  

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 

34 

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) 

47 

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) 

60 

61 

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 

72 

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 

95 

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 

111 

112 

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) 

117 

118 

119def _padding(data: bytes) -> bytes: 

120 return (data + _PADDING)[:32] 

121 

122 

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) 

137 

138 

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. 

152 

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. 

192 

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. 

205 

206 Returns: 

207 The u_hash digest of length key_size 

208 

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] 

223 

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. 

228 

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. 

256 

257 Args: 

258 owner_password: 

259 rev: The encryption revision (see PDF standard) 

260 key_size: The size of the key in bytes 

261 

262 Returns: 

263 The RC4 key 

264 

265 """ 

266 a = _padding(owner_password) 

267 o_hash_digest = hashlib.md5(a).digest() 

268 

269 if rev >= 3: 

270 for _ in range(50): 

271 o_hash_digest = hashlib.md5(o_hash_digest).digest() 

272 

273 return o_hash_digest[: key_size // 8] 

274 

275 @staticmethod 

276 def compute_O_value(rc4_key: bytes, user_password: bytes, rev: int) -> bytes: 

277 """ 

278 See :func:`compute_O_value_key`. 

279 

280 Args: 

281 rc4_key: 

282 user_password: 

283 rev: The encryption revision (see PDF standard) 

284 

285 Returns: 

286 The RC4 encrypted 

287 

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 

296 

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. 

301 

302 (Security handlers of revision 2) 

303 

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. 

311 

312 Args: 

313 key: 

314 rev: The encryption revision (see PDF standard) 

315 id1_entry: 

316 

317 Returns: 

318 The value 

319 

320 """ 

321 if rev <= 2: 

322 return rc4_encrypt(key, _PADDING) 

323 

324 """ 

325 Algorithm 5: Computing the encryption dictionary’s U (user password) value. 

326 

327 (Security handlers of revision 3 or greater) 

328 

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) 

356 

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. 

370 

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. 

386 

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. 

400 

401 Returns: 

402 The key 

403 

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 

415 

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. 

429 

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. 

449 

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. 

463 

464 Returns: 

465 bytes 

466 

467 """ 

468 rc4_key = AlgV4.compute_O_value_key(owner_password, rev, key_size) 

469 

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 ) 

487 

488 

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. 

496 

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. 

501 

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. 

535 

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. 

547 

548 Returns: 

549 The key 

550 

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) 

561 

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`. 

568 

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: 

577 

578 Returns: 

579 bytes 

580 

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) 

588 

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] 

609 

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`. 

616 

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: 

627 

628 Returns: 

629 A boolean 

630 

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] 

636 

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 } 

658 

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. 

664 

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. 

676 

677 Args: 

678 R: 

679 password: 

680 key: 

681 

682 Returns: 

683 A tuple (u-value, ue value) 

684 

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 

690 

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 

695 

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. 

703 

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. 

718 

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. 

726 

727 Returns: 

728 A tuple (O value, OE value) 

729 

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 

741 

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. 

747 

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. 

761 

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. 

770 

771 Returns: 

772 The perms value 

773 

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) 

779 

780 

781class PasswordType(IntEnum): 

782 NOT_DECRYPTED = 0 

783 USER_PASSWORD = 1 

784 OWNER_PASSWORD = 2 

785 

786 

787def _saslprep(password: str) -> str: 

788 """ 

789 Apply the SASLprep profile (RFC 4013) of stringprep (RFC 3454). 

790 

791 This normalizes Unicode passwords for PDF 2.0 (AES-256, revision 5/6) 

792 encryption as required by the PDF specification. 

793 

794 Args: 

795 password: The Unicode password string to normalize. 

796 

797 Returns: 

798 The SASLprep-normalized string. 

799 

800 Raises: 

801 ValueError: If the password contains prohibited characters 

802 or fails the bidirectional character check. 

803 

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) 

817 

818 # Normalization (RFC 4013 §2.2) — Unicode NFKC 

819 password = unicodedata.normalize("NFKC", password) 

820 

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 ) 

827 

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 

836 

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 ) 

850 

851 return password 

852 

853 

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) 

861 

862 

863class EncryptionValues: 

864 O: bytes # noqa: E741 

865 U: bytes 

866 OE: bytes 

867 UE: bytes 

868 Perms: bytes 

869 

870 

871class Encryption: 

872 """ 

873 Collects and manages parameters for PDF document encryption and decryption. 

874 

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. 

893 

894 """ 

895 

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() 

922 

923 self._password_type = PasswordType.NOT_DECRYPTED 

924 self._key: Optional[bytes] = None 

925 self._are_permissions_valid: bool = True 

926 

927 def is_decrypted(self) -> bool: 

928 return self._password_type != PasswordType.NOT_DECRYPTED 

929 

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 

934 

935 cf = self._make_crypt_filter(idnum, generation) 

936 return cf.encrypt_object(obj) 

937 

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 

942 

943 cf = self._make_crypt_filter(idnum, generation) 

944 return cf.decrypt_object(obj, strict=strict) 

945 

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 ) 

958 

959 def _make_crypt_filter(self, idnum: int, generation: int) -> CryptFilter: 

960 """ 

961 Algorithm 1: Encryption of data using the RC4 or AES algorithms. 

962 

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. 

989 

990 Algorithm 3.1a: Encryption of data using the AES-256 algorithm. 

991 

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. 

994 

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] 

1005 

1006 assert self._key 

1007 key = self._key 

1008 

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)] 

1015 

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"" 

1022 

1023 # for AES-256 

1024 aes256_key = key 

1025 

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) 

1029 

1030 return CryptFilter(stm_crypt, str_crypt, ef_crypt) 

1031 

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() 

1042 

1043 return CryptRC4(rc4_key) 

1044 

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 

1073 

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 

1083 

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 

1111 

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 

1125 

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 

1131 

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 

1147 

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"] 

1160 

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 

1168 

1169 dict_obj[NameObject("/O")] = ByteStringObject(self.values.O) 

1170 dict_obj[NameObject("/U")] = ByteStringObject(self.values.U) 

1171 

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") 

1185 

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 

1191 

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) 

1195 

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) 

1206 

1207 self._key = key 

1208 self.values.O = o_value 

1209 self.values.U = u_value 

1210 

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") 

1219 

1220 stm_filter = "/V2" 

1221 str_filter = "/V2" 

1222 ef_filter = "/V2" 

1223 

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"] 

1229 

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) 

1233 

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] 

1240 

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!") 

1248 

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 ) 

1279 

1280 @staticmethod 

1281 def make( 

1282 alg: EncryptAlgorithm, permissions: int, first_id_entry: bytes 

1283 ) -> "Encryption": 

1284 alg_ver, alg_rev, key_bits = alg 

1285 

1286 stm_filter, str_filter, ef_filter = "/V2", "/V2", "/V2" 

1287 

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" 

1292 

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 )