Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/rsa/pkcs1.py: 23%
129 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:40 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:40 +0000
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}
52HASH_METHODS: typing.Dict[str, typing.Callable[[], HashType]] = {
53 "MD5": hashlib.md5,
54 "SHA-1": hashlib.sha1,
55 "SHA-224": hashlib.sha224,
56 "SHA-256": hashlib.sha256,
57 "SHA-384": hashlib.sha384,
58 "SHA-512": hashlib.sha512,
59}
60"""Hash methods supported by this library."""
63if sys.version_info >= (3, 6):
64 # Python 3.6 introduced SHA3 support.
65 HASH_ASN1.update(
66 {
67 "SHA3-256": b"\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x08\x05\x00\x04\x20",
68 "SHA3-384": b"\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x09\x05\x00\x04\x30",
69 "SHA3-512": b"\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x0a\x05\x00\x04\x40",
70 }
71 )
73 HASH_METHODS.update(
74 {
75 "SHA3-256": hashlib.sha3_256,
76 "SHA3-384": hashlib.sha3_384,
77 "SHA3-512": hashlib.sha3_512,
78 }
79 )
82class CryptoError(Exception):
83 """Base class for all exceptions in this module."""
86class DecryptionError(CryptoError):
87 """Raised when decryption fails."""
90class VerificationError(CryptoError):
91 """Raised when verification fails."""
94def _pad_for_encryption(message: bytes, target_length: int) -> bytes:
95 r"""Pads the message for encryption, returning the padded message.
97 :return: 00 02 RANDOM_DATA 00 MESSAGE
99 >>> block = _pad_for_encryption(b'hello', 16)
100 >>> len(block)
101 16
102 >>> block[0:2]
103 b'\x00\x02'
104 >>> block[-6:]
105 b'\x00hello'
107 """
109 max_msglength = target_length - 11
110 msglength = len(message)
112 if msglength > max_msglength:
113 raise OverflowError(
114 "%i bytes needed for message, but there is only"
115 " space for %i" % (msglength, max_msglength)
116 )
118 # Get random padding
119 padding = b""
120 padding_length = target_length - msglength - 3
122 # We remove 0-bytes, so we'll end up with less padding than we've asked for,
123 # so keep adding data until we're at the correct length.
124 while len(padding) < padding_length:
125 needed_bytes = padding_length - len(padding)
127 # Always read at least 8 bytes more than we need, and trim off the rest
128 # after removing the 0-bytes. This increases the chance of getting
129 # enough bytes, especially when needed_bytes is small
130 new_padding = os.urandom(needed_bytes + 5)
131 new_padding = new_padding.replace(b"\x00", b"")
132 padding = padding + new_padding[:needed_bytes]
134 assert len(padding) == padding_length
136 return b"".join([b"\x00\x02", padding, b"\x00", message])
139def _pad_for_signing(message: bytes, target_length: int) -> bytes:
140 r"""Pads the message for signing, returning the padded message.
142 The padding is always a repetition of FF bytes.
144 :return: 00 01 PADDING 00 MESSAGE
146 >>> block = _pad_for_signing(b'hello', 16)
147 >>> len(block)
148 16
149 >>> block[0:2]
150 b'\x00\x01'
151 >>> block[-6:]
152 b'\x00hello'
153 >>> block[2:-6]
154 b'\xff\xff\xff\xff\xff\xff\xff\xff'
156 """
158 max_msglength = target_length - 11
159 msglength = len(message)
161 if msglength > max_msglength:
162 raise OverflowError(
163 "%i bytes needed for message, but there is only"
164 " space for %i" % (msglength, max_msglength)
165 )
167 padding_length = target_length - msglength - 3
169 return b"".join([b"\x00\x01", padding_length * b"\xff", b"\x00", message])
172def encrypt(message: bytes, pub_key: key.PublicKey) -> bytes:
173 """Encrypts the given message using PKCS#1 v1.5
175 :param message: the message to encrypt. Must be a byte string no longer than
176 ``k-11`` bytes, where ``k`` is the number of bytes needed to encode
177 the ``n`` component of the public key.
178 :param pub_key: the :py:class:`rsa.PublicKey` to encrypt with.
179 :raise OverflowError: when the message is too large to fit in the padded
180 block.
182 >>> from rsa import key, common
183 >>> (pub_key, priv_key) = key.newkeys(256)
184 >>> message = b'hello'
185 >>> crypto = encrypt(message, pub_key)
187 The crypto text should be just as long as the public key 'n' component:
189 >>> len(crypto) == common.byte_size(pub_key.n)
190 True
192 """
194 keylength = common.byte_size(pub_key.n)
195 padded = _pad_for_encryption(message, keylength)
197 payload = transform.bytes2int(padded)
198 encrypted = core.encrypt_int(payload, pub_key.e, pub_key.n)
199 block = transform.int2bytes(encrypted, keylength)
201 return block
204def decrypt(crypto: bytes, priv_key: key.PrivateKey) -> bytes:
205 r"""Decrypts the given message using PKCS#1 v1.5
207 The decryption is considered 'failed' when the resulting cleartext doesn't
208 start with the bytes 00 02, or when the 00 byte between the padding and
209 the message cannot be found.
211 :param crypto: the crypto text as returned by :py:func:`rsa.encrypt`
212 :param priv_key: the :py:class:`rsa.PrivateKey` to decrypt with.
213 :raise DecryptionError: when the decryption fails. No details are given as
214 to why the code thinks the decryption fails, as this would leak
215 information about the private key.
218 >>> import rsa
219 >>> (pub_key, priv_key) = rsa.newkeys(256)
221 It works with strings:
223 >>> crypto = encrypt(b'hello', pub_key)
224 >>> decrypt(crypto, priv_key)
225 b'hello'
227 And with binary data:
229 >>> crypto = encrypt(b'\x00\x00\x00\x00\x01', pub_key)
230 >>> decrypt(crypto, priv_key)
231 b'\x00\x00\x00\x00\x01'
233 Altering the encrypted information will *likely* cause a
234 :py:class:`rsa.pkcs1.DecryptionError`. If you want to be *sure*, use
235 :py:func:`rsa.sign`.
238 .. warning::
240 Never display the stack trace of a
241 :py:class:`rsa.pkcs1.DecryptionError` exception. It shows where in the
242 code the exception occurred, and thus leaks information about the key.
243 It's only a tiny bit of information, but every bit makes cracking the
244 keys easier.
246 >>> crypto = encrypt(b'hello', pub_key)
247 >>> crypto = crypto[0:5] + b'X' + crypto[6:] # change a byte
248 >>> decrypt(crypto, priv_key)
249 Traceback (most recent call last):
250 ...
251 rsa.pkcs1.DecryptionError: Decryption failed
253 """
255 blocksize = common.byte_size(priv_key.n)
256 encrypted = transform.bytes2int(crypto)
257 decrypted = priv_key.blinded_decrypt(encrypted)
258 cleartext = transform.int2bytes(decrypted, blocksize)
260 # Detect leading zeroes in the crypto. These are not reflected in the
261 # encrypted value (as leading zeroes do not influence the value of an
262 # integer). This fixes CVE-2020-13757.
263 if len(crypto) > blocksize:
264 # This is operating on public information, so doesn't need to be constant-time.
265 raise DecryptionError("Decryption failed")
267 # If we can't find the cleartext marker, decryption failed.
268 cleartext_marker_bad = not compare_digest(cleartext[:2], b"\x00\x02")
270 # Find the 00 separator between the padding and the message
271 sep_idx = cleartext.find(b"\x00", 2)
273 # sep_idx indicates the position of the `\x00` separator that separates the
274 # padding from the actual message. The padding should be at least 8 bytes
275 # long (see https://tools.ietf.org/html/rfc8017#section-7.2.2 step 3), which
276 # means the separator should be at least at index 10 (because of the
277 # `\x00\x02` marker that precedes it).
278 sep_idx_bad = sep_idx < 10
280 anything_bad = cleartext_marker_bad | sep_idx_bad
281 if anything_bad:
282 raise DecryptionError("Decryption failed")
284 return cleartext[sep_idx + 1 :]
287def sign_hash(hash_value: bytes, priv_key: key.PrivateKey, hash_method: str) -> bytes:
288 """Signs a precomputed hash with the private key.
290 Hashes the message, then signs the hash with the given key. This is known
291 as a "detached signature", because the message itself isn't altered.
293 :param hash_value: A precomputed hash to sign (ignores message).
294 :param priv_key: the :py:class:`rsa.PrivateKey` to sign with
295 :param hash_method: the hash method used on the message. Use 'MD5', 'SHA-1',
296 'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'.
297 :return: a message signature block.
298 :raise OverflowError: if the private key is too small to contain the
299 requested hash.
301 """
303 # Get the ASN1 code for this hash method
304 if hash_method not in HASH_ASN1:
305 raise ValueError("Invalid hash method: %s" % hash_method)
306 asn1code = HASH_ASN1[hash_method]
308 # Encrypt the hash with the private key
309 cleartext = asn1code + hash_value
310 keylength = common.byte_size(priv_key.n)
311 padded = _pad_for_signing(cleartext, keylength)
313 payload = transform.bytes2int(padded)
314 encrypted = priv_key.blinded_encrypt(payload)
315 block = transform.int2bytes(encrypted, keylength)
317 return block
320def sign(message: bytes, priv_key: key.PrivateKey, hash_method: str) -> bytes:
321 """Signs the message with the private key.
323 Hashes the message, then signs the hash with the given key. This is known
324 as a "detached signature", because the message itself isn't altered.
326 :param message: the message to sign. Can be an 8-bit string or a file-like
327 object. If ``message`` has a ``read()`` method, it is assumed to be a
328 file-like object.
329 :param priv_key: the :py:class:`rsa.PrivateKey` to sign with
330 :param hash_method: the hash method used on the message. Use 'MD5', 'SHA-1',
331 'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'.
332 :return: a message signature block.
333 :raise OverflowError: if the private key is too small to contain the
334 requested hash.
336 """
338 msg_hash = compute_hash(message, hash_method)
339 return sign_hash(msg_hash, priv_key, hash_method)
342def verify(message: bytes, signature: bytes, pub_key: key.PublicKey) -> str:
343 """Verifies that the signature matches the message.
345 The hash method is detected automatically from the signature.
347 :param message: the signed message. Can be an 8-bit string or a file-like
348 object. If ``message`` has a ``read()`` method, it is assumed to be a
349 file-like object.
350 :param signature: the signature block, as created with :py:func:`rsa.sign`.
351 :param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message.
352 :raise VerificationError: when the signature doesn't match the message.
353 :returns: the name of the used hash.
355 """
357 keylength = common.byte_size(pub_key.n)
358 encrypted = transform.bytes2int(signature)
359 decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n)
360 clearsig = transform.int2bytes(decrypted, keylength)
362 # Get the hash method
363 method_name = _find_method_hash(clearsig)
364 message_hash = compute_hash(message, method_name)
366 # Reconstruct the expected padded hash
367 cleartext = HASH_ASN1[method_name] + message_hash
368 expected = _pad_for_signing(cleartext, keylength)
370 if len(signature) != keylength:
371 raise VerificationError("Verification failed")
373 # Compare with the signed one
374 if expected != clearsig:
375 raise VerificationError("Verification failed")
377 return method_name
380def find_signature_hash(signature: bytes, pub_key: key.PublicKey) -> str:
381 """Returns the hash name detected from the signature.
383 If you also want to verify the message, use :py:func:`rsa.verify()` instead.
384 It also returns the name of the used hash.
386 :param signature: the signature block, as created with :py:func:`rsa.sign`.
387 :param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message.
388 :returns: the name of the used hash.
389 """
391 keylength = common.byte_size(pub_key.n)
392 encrypted = transform.bytes2int(signature)
393 decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n)
394 clearsig = transform.int2bytes(decrypted, keylength)
396 return _find_method_hash(clearsig)
399def yield_fixedblocks(infile: typing.BinaryIO, blocksize: int) -> typing.Iterator[bytes]:
400 """Generator, yields each block of ``blocksize`` bytes in the input file.
402 :param infile: file to read and separate in blocks.
403 :param blocksize: block size in bytes.
404 :returns: a generator that yields the contents of each block
405 """
407 while True:
408 block = infile.read(blocksize)
410 read_bytes = len(block)
411 if read_bytes == 0:
412 break
414 yield block
416 if read_bytes < blocksize:
417 break
420def compute_hash(message: typing.Union[bytes, typing.BinaryIO], method_name: str) -> bytes:
421 """Returns the message digest.
423 :param message: the signed message. Can be an 8-bit string or a file-like
424 object. If ``message`` has a ``read()`` method, it is assumed to be a
425 file-like object.
426 :param method_name: the hash method, must be a key of
427 :py:const:`rsa.pkcs1.HASH_METHODS`.
429 """
431 if method_name not in HASH_METHODS:
432 raise ValueError("Invalid hash method: %s" % method_name)
434 method = HASH_METHODS[method_name]
435 hasher = method()
437 if isinstance(message, bytes):
438 hasher.update(message)
439 else:
440 assert hasattr(message, "read") and hasattr(message.read, "__call__")
441 # read as 1K blocks
442 for block in yield_fixedblocks(message, 1024):
443 hasher.update(block)
445 return hasher.digest()
448def _find_method_hash(clearsig: bytes) -> str:
449 """Finds the hash method.
451 :param clearsig: full padded ASN1 and hash.
452 :return: the used hash method.
453 :raise VerificationFailed: when the hash method cannot be found
454 """
456 for (hashname, asn1code) in HASH_ASN1.items():
457 if asn1code in clearsig:
458 return hashname
460 raise VerificationError("Verification failed")
463__all__ = [
464 "encrypt",
465 "decrypt",
466 "sign",
467 "verify",
468 "DecryptionError",
469 "VerificationError",
470 "CryptoError",
471]
473if __name__ == "__main__":
474 print("Running doctests 1000x or until failure")
475 import doctest
477 for count in range(1000):
478 (failures, tests) = doctest.testmod()
479 if failures:
480 break
482 if count % 100 == 0 and count:
483 print("%i times" % count)
485 print("Doctests done")