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 cryptography import __version__
31from cryptography.hazmat.primitives.ciphers.algorithms import AES
32from cryptography.hazmat.primitives.ciphers.base import Cipher
33from cryptography.hazmat.primitives.ciphers.modes import CBC, ECB
34from cryptography.hazmat.primitives.padding import PKCS7
35
36from pypdf._crypt_providers._base import CryptBase
37from pypdf._utils import logger_warning
38from pypdf.errors import PdfStreamError
39
40try:
41 # 43.0.0: https://cryptography.io/en/latest/changelog/#v43-0-0
42 from cryptography.hazmat.decrepit.ciphers.algorithms import ARC4
43except ImportError:
44 from cryptography.hazmat.primitives.ciphers.algorithms import ARC4
45
46crypt_provider = ("cryptography", __version__)
47
48
49class CryptRC4(CryptBase):
50 def __init__(self, key: bytes) -> None:
51 self.cipher = Cipher(ARC4(key), mode=None)
52
53 def encrypt(self, data: bytes) -> bytes:
54 encryptor = self.cipher.encryptor()
55 return encryptor.update(data) + encryptor.finalize()
56
57 def decrypt(self, data: bytes, *, strict: bool = True) -> bytes:
58 decryptor = self.cipher.decryptor()
59 return decryptor.update(data) + decryptor.finalize()
60
61
62class CryptAES(CryptBase):
63 def __init__(self, key: bytes) -> None:
64 self.alg = AES(key)
65
66 def encrypt(self, data: bytes) -> bytes:
67 iv = secrets.token_bytes(16)
68 padder = PKCS7(128).padder()
69 padded_data = padder.update(data) + padder.finalize()
70
71 cipher = Cipher(self.alg, CBC(iv))
72 encryptor = cipher.encryptor()
73 return iv + encryptor.update(padded_data) + encryptor.finalize()
74
75 def decrypt(self, data: bytes, *, strict: bool = True) -> bytes:
76 iv = data[:16]
77 data = data[16:]
78 # for empty encrypted data
79 if not data:
80 return data
81
82 if not strict and len(data) % 16 != 0:
83 logger_warning("Adding missing padding.", source=__name__)
84 padder = PKCS7(128).padder()
85 data = padder.update(data) + padder.finalize()
86
87 cipher = Cipher(self.alg, CBC(iv))
88 decryptor = cipher.decryptor()
89 try:
90 padded_data = decryptor.update(data) + decryptor.finalize()
91 except ValueError as exception:
92 # Only raised in strict mode. Non-strict mode fixes padding.
93 raise PdfStreamError(exception)
94
95 unpadder = PKCS7(128).unpadder()
96 try:
97 return unpadder.update(padded_data) + unpadder.finalize()
98 except ValueError as exception:
99 if strict:
100 raise PdfStreamError(exception)
101 logger_warning("Ignoring padding error: %(exception)s", source=__name__, exception=exception)
102 return padded_data[: -padded_data[-1]]
103
104
105def rc4_encrypt(key: bytes, data: bytes) -> bytes:
106 encryptor = Cipher(ARC4(key), mode=None).encryptor()
107 return encryptor.update(data) + encryptor.finalize()
108
109
110def rc4_decrypt(key: bytes, data: bytes) -> bytes:
111 decryptor = Cipher(ARC4(key), mode=None).decryptor()
112 return decryptor.update(data) + decryptor.finalize()
113
114
115def aes_ecb_encrypt(key: bytes, data: bytes) -> bytes:
116 encryptor = Cipher(AES(key), mode=ECB()).encryptor()
117 return encryptor.update(data) + encryptor.finalize()
118
119
120def aes_ecb_decrypt(key: bytes, data: bytes) -> bytes:
121 decryptor = Cipher(AES(key), mode=ECB()).decryptor()
122 return decryptor.update(data) + decryptor.finalize()
123
124
125def aes_cbc_encrypt(key: bytes, iv: bytes, data: bytes) -> bytes:
126 encryptor = Cipher(AES(key), mode=CBC(iv)).encryptor()
127 return encryptor.update(data) + encryptor.finalize()
128
129
130def aes_cbc_decrypt(key: bytes, iv: bytes, data: bytes) -> bytes:
131 decryptor = Cipher(AES(key), mode=CBC(iv)).decryptor()
132 return decryptor.update(data) + decryptor.finalize()