1# Copyright (c) 2023, 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.
27
28import secrets
29
30from Crypto import __version__
31from Crypto.Cipher import AES, ARC4
32from Crypto.Util.Padding import pad, unpad
33
34from pypdf._crypt_providers._base import CryptBase
35from pypdf._utils import logger_warning
36from pypdf.errors import PdfStreamError
37
38crypt_provider = ("pycryptodome", __version__)
39
40
41class CryptRC4(CryptBase):
42 def __init__(self, key: bytes) -> None:
43 self.key = key
44
45 def encrypt(self, data: bytes) -> bytes:
46 return ARC4.ARC4Cipher(self.key).encrypt(data)
47
48 def decrypt(self, data: bytes, *, strict: bool = True) -> bytes:
49 return ARC4.ARC4Cipher(self.key).decrypt(data)
50
51
52class CryptAES(CryptBase):
53 def __init__(self, key: bytes) -> None:
54 self.key = key
55
56 def encrypt(self, data: bytes) -> bytes:
57 iv = secrets.token_bytes(16)
58 padded_data = pad(data, 16)
59 aes = AES.new(self.key, AES.MODE_CBC, iv)
60 return iv + aes.encrypt(padded_data)
61
62 def decrypt(self, data: bytes, *, strict: bool = True) -> bytes:
63 iv = data[:16]
64 data = data[16:]
65 # for empty encrypted data
66 if not data:
67 return data
68
69 if not strict and len(data) % 16 != 0:
70 logger_warning("Adding missing padding.", src=__name__)
71 data = pad(data, 16)
72
73 aes = AES.new(self.key, AES.MODE_CBC, iv)
74 try:
75 padded_data = aes.decrypt(data)
76 except ValueError as exception:
77 # Only raised in strict mode. Non-strict mode fixes padding.
78 raise PdfStreamError(exception)
79
80 try:
81 return unpad(padded_data, 16)
82 except ValueError as exception:
83 if strict:
84 raise PdfStreamError(exception)
85 logger_warning(f"Ignoring padding error: {exception}", src=__name__)
86 return padded_data[: -padded_data[-1]]
87
88
89def rc4_encrypt(key: bytes, data: bytes) -> bytes:
90 return ARC4.ARC4Cipher(key).encrypt(data)
91
92
93def rc4_decrypt(key: bytes, data: bytes) -> bytes:
94 return ARC4.ARC4Cipher(key).decrypt(data)
95
96
97def aes_ecb_encrypt(key: bytes, data: bytes) -> bytes:
98 return AES.new(key, AES.MODE_ECB).encrypt(data)
99
100
101def aes_ecb_decrypt(key: bytes, data: bytes) -> bytes:
102 return AES.new(key, AES.MODE_ECB).decrypt(data)
103
104
105def aes_cbc_encrypt(key: bytes, iv: bytes, data: bytes) -> bytes:
106 return AES.new(key, AES.MODE_CBC, iv).encrypt(data)
107
108
109def aes_cbc_decrypt(key: bytes, iv: bytes, data: bytes) -> bytes:
110 return AES.new(key, AES.MODE_CBC, iv).decrypt(data)