aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/rsa/pkcs1.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/rsa/pkcs1.py')
-rw-r--r--.venv/lib/python3.12/site-packages/rsa/pkcs1.py485
1 files changed, 485 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/rsa/pkcs1.py b/.venv/lib/python3.12/site-packages/rsa/pkcs1.py
new file mode 100644
index 00000000..b7f54220
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/rsa/pkcs1.py
@@ -0,0 +1,485 @@
+# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Functions for PKCS#1 version 1.5 encryption and signing
+
+This module implements certain functionality from PKCS#1 version 1.5. For a
+very clear example, read http://www.di-mgt.com.au/rsa_alg.html#pkcs1schemes
+
+At least 8 bytes of random padding is used when encrypting a message. This makes
+these methods much more secure than the ones in the ``rsa`` module.
+
+WARNING: this module leaks information when decryption fails. The exceptions
+that are raised contain the Python traceback information, which can be used to
+deduce where in the process the failure occurred. DO NOT PASS SUCH INFORMATION
+to your users.
+"""
+
+import hashlib
+import os
+import sys
+import typing
+from hmac import compare_digest
+
+from . import common, transform, core, key
+
+if typing.TYPE_CHECKING:
+ HashType = hashlib._Hash
+else:
+ HashType = typing.Any
+
+# ASN.1 codes that describe the hash algorithm used.
+HASH_ASN1 = {
+ "MD5": b"\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04\x10",
+ "SHA-1": b"\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14",
+ "SHA-224": b"\x30\x2d\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04\x05\x00\x04\x1c",
+ "SHA-256": b"\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20",
+ "SHA-384": b"\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30",
+ "SHA-512": b"\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40",
+}
+
+HASH_METHODS: typing.Dict[str, typing.Callable[[], HashType]] = {
+ "MD5": hashlib.md5,
+ "SHA-1": hashlib.sha1,
+ "SHA-224": hashlib.sha224,
+ "SHA-256": hashlib.sha256,
+ "SHA-384": hashlib.sha384,
+ "SHA-512": hashlib.sha512,
+}
+"""Hash methods supported by this library."""
+
+
+if sys.version_info >= (3, 6):
+ # Python 3.6 introduced SHA3 support.
+ HASH_ASN1.update(
+ {
+ "SHA3-256": b"\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x08\x05\x00\x04\x20",
+ "SHA3-384": b"\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x09\x05\x00\x04\x30",
+ "SHA3-512": b"\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x0a\x05\x00\x04\x40",
+ }
+ )
+
+ HASH_METHODS.update(
+ {
+ "SHA3-256": hashlib.sha3_256,
+ "SHA3-384": hashlib.sha3_384,
+ "SHA3-512": hashlib.sha3_512,
+ }
+ )
+
+
+class CryptoError(Exception):
+ """Base class for all exceptions in this module."""
+
+
+class DecryptionError(CryptoError):
+ """Raised when decryption fails."""
+
+
+class VerificationError(CryptoError):
+ """Raised when verification fails."""
+
+
+def _pad_for_encryption(message: bytes, target_length: int) -> bytes:
+ r"""Pads the message for encryption, returning the padded message.
+
+ :return: 00 02 RANDOM_DATA 00 MESSAGE
+
+ >>> block = _pad_for_encryption(b'hello', 16)
+ >>> len(block)
+ 16
+ >>> block[0:2]
+ b'\x00\x02'
+ >>> block[-6:]
+ b'\x00hello'
+
+ """
+
+ max_msglength = target_length - 11
+ msglength = len(message)
+
+ if msglength > max_msglength:
+ raise OverflowError(
+ "%i bytes needed for message, but there is only"
+ " space for %i" % (msglength, max_msglength)
+ )
+
+ # Get random padding
+ padding = b""
+ padding_length = target_length - msglength - 3
+
+ # We remove 0-bytes, so we'll end up with less padding than we've asked for,
+ # so keep adding data until we're at the correct length.
+ while len(padding) < padding_length:
+ needed_bytes = padding_length - len(padding)
+
+ # Always read at least 8 bytes more than we need, and trim off the rest
+ # after removing the 0-bytes. This increases the chance of getting
+ # enough bytes, especially when needed_bytes is small
+ new_padding = os.urandom(needed_bytes + 5)
+ new_padding = new_padding.replace(b"\x00", b"")
+ padding = padding + new_padding[:needed_bytes]
+
+ assert len(padding) == padding_length
+
+ return b"".join([b"\x00\x02", padding, b"\x00", message])
+
+
+def _pad_for_signing(message: bytes, target_length: int) -> bytes:
+ r"""Pads the message for signing, returning the padded message.
+
+ The padding is always a repetition of FF bytes.
+
+ :return: 00 01 PADDING 00 MESSAGE
+
+ >>> block = _pad_for_signing(b'hello', 16)
+ >>> len(block)
+ 16
+ >>> block[0:2]
+ b'\x00\x01'
+ >>> block[-6:]
+ b'\x00hello'
+ >>> block[2:-6]
+ b'\xff\xff\xff\xff\xff\xff\xff\xff'
+
+ """
+
+ max_msglength = target_length - 11
+ msglength = len(message)
+
+ if msglength > max_msglength:
+ raise OverflowError(
+ "%i bytes needed for message, but there is only"
+ " space for %i" % (msglength, max_msglength)
+ )
+
+ padding_length = target_length - msglength - 3
+
+ return b"".join([b"\x00\x01", padding_length * b"\xff", b"\x00", message])
+
+
+def encrypt(message: bytes, pub_key: key.PublicKey) -> bytes:
+ """Encrypts the given message using PKCS#1 v1.5
+
+ :param message: the message to encrypt. Must be a byte string no longer than
+ ``k-11`` bytes, where ``k`` is the number of bytes needed to encode
+ the ``n`` component of the public key.
+ :param pub_key: the :py:class:`rsa.PublicKey` to encrypt with.
+ :raise OverflowError: when the message is too large to fit in the padded
+ block.
+
+ >>> from rsa import key, common
+ >>> (pub_key, priv_key) = key.newkeys(256)
+ >>> message = b'hello'
+ >>> crypto = encrypt(message, pub_key)
+
+ The crypto text should be just as long as the public key 'n' component:
+
+ >>> len(crypto) == common.byte_size(pub_key.n)
+ True
+
+ """
+
+ keylength = common.byte_size(pub_key.n)
+ padded = _pad_for_encryption(message, keylength)
+
+ payload = transform.bytes2int(padded)
+ encrypted = core.encrypt_int(payload, pub_key.e, pub_key.n)
+ block = transform.int2bytes(encrypted, keylength)
+
+ return block
+
+
+def decrypt(crypto: bytes, priv_key: key.PrivateKey) -> bytes:
+ r"""Decrypts the given message using PKCS#1 v1.5
+
+ The decryption is considered 'failed' when the resulting cleartext doesn't
+ start with the bytes 00 02, or when the 00 byte between the padding and
+ the message cannot be found.
+
+ :param crypto: the crypto text as returned by :py:func:`rsa.encrypt`
+ :param priv_key: the :py:class:`rsa.PrivateKey` to decrypt with.
+ :raise DecryptionError: when the decryption fails. No details are given as
+ to why the code thinks the decryption fails, as this would leak
+ information about the private key.
+
+
+ >>> import rsa
+ >>> (pub_key, priv_key) = rsa.newkeys(256)
+
+ It works with strings:
+
+ >>> crypto = encrypt(b'hello', pub_key)
+ >>> decrypt(crypto, priv_key)
+ b'hello'
+
+ And with binary data:
+
+ >>> crypto = encrypt(b'\x00\x00\x00\x00\x01', pub_key)
+ >>> decrypt(crypto, priv_key)
+ b'\x00\x00\x00\x00\x01'
+
+ Altering the encrypted information will *likely* cause a
+ :py:class:`rsa.pkcs1.DecryptionError`. If you want to be *sure*, use
+ :py:func:`rsa.sign`.
+
+
+ .. warning::
+
+ Never display the stack trace of a
+ :py:class:`rsa.pkcs1.DecryptionError` exception. It shows where in the
+ code the exception occurred, and thus leaks information about the key.
+ It's only a tiny bit of information, but every bit makes cracking the
+ keys easier.
+
+ >>> crypto = encrypt(b'hello', pub_key)
+ >>> crypto = crypto[0:5] + b'X' + crypto[6:] # change a byte
+ >>> decrypt(crypto, priv_key)
+ Traceback (most recent call last):
+ ...
+ rsa.pkcs1.DecryptionError: Decryption failed
+
+ """
+
+ blocksize = common.byte_size(priv_key.n)
+ encrypted = transform.bytes2int(crypto)
+ decrypted = priv_key.blinded_decrypt(encrypted)
+ cleartext = transform.int2bytes(decrypted, blocksize)
+
+ # Detect leading zeroes in the crypto. These are not reflected in the
+ # encrypted value (as leading zeroes do not influence the value of an
+ # integer). This fixes CVE-2020-13757.
+ if len(crypto) > blocksize:
+ # This is operating on public information, so doesn't need to be constant-time.
+ raise DecryptionError("Decryption failed")
+
+ # If we can't find the cleartext marker, decryption failed.
+ cleartext_marker_bad = not compare_digest(cleartext[:2], b"\x00\x02")
+
+ # Find the 00 separator between the padding and the message
+ sep_idx = cleartext.find(b"\x00", 2)
+
+ # sep_idx indicates the position of the `\x00` separator that separates the
+ # padding from the actual message. The padding should be at least 8 bytes
+ # long (see https://tools.ietf.org/html/rfc8017#section-7.2.2 step 3), which
+ # means the separator should be at least at index 10 (because of the
+ # `\x00\x02` marker that precedes it).
+ sep_idx_bad = sep_idx < 10
+
+ anything_bad = cleartext_marker_bad | sep_idx_bad
+ if anything_bad:
+ raise DecryptionError("Decryption failed")
+
+ return cleartext[sep_idx + 1 :]
+
+
+def sign_hash(hash_value: bytes, priv_key: key.PrivateKey, hash_method: str) -> bytes:
+ """Signs a precomputed hash with the private key.
+
+ Hashes the message, then signs the hash with the given key. This is known
+ as a "detached signature", because the message itself isn't altered.
+
+ :param hash_value: A precomputed hash to sign (ignores message).
+ :param priv_key: the :py:class:`rsa.PrivateKey` to sign with
+ :param hash_method: the hash method used on the message. Use 'MD5', 'SHA-1',
+ 'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'.
+ :return: a message signature block.
+ :raise OverflowError: if the private key is too small to contain the
+ requested hash.
+
+ """
+
+ # Get the ASN1 code for this hash method
+ if hash_method not in HASH_ASN1:
+ raise ValueError("Invalid hash method: %s" % hash_method)
+ asn1code = HASH_ASN1[hash_method]
+
+ # Encrypt the hash with the private key
+ cleartext = asn1code + hash_value
+ keylength = common.byte_size(priv_key.n)
+ padded = _pad_for_signing(cleartext, keylength)
+
+ payload = transform.bytes2int(padded)
+ encrypted = priv_key.blinded_encrypt(payload)
+ block = transform.int2bytes(encrypted, keylength)
+
+ return block
+
+
+def sign(message: bytes, priv_key: key.PrivateKey, hash_method: str) -> bytes:
+ """Signs the message with the private key.
+
+ Hashes the message, then signs the hash with the given key. This is known
+ as a "detached signature", because the message itself isn't altered.
+
+ :param message: the message to sign. Can be an 8-bit string or a file-like
+ object. If ``message`` has a ``read()`` method, it is assumed to be a
+ file-like object.
+ :param priv_key: the :py:class:`rsa.PrivateKey` to sign with
+ :param hash_method: the hash method used on the message. Use 'MD5', 'SHA-1',
+ 'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'.
+ :return: a message signature block.
+ :raise OverflowError: if the private key is too small to contain the
+ requested hash.
+
+ """
+
+ msg_hash = compute_hash(message, hash_method)
+ return sign_hash(msg_hash, priv_key, hash_method)
+
+
+def verify(message: bytes, signature: bytes, pub_key: key.PublicKey) -> str:
+ """Verifies that the signature matches the message.
+
+ The hash method is detected automatically from the signature.
+
+ :param message: the signed message. Can be an 8-bit string or a file-like
+ object. If ``message`` has a ``read()`` method, it is assumed to be a
+ file-like object.
+ :param signature: the signature block, as created with :py:func:`rsa.sign`.
+ :param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message.
+ :raise VerificationError: when the signature doesn't match the message.
+ :returns: the name of the used hash.
+
+ """
+
+ keylength = common.byte_size(pub_key.n)
+ encrypted = transform.bytes2int(signature)
+ decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n)
+ clearsig = transform.int2bytes(decrypted, keylength)
+
+ # Get the hash method
+ method_name = _find_method_hash(clearsig)
+ message_hash = compute_hash(message, method_name)
+
+ # Reconstruct the expected padded hash
+ cleartext = HASH_ASN1[method_name] + message_hash
+ expected = _pad_for_signing(cleartext, keylength)
+
+ if len(signature) != keylength:
+ raise VerificationError("Verification failed")
+
+ # Compare with the signed one
+ if expected != clearsig:
+ raise VerificationError("Verification failed")
+
+ return method_name
+
+
+def find_signature_hash(signature: bytes, pub_key: key.PublicKey) -> str:
+ """Returns the hash name detected from the signature.
+
+ If you also want to verify the message, use :py:func:`rsa.verify()` instead.
+ It also returns the name of the used hash.
+
+ :param signature: the signature block, as created with :py:func:`rsa.sign`.
+ :param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message.
+ :returns: the name of the used hash.
+ """
+
+ keylength = common.byte_size(pub_key.n)
+ encrypted = transform.bytes2int(signature)
+ decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n)
+ clearsig = transform.int2bytes(decrypted, keylength)
+
+ return _find_method_hash(clearsig)
+
+
+def yield_fixedblocks(infile: typing.BinaryIO, blocksize: int) -> typing.Iterator[bytes]:
+ """Generator, yields each block of ``blocksize`` bytes in the input file.
+
+ :param infile: file to read and separate in blocks.
+ :param blocksize: block size in bytes.
+ :returns: a generator that yields the contents of each block
+ """
+
+ while True:
+ block = infile.read(blocksize)
+
+ read_bytes = len(block)
+ if read_bytes == 0:
+ break
+
+ yield block
+
+ if read_bytes < blocksize:
+ break
+
+
+def compute_hash(message: typing.Union[bytes, typing.BinaryIO], method_name: str) -> bytes:
+ """Returns the message digest.
+
+ :param message: the signed message. Can be an 8-bit string or a file-like
+ object. If ``message`` has a ``read()`` method, it is assumed to be a
+ file-like object.
+ :param method_name: the hash method, must be a key of
+ :py:const:`rsa.pkcs1.HASH_METHODS`.
+
+ """
+
+ if method_name not in HASH_METHODS:
+ raise ValueError("Invalid hash method: %s" % method_name)
+
+ method = HASH_METHODS[method_name]
+ hasher = method()
+
+ if isinstance(message, bytes):
+ hasher.update(message)
+ else:
+ assert hasattr(message, "read") and hasattr(message.read, "__call__")
+ # read as 1K blocks
+ for block in yield_fixedblocks(message, 1024):
+ hasher.update(block)
+
+ return hasher.digest()
+
+
+def _find_method_hash(clearsig: bytes) -> str:
+ """Finds the hash method.
+
+ :param clearsig: full padded ASN1 and hash.
+ :return: the used hash method.
+ :raise VerificationFailed: when the hash method cannot be found
+ """
+
+ for (hashname, asn1code) in HASH_ASN1.items():
+ if asn1code in clearsig:
+ return hashname
+
+ raise VerificationError("Verification failed")
+
+
+__all__ = [
+ "encrypt",
+ "decrypt",
+ "sign",
+ "verify",
+ "DecryptionError",
+ "VerificationError",
+ "CryptoError",
+]
+
+if __name__ == "__main__":
+ print("Running doctests 1000x or until failure")
+ import doctest
+
+ for count in range(1000):
+ (failures, tests) = doctest.testmod()
+ if failures:
+ break
+
+ if count % 100 == 0 and count:
+ print("%i times" % count)
+
+ print("Doctests done")