Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
pkcs1.py486 linesDownload Raw Back to rsa
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.
14
15"""Functions for PKCS#1 version 1.5 encryption and signing
16
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
19
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.
22
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"""
28
29import hashlib
30import os
31import sys
32import typing
33from hmac import compare_digest
34
35from . import common, transform, core, key
36
37if typing.TYPE_CHECKING:
38    HashType = hashlib._Hash
39else:
40    HashType = typing.Any
41
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}
51
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."""
61
62
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    )
72
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    )
80
81
82class CryptoError(Exception):
83    """Base class for all exceptions in this module."""
84
85
86class DecryptionError(CryptoError):
87    """Raised when decryption fails."""
88
89
90class VerificationError(CryptoError):
91    """Raised when verification fails."""
92
93
94def _pad_for_encryption(message: bytes, target_length: int) -> bytes:
95    r"""Pads the message for encryption, returning the padded message.
96
97    :return: 00 02 RANDOM_DATA 00 MESSAGE
98
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'
106
107    """
108
109    max_msglength = target_length - 11
110    msglength = len(message)
111
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        )
117
118    # Get random padding
119    padding = b""
120    padding_length = target_length - msglength - 3
121
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)
126
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]
133
134    assert len(padding) == padding_length
135
136    return b"".join([b"\x00\x02", padding, b"\x00", message])
137
138
139def _pad_for_signing(message: bytes, target_length: int) -> bytes:
140    r"""Pads the message for signing, returning the padded message.
141
142    The padding is always a repetition of FF bytes.
143
144    :return: 00 01 PADDING 00 MESSAGE
145
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'
155
156    """
157
158    max_msglength = target_length - 11
159    msglength = len(message)
160
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        )
166
167    padding_length = target_length - msglength - 3
168
169    return b"".join([b"\x00\x01", padding_length * b"\xff", b"\x00", message])
170
171
172def encrypt(message: bytes, pub_key: key.PublicKey) -> bytes:
173    """Encrypts the given message using PKCS#1 v1.5
174
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.
181
182    >>> from rsa import key, common
183    >>> (pub_key, priv_key) = key.newkeys(256)
184    >>> message = b'hello'
185    >>> crypto = encrypt(message, pub_key)
186
187    The crypto text should be just as long as the public key 'n' component:
188
189    >>> len(crypto) == common.byte_size(pub_key.n)
190    True
191
192    """
193
194    keylength = common.byte_size(pub_key.n)
195    padded = _pad_for_encryption(message, keylength)
196
197    payload = transform.bytes2int(padded)
198    encrypted = core.encrypt_int(payload, pub_key.e, pub_key.n)
199    block = transform.int2bytes(encrypted, keylength)
200
201    return block
202
203
204def decrypt(crypto: bytes, priv_key: key.PrivateKey) -> bytes:
205    r"""Decrypts the given message using PKCS#1 v1.5
206
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.
210
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.
216
217
218    >>> import rsa
219    >>> (pub_key, priv_key) = rsa.newkeys(256)
220
221    It works with strings:
222
223    >>> crypto = encrypt(b'hello', pub_key)
224    >>> decrypt(crypto, priv_key)
225    b'hello'
226
227    And with binary data:
228
229    >>> crypto = encrypt(b'\x00\x00\x00\x00\x01', pub_key)
230    >>> decrypt(crypto, priv_key)
231    b'\x00\x00\x00\x00\x01'
232
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`.
236
237
238    .. warning::
239
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.
245
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
252
253    """
254
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)
259
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")
266
267    # If we can't find the cleartext marker, decryption failed.
268    cleartext_marker_bad = not compare_digest(cleartext[:2], b"\x00\x02")
269
270    # Find the 00 separator between the padding and the message
271    sep_idx = cleartext.find(b"\x00", 2)
272
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
279
280    anything_bad = cleartext_marker_bad | sep_idx_bad
281    if anything_bad:
282        raise DecryptionError("Decryption failed")
283
284    return cleartext[sep_idx + 1 :]
285
286
287def sign_hash(hash_value: bytes, priv_key: key.PrivateKey, hash_method: str) -> bytes:
288    """Signs a precomputed hash with the private key.
289
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.
292
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.
300
301    """
302
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]
307
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)
312
313    payload = transform.bytes2int(padded)
314    encrypted = priv_key.blinded_encrypt(payload)
315    block = transform.int2bytes(encrypted, keylength)
316
317    return block
318
319
320def sign(message: bytes, priv_key: key.PrivateKey, hash_method: str) -> bytes:
321    """Signs the message with the private key.
322
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.
325
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.
335
336    """
337
338    msg_hash = compute_hash(message, hash_method)
339    return sign_hash(msg_hash, priv_key, hash_method)
340
341
342def verify(message: bytes, signature: bytes, pub_key: key.PublicKey) -> str:
343    """Verifies that the signature matches the message.
344
345    The hash method is detected automatically from the signature.
346
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.
354
355    """
356
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)
361
362    # Get the hash method
363    method_name = _find_method_hash(clearsig)
364    message_hash = compute_hash(message, method_name)
365
366    # Reconstruct the expected padded hash
367    cleartext = HASH_ASN1[method_name] + message_hash
368    expected = _pad_for_signing(cleartext, keylength)
369
370    if len(signature) != keylength:
371        raise VerificationError("Verification failed")
372
373    # Compare with the signed one
374    if expected != clearsig:
375        raise VerificationError("Verification failed")
376
377    return method_name
378
379
380def find_signature_hash(signature: bytes, pub_key: key.PublicKey) -> str:
381    """Returns the hash name detected from the signature.
382
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.
385
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    """
390
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)
395
396    return _find_method_hash(clearsig)
397
398
399def yield_fixedblocks(infile: typing.BinaryIO, blocksize: int) -> typing.Iterator[bytes]:
400    """Generator, yields each block of ``blocksize`` bytes in the input file.
401
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    """
406
407    while True:
408        block = infile.read(blocksize)
409
410        read_bytes = len(block)
411        if read_bytes == 0:
412            break
413
414        yield block
415
416        if read_bytes < blocksize:
417            break
418
419
420def compute_hash(message: typing.Union[bytes, typing.BinaryIO], method_name: str) -> bytes:
421    """Returns the message digest.
422
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`.
428
429    """
430
431    if method_name not in HASH_METHODS:
432        raise ValueError("Invalid hash method: %s" % method_name)
433
434    method = HASH_METHODS[method_name]
435    hasher = method()
436
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)
444
445    return hasher.digest()
446
447
448def _find_method_hash(clearsig: bytes) -> str:
449    """Finds the hash method.
450
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    """
455
456    for (hashname, asn1code) in HASH_ASN1.items():
457        if asn1code in clearsig:
458            return hashname
459
460    raise VerificationError("Verification failed")
461
462
463__all__ = [
464    "encrypt",
465    "decrypt",
466    "sign",
467    "verify",
468    "DecryptionError",
469    "VerificationError",
470    "CryptoError",
471]
472
473if __name__ == "__main__":
474    print("Running doctests 1000x or until failure")
475    import doctest
476
477    for count in range(1000):
478        (failures, tests) = doctest.testmod()
479        if failures:
480            break
481
482        if count % 100 == 0 and count:
483            print("%i times" % count)
484
485    print("Doctests done")
486 
codekingpro/portable-devtools · Team Ai