Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/rsa/pkcs1.py: 45%
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 2011 Sybren A. Stüvel <sybren@stuvel.eu>
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
15"""Functions for PKCS#1 version 1.5 encryption and signing
17This module implements certain functionality from PKCS#1 version 1.5. For a
18very clear example, read http://www.di-mgt.com.au/rsa_alg.html#pkcs1schemes
20At least 8 bytes of random padding is used when encrypting a message. This makes
21these methods much more secure than the ones in the ``rsa`` module.
23WARNING: this module leaks information when decryption fails. The exceptions
24that are raised contain the Python traceback information, which can be used to
25deduce where in the process the failure occurred. DO NOT PASS SUCH INFORMATION
26to your users.
27"""
29import hashlib
30import os
31import sys
32import typing
33from hmac import compare_digest
35from . import common, transform, core, key
37if typing.TYPE_CHECKING:
38 HashType = hashlib._Hash
39else:
40 HashType = typing.Any
42# ASN.1 codes that describe the hash algorithm used.
43HASH_ASN1 = {
44 "MD5": b"\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04\x10",
45 "SHA-1": b"\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14",
46 "SHA-224": b"\x30\x2d\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04\x05\x00\x04\x1c",
47 "SHA-256": b"\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20",
48 "SHA-384": b"\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30",
49 "SHA-512": b"\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40",
50 "SHA3-256": b"\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x08\x05\x00\x04\x20",
51 "SHA3-384": b"\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x09\x05\x00\x04\x30",
52 "SHA3-512": b"\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x0a\x05\x00\x04\x40",
53}
55HASH_METHODS: typing.Dict[str, typing.Callable[[], HashType]] = {
56 "MD5": hashlib.md5,
57 "SHA-1": hashlib.sha1,
58 "SHA-224": hashlib.sha224,
59 "SHA-256": hashlib.sha256,
60 "SHA-384": hashlib.sha384,
61 "SHA-512": hashlib.sha512,
62 "SHA3-256": hashlib.sha3_256,
63 "SHA3-384": hashlib.sha3_384,
64 "SHA3-512": hashlib.sha3_512,
65}
66"""Hash methods supported by this library."""
69class CryptoError(Exception):
70 """Base class for all exceptions in this module."""
73class DecryptionError(CryptoError):
74 """Raised when decryption fails."""
77class VerificationError(CryptoError):
78 """Raised when verification fails."""
81def _pad_for_encryption(message: bytes, target_length: int) -> bytes:
82 r"""Pads the message for encryption, returning the padded message.
84 :return: 00 02 RANDOM_DATA 00 MESSAGE
86 >>> block = _pad_for_encryption(b'hello', 16)
87 >>> len(block)
88 16
89 >>> block[0:2]
90 b'\x00\x02'
91 >>> block[-6:]
92 b'\x00hello'
94 """
96 max_msglength = target_length - 11
97 msglength = len(message)
99 if msglength > max_msglength:
100 raise OverflowError(
101 "%i bytes needed for message, but there is only"
102 " space for %i" % (msglength, max_msglength)
103 )
105 # Get random padding
106 padding = b""
107 padding_length = target_length - msglength - 3
109 # We remove 0-bytes, so we'll end up with less padding than we've asked for,
110 # so keep adding data until we're at the correct length.
111 while len(padding) < padding_length:
112 needed_bytes = padding_length - len(padding)
114 # Always read at least 8 bytes more than we need, and trim off the rest
115 # after removing the 0-bytes. This increases the chance of getting
116 # enough bytes, especially when needed_bytes is small
117 new_padding = os.urandom(needed_bytes + 5)
118 new_padding = new_padding.replace(b"\x00", b"")
119 padding = padding + new_padding[:needed_bytes]
121 assert len(padding) == padding_length
123 return b"".join([b"\x00\x02", padding, b"\x00", message])
126def _pad_for_signing(message: bytes, target_length: int) -> bytes:
127 r"""Pads the message for signing, returning the padded message.
129 The padding is always a repetition of FF bytes.
131 :return: 00 01 PADDING 00 MESSAGE
133 >>> block = _pad_for_signing(b'hello', 16)
134 >>> len(block)
135 16
136 >>> block[0:2]
137 b'\x00\x01'
138 >>> block[-6:]
139 b'\x00hello'
140 >>> block[2:-6]
141 b'\xff\xff\xff\xff\xff\xff\xff\xff'
143 """
145 max_msglength = target_length - 11
146 msglength = len(message)
148 if msglength > max_msglength:
149 raise OverflowError(
150 "%i bytes needed for message, but there is only"
151 " space for %i" % (msglength, max_msglength)
152 )
154 padding_length = target_length - msglength - 3
156 return b"".join([b"\x00\x01", padding_length * b"\xff", b"\x00", message])
159def encrypt(message: bytes, pub_key: key.PublicKey) -> bytes:
160 """Encrypts the given message using PKCS#1 v1.5
162 :param message: the message to encrypt. Must be a byte string no longer than
163 ``k-11`` bytes, where ``k`` is the number of bytes needed to encode
164 the ``n`` component of the public key.
165 :param pub_key: the :py:class:`rsa.PublicKey` to encrypt with.
166 :raise OverflowError: when the message is too large to fit in the padded
167 block.
169 >>> from rsa import key, common
170 >>> (pub_key, priv_key) = key.newkeys(256)
171 >>> message = b'hello'
172 >>> crypto = encrypt(message, pub_key)
174 The crypto text should be just as long as the public key 'n' component:
176 >>> len(crypto) == common.byte_size(pub_key.n)
177 True
179 """
181 keylength = common.byte_size(pub_key.n)
182 padded = _pad_for_encryption(message, keylength)
184 payload = transform.bytes2int(padded)
185 encrypted = core.encrypt_int(payload, pub_key.e, pub_key.n)
186 block = transform.int2bytes(encrypted, keylength)
188 return block
191def decrypt(crypto: bytes, priv_key: key.PrivateKey) -> bytes:
192 r"""Decrypts the given message using PKCS#1 v1.5
194 The decryption is considered 'failed' when the resulting cleartext doesn't
195 start with the bytes 00 02, or when the 00 byte between the padding and
196 the message cannot be found.
198 :param crypto: the crypto text as returned by :py:func:`rsa.encrypt`
199 :param priv_key: the :py:class:`rsa.PrivateKey` to decrypt with.
200 :raise DecryptionError: when the decryption fails. No details are given as
201 to why the code thinks the decryption fails, as this would leak
202 information about the private key.
205 >>> import rsa
206 >>> (pub_key, priv_key) = rsa.newkeys(256)
208 It works with strings:
210 >>> crypto = encrypt(b'hello', pub_key)
211 >>> decrypt(crypto, priv_key)
212 b'hello'
214 And with binary data:
216 >>> crypto = encrypt(b'\x00\x00\x00\x00\x01', pub_key)
217 >>> decrypt(crypto, priv_key)
218 b'\x00\x00\x00\x00\x01'
220 Altering the encrypted information will *likely* cause a
221 :py:class:`rsa.pkcs1.DecryptionError`. If you want to be *sure*, use
222 :py:func:`rsa.sign`.
225 .. warning::
227 Never display the stack trace of a
228 :py:class:`rsa.pkcs1.DecryptionError` exception. It shows where in the
229 code the exception occurred, and thus leaks information about the key.
230 It's only a tiny bit of information, but every bit makes cracking the
231 keys easier.
233 >>> crypto = encrypt(b'hello', pub_key)
234 >>> crypto = crypto[0:5] + b'X' + crypto[6:] # change a byte
235 >>> decrypt(crypto, priv_key)
236 Traceback (most recent call last):
237 ...
238 rsa.pkcs1.DecryptionError: Decryption failed
240 """
242 blocksize = common.byte_size(priv_key.n)
243 encrypted = transform.bytes2int(crypto)
244 decrypted = priv_key.blinded_decrypt(encrypted)
245 cleartext = transform.int2bytes(decrypted, blocksize)
247 # Detect leading zeroes in the crypto. These are not reflected in the
248 # encrypted value (as leading zeroes do not influence the value of an
249 # integer). This fixes CVE-2020-13757.
250 if len(crypto) > blocksize:
251 # This is operating on public information, so doesn't need to be constant-time.
252 raise DecryptionError("Decryption failed")
254 # If we can't find the cleartext marker, decryption failed.
255 cleartext_marker_bad = not compare_digest(cleartext[:2], b"\x00\x02")
257 # Find the 00 separator between the padding and the message
258 sep_idx = cleartext.find(b"\x00", 2)
260 # sep_idx indicates the position of the `\x00` separator that separates the
261 # padding from the actual message. The padding should be at least 8 bytes
262 # long (see https://tools.ietf.org/html/rfc8017#section-7.2.2 step 3), which
263 # means the separator should be at least at index 10 (because of the
264 # `\x00\x02` marker that precedes it).
265 sep_idx_bad = sep_idx < 10
267 anything_bad = cleartext_marker_bad | sep_idx_bad
268 if anything_bad:
269 raise DecryptionError("Decryption failed")
271 return cleartext[sep_idx + 1 :]
274def sign_hash(hash_value: bytes, priv_key: key.PrivateKey, hash_method: str) -> bytes:
275 """Signs a precomputed hash with the private key.
277 Signs the hash with the given key. This is known as a "detached signature",
278 because the message itself isn't altered.
280 :param hash_value: A precomputed hash to sign (ignores message).
281 :param priv_key: the :py:class:`rsa.PrivateKey` to sign with
282 :param hash_method: the hash method used on the message. Use 'MD5', 'SHA-1',
283 'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'.
284 :return: a message signature block.
285 :raise OverflowError: if the private key is too small to contain the
286 requested hash.
288 """
290 # Get the ASN1 code for this hash method
291 if hash_method not in HASH_ASN1:
292 raise ValueError("Invalid hash method: %s" % hash_method)
293 asn1code = HASH_ASN1[hash_method]
295 # Encrypt the hash with the private key
296 cleartext = asn1code + hash_value
297 keylength = common.byte_size(priv_key.n)
298 padded = _pad_for_signing(cleartext, keylength)
300 payload = transform.bytes2int(padded)
301 encrypted = priv_key.blinded_decrypt(payload)
302 block = transform.int2bytes(encrypted, keylength)
304 return block
307def sign(message: bytes, priv_key: key.PrivateKey, hash_method: str) -> bytes:
308 """Signs the message with the private key.
310 Hashes the message, then signs the hash with the given key. This is known
311 as a "detached signature", because the message itself isn't altered.
313 :param message: the message to sign. Can be an 8-bit string or a file-like
314 object. If ``message`` has a ``read()`` method, it is assumed to be a
315 file-like object.
316 :param priv_key: the :py:class:`rsa.PrivateKey` to sign with
317 :param hash_method: the hash method used on the message. Use 'MD5', 'SHA-1',
318 'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'.
319 :return: a message signature block.
320 :raise OverflowError: if the private key is too small to contain the
321 requested hash.
323 """
325 msg_hash = compute_hash(message, hash_method)
326 return sign_hash(msg_hash, priv_key, hash_method)
329def verify(message: bytes, signature: bytes, pub_key: key.PublicKey) -> str:
330 """Verifies that the signature matches the message.
332 The hash method is detected automatically from the signature.
334 :param message: the signed message. Can be an 8-bit string or a file-like
335 object. If ``message`` has a ``read()`` method, it is assumed to be a
336 file-like object.
337 :param signature: the signature block, as created with :py:func:`rsa.sign`.
338 :param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message.
339 :raise VerificationError: when the signature doesn't match the message.
340 :returns: the name of the used hash.
342 """
344 keylength = common.byte_size(pub_key.n)
345 if len(signature) != keylength:
346 raise VerificationError("Verification failed")
348 encrypted = transform.bytes2int(signature)
349 decrypted = core.encrypt_int(encrypted, pub_key.e, pub_key.n)
350 clearsig = transform.int2bytes(decrypted, keylength)
352 # Get the hash method
353 method_name = _find_method_hash(clearsig)
354 message_hash = compute_hash(message, method_name)
356 # Reconstruct the expected padded hash
357 cleartext = HASH_ASN1[method_name] + message_hash
358 expected = _pad_for_signing(cleartext, keylength)
360 # Compare with the signed one
361 if expected != clearsig:
362 raise VerificationError("Verification failed")
364 return method_name
367def find_signature_hash(signature: bytes, pub_key: key.PublicKey) -> str:
368 """Returns the hash name detected from the signature.
370 If you also want to verify the message, use :py:func:`rsa.verify()` instead.
371 It also returns the name of the used hash.
373 :param signature: the signature block, as created with :py:func:`rsa.sign`.
374 :param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message.
375 :returns: the name of the used hash.
376 """
378 keylength = common.byte_size(pub_key.n)
379 encrypted = transform.bytes2int(signature)
380 decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n)
381 clearsig = transform.int2bytes(decrypted, keylength)
383 return _find_method_hash(clearsig)
386def yield_fixedblocks(infile: typing.BinaryIO, blocksize: int) -> typing.Iterator[bytes]:
387 """Generator, yields each block of ``blocksize`` bytes in the input file.
389 :param infile: file to read and separate in blocks.
390 :param blocksize: block size in bytes.
391 :returns: a generator that yields the contents of each block
392 """
394 while True:
395 block = infile.read(blocksize)
397 read_bytes = len(block)
398 if read_bytes == 0:
399 break
401 yield block
403 if read_bytes < blocksize:
404 break
407def compute_hash(message: typing.Union[bytes, typing.BinaryIO], method_name: str) -> bytes:
408 """Returns the message digest.
410 :param message: the signed message. Can be an 8-bit string or a file-like
411 object. If ``message`` has a ``read()`` method, it is assumed to be a
412 file-like object.
413 :param method_name: the hash method, must be a key of
414 :py:const:`rsa.pkcs1.HASH_METHODS`.
416 """
418 if method_name not in HASH_METHODS:
419 raise ValueError("Invalid hash method: %s" % method_name)
421 method = HASH_METHODS[method_name]
422 hasher = method()
424 if isinstance(message, bytes):
425 hasher.update(message)
426 else:
427 assert hasattr(message, "read") and hasattr(message.read, "__call__")
428 # read as 1K blocks
429 for block in yield_fixedblocks(message, 1024):
430 hasher.update(block)
432 return hasher.digest()
435def _find_method_hash(clearsig: bytes) -> str:
436 """Finds the hash method.
438 :param clearsig: full padded ASN1 and hash.
439 :return: the used hash method.
440 :raise VerificationFailed: when the hash method cannot be found
441 """
443 for (hashname, asn1code) in HASH_ASN1.items():
444 if asn1code in clearsig:
445 return hashname
447 raise VerificationError("Verification failed")
450__all__ = [
451 "encrypt",
452 "decrypt",
453 "sign",
454 "verify",
455 "DecryptionError",
456 "VerificationError",
457 "CryptoError",
458]
460if __name__ == "__main__":
461 print("Running doctests 1000x or until failure")
462 import doctest
464 for count in range(1000):
465 (failures, tests) = doctest.testmod()
466 if failures:
467 break
469 if count % 100 == 0 and count:
470 print("%i times" % count)
472 print("Doctests done")