Coverage for confy_addons/encryption/rsa.py: 100%
115 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-02-20 18:19 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2026-02-20 18:19 +0000
1"""RSA encryption handler with automatic key pair generation.
3This module provides classes for RSA encryption and decryption using
4the `cryptography` library. It includes functionality for generating RSA
5key pairs, serializing public keys, and performing encryption and decryption
6operations using RSA with OAEP padding.
7"""
9import base64
10import binascii
11import logging
13from cryptography.hazmat.primitives import hashes, serialization
14from cryptography.hazmat.primitives.asymmetric import padding, rsa
15from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey
16from cryptography.hazmat.primitives.asymmetric.types import PublicKeyTypes
18from confy_addons.core.constants import DEFAULT_RSA_KEY_SIZE, LOGGER_LEVEL, RSA_PUBLIC_EXPONENT
19from confy_addons.core.exceptions import (
20 DecryptionError,
21 EncryptionError,
22)
23from confy_addons.core.mixins import EncryptionMixin
25logging.basicConfig(level=LOGGER_LEVEL)
26logger = logging.getLogger(__name__)
29class RSAEncryption(EncryptionMixin):
30 """RSA encryption handler with automatic key pair generation.
32 This class generates and manages an RSA key pair for asymmetric encryption
33 and decryption operations. It provides convenient access to both private
34 and public keys, as well as serialized forms of the public key.
36 Attributes:
37 key_size: The size of the RSA key in bits.
38 public_key: The RSA public key.
39 private_key: The RSA private key.
40 serialized_public_key: The public key in PEM format.
41 base64_public_key: The public key in base64-encoded PEM format.
43 """
45 def __init__(self, key_size: int = DEFAULT_RSA_KEY_SIZE):
46 """Initialize RSAEncryption with a new key pair.
48 Generates a new RSA key pair with the specified key size and public
49 exponent. The private key is stored internally for decryption operations.
51 Args:
52 key_size: The size of the RSA key in bits. Defaults to 4096.
54 Raises:
55 TypeError: If key_size is not an integer.
56 ValueError: If key_size is less than the recommended minimum for security.
57 RuntimeError: If key pair generation fails.
59 """
60 if not isinstance(key_size, int):
61 logger.error(f'Invalid key_size type: {type(key_size)}')
62 raise TypeError('key_size must be an integer')
63 if key_size < DEFAULT_RSA_KEY_SIZE:
64 logger.error(f'Invalid key_size value: {key_size}')
65 raise ValueError(f'key_size must be at least {DEFAULT_RSA_KEY_SIZE} bits for security')
67 self._key_size = key_size
68 self._public_exponent = RSA_PUBLIC_EXPONENT
70 try:
71 self._private_key = rsa.generate_private_key(
72 public_exponent=self._public_exponent, key_size=self._key_size
73 )
74 except Exception as e:
75 logger.error('Error occurred while generating RSA key pair: %s', e)
76 raise RuntimeError('Failed to generate RSA key pair') from e
78 logger.debug(f'RSA key pair generated with key size: {self._key_size} bits')
80 def __repr__(self):
81 """Return a string representation of the RSAEncryption instance.
83 Returns:
84 str: A detailed string representation including module, class name,
85 parameters, and memory address.
87 """
88 class_name = type(self).__name__
89 return f"""{self.__module__}.{class_name}(key_size={self._key_size!r})
90 object at {hex(id(self))}"""
92 def decrypt(self, encrypted_data: bytes) -> bytes:
93 """Decrypts data using the private key.
95 Decrypts the provided encrypted data using RSA with OAEP padding
96 and SHA256 hashing algorithm.
98 Args:
99 encrypted_data: The encrypted bytes to decrypt.
101 Returns:
102 bytes: The decrypted data.
104 Raises:
105 TypeError: If encrypted_data is not bytes.
106 ValueError: If encrypted_data is empty.
107 DecryptionError: If decryption fails.
109 """
110 if not isinstance(encrypted_data, bytes):
111 logger.error(f'Invalid encrypted_data type: {type(encrypted_data)}')
112 raise TypeError('encrypted_data must be bytes')
113 if len(encrypted_data) == 0:
114 logger.error('Invalid encrypted_data value: %s', encrypted_data)
115 raise ValueError('encrypted_data is empty')
117 try:
118 decrypted_data = self._private_key.decrypt(
119 encrypted_data,
120 padding.OAEP(
121 mgf=padding.MGF1(algorithm=hashes.SHA256()),
122 algorithm=hashes.SHA256(),
123 label=None,
124 ),
125 )
126 return decrypted_data
127 except Exception as e:
128 logger.error('Decryption failed: %s', e)
129 raise DecryptionError('RSA decryption failed') from e
131 def sign(self, data: bytes) -> bytes:
132 """Signs the data using the private key with PSS padding.
134 Args:
135 data: The bytes to be signed.
137 Returns:
138 bytes: The signature.
140 Raises:
141 TypeError: If the data is not a byte.
142 ValueError: If the data is empty.
143 RuntimeError: If the signature fails.
145 """
146 if not isinstance(data, bytes):
147 logger.error(f'Invalid data type for signing: {type(data)}')
148 raise TypeError('data must be bytes')
149 if len(data) == 0:
150 logger.error('Invalid data value for signing: empty')
151 raise ValueError('data is empty')
153 try:
154 return self._private_key.sign(
155 data,
156 padding.PSS(
157 mgf=padding.MGF1(algorithm=hashes.SHA256()),
158 salt_length=padding.PSS.MAX_LENGTH,
159 ),
160 hashes.SHA256(),
161 )
162 except Exception as e:
163 logger.exception('Signing failed: %s', e)
164 raise RuntimeError('RSA signing failed') from e
166 @property
167 def key_size(self) -> int:
168 """Returns the size of the RSA key in bits.
170 Returns:
171 int: The key size in bits.
173 """
174 return self._key_size
176 @property
177 def public_key(self) -> RSAPublicKey:
178 """Returns the RSA public key.
180 Returns:
181 RSAPublicKey: The public key object that can be shared with others
182 for encryption operations.
184 """
185 return self._private_key.public_key()
187 @property
188 def private_key(self) -> RSAPrivateKey:
189 """Returns the RSA private key.
191 Returns:
192 RSAPrivateKey: The private key object that should be kept secure
193 and used for decryption operations.
195 """
196 return self._private_key
198 @property
199 def serialized_public_key(self) -> bytes:
200 """Returns the public key in PEM format.
202 Serializes the public key to PEM format with SubjectPublicKeyInfo
203 structure, suitable for transmission or storage.
205 Returns:
206 bytes: The public key in PEM-encoded bytes.
208 """
209 return self._private_key.public_key().public_bytes(
210 encoding=serialization.Encoding.PEM,
211 format=serialization.PublicFormat.SubjectPublicKeyInfo,
212 )
214 @property
215 def base64_public_key(self) -> str:
216 """Returns the public key in base64-encoded PEM format.
218 Provides the public key as a base64-encoded string, which is convenient
219 for transmission over text-based protocols.
221 Returns:
222 str: The base64-encoded PEM representation of the public key.
224 """
225 serialized_public_key = self._private_key.public_key().public_bytes(
226 encoding=serialization.Encoding.PEM,
227 format=serialization.PublicFormat.SubjectPublicKeyInfo,
228 )
230 return base64.b64encode(serialized_public_key).decode()
233class RSAPublicEncryption(EncryptionMixin):
234 """RSA encryption handler using a public key only.
236 This class provides encryption operations using an existing RSA public key.
237 It is intended for encrypting data that will be decrypted by the holder
238 of the corresponding private key.
240 Attributes:
241 key: The RSA public key used for encryption.
243 """
245 def __init__(self, key: RSAPublicKey):
246 """Initialize RSAPublicEncryption with a public key.
248 Args:
249 key: An RSA public key object to use for encryption operations.
251 Raises:
252 TypeError: If the provided key is not an instance of RSAPublicKey.
254 """
255 if not isinstance(key, RSAPublicKey):
256 raise TypeError('key must be an instance of RSAPublicKey')
258 self._key = key
260 def __repr__(self):
261 """Return a string representation of the RSAPublicEncryption instance.
263 Returns:
264 str: A detailed string representation including module, class name,
265 key, and memory address.
267 """
268 class_name = type(self).__name__
269 return f'{self.__module__}.{class_name}(key={self._key!r}) object at {hex(id(self))}'
271 def encrypt(self, data: bytes) -> bytes:
272 """Encrypts data using the public key.
274 Encrypts the provided data using RSA with OAEP padding and SHA256
275 hashing algorithm for secure encryption.
277 Args:
278 data: The bytes to encrypt.
280 Returns:
281 bytes: The encrypted data.
283 Raises:
284 TypeError: If the provided data is not bytes.
285 EncryptionError: If encryption fails.
287 """
288 if not isinstance(data, bytes):
289 logger.error(f'Invalid data type: {type(data)}')
290 raise TypeError('data must be bytes')
291 try:
292 return self._key.encrypt(
293 data,
294 padding.OAEP(
295 mgf=padding.MGF1(algorithm=hashes.SHA256()),
296 algorithm=hashes.SHA256(),
297 label=None,
298 ),
299 )
300 except Exception as e:
301 logger.exception('Encryption failed: %s', e)
302 raise EncryptionError('RSA encryption failed') from e
304 def verify(self, data: bytes, signature: bytes):
305 """Verifica a assinatura dos dados usando a chave pública.
307 Args:
308 data: Os dados originais que foram assinados.
309 signature: A assinatura recebida.
311 Raises:
312 TypeError: Se os dados ou assinatura não forem bytes.
313 SignatureVerificationError: Se a assinatura for inválida.
315 """
316 if not isinstance(data, bytes) or not isinstance(signature, bytes):
317 raise TypeError('data and signature must be bytes')
319 try:
320 self._key.verify(
321 signature,
322 data,
323 padding.PSS(
324 mgf=padding.MGF1(algorithm=hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH
325 ),
326 hashes.SHA256(),
327 )
328 # Se chegar aqui, a verificação foi bem-sucedida
329 logger.debug('Signature verification successful')
331 except Exception as e:
332 logger.error(f'An unexpected error occurred during verification: {e}')
333 raise Exception(f'Verification error: {e}') from e
335 @property
336 def key(self) -> RSAPublicKey:
337 """Returns the RSA public key.
339 Returns:
340 RSAPublicKey: The public key object used for encryption.
342 """
343 return self._key
346def deserialize_public_key(b64_key: str) -> PublicKeyTypes:
347 """Deserializes a base64-encoded PEM string back to an RSA public key object.
349 Decodes a base64-encoded PEM string and loads it as an RSA public key object
350 that can be used for encryption operations.
352 Args:
353 b64_key (str): The base64-encoded PEM representation of the public key.
355 Returns:
356 PublicKeyTypes: The deserialized RSA public key object.
358 Raises:
359 TypeError: If b64_key is not a string.
360 ValueError: If the base64 decoding fails or the key cannot be loaded.
362 """
363 if not isinstance(b64_key, str):
364 logger.error(f'Invalid b64_key type: {type(b64_key)}')
365 raise TypeError('b64_key must be a base64-encoded string')
366 try:
367 key_bytes = base64.b64decode(b64_key.encode('ascii'), validate=True)
368 except (binascii.Error, ValueError) as e:
369 logger.error(f'Error occurred while decoding base64 key: {e}')
370 raise ValueError('Invalid base64 public key') from e
372 try:
373 return serialization.load_pem_public_key(key_bytes)
374 except Exception as e:
375 logger.error(f'Error occurred while loading public key from PEM: {e}')
376 raise ValueError('Failed to load public key from PEM') from e