• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# -*- coding: utf-8 -*-
2#
3#  Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
4#
5#  Licensed under the Apache License, Version 2.0 (the "License");
6#  you may not use this file except in compliance with the License.
7#  You may obtain a copy of the License at
8#
9#      https://www.apache.org/licenses/LICENSE-2.0
10#
11#  Unless required by applicable law or agreed to in writing, software
12#  distributed under the License is distributed on an "AS IS" BASIS,
13#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#  See the License for the specific language governing permissions and
15#  limitations under the License.
16
17"""Functions for PKCS#1 version 1.5 encryption and signing
18
19This module implements certain functionality from PKCS#1 version 1.5. For a
20very clear example, read http://www.di-mgt.com.au/rsa_alg.html#pkcs1schemes
21
22At least 8 bytes of random padding is used when encrypting a message. This makes
23these methods much more secure than the ones in the ``rsa`` module.
24
25WARNING: this module leaks information when decryption fails. The exceptions
26that are raised contain the Python traceback information, which can be used to
27deduce where in the process the failure occurred. DO NOT PASS SUCH INFORMATION
28to your users.
29"""
30
31import hashlib
32import os
33
34from rsa._compat import range
35from rsa import common, transform, core
36
37# ASN.1 codes that describe the hash algorithm used.
38HASH_ASN1 = {
39    'MD5': b'\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04\x10',
40    'SHA-1': b'\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14',
41    'SHA-224': b'\x30\x2d\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04\x05\x00\x04\x1c',
42    'SHA-256': b'\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20',
43    'SHA-384': b'\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30',
44    'SHA-512': b'\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40',
45}
46
47HASH_METHODS = {
48    'MD5': hashlib.md5,
49    'SHA-1': hashlib.sha1,
50    'SHA-224': hashlib.sha224,
51    'SHA-256': hashlib.sha256,
52    'SHA-384': hashlib.sha384,
53    'SHA-512': hashlib.sha512,
54}
55
56
57class CryptoError(Exception):
58    """Base class for all exceptions in this module."""
59
60
61class DecryptionError(CryptoError):
62    """Raised when decryption fails."""
63
64
65class VerificationError(CryptoError):
66    """Raised when verification fails."""
67
68
69def _pad_for_encryption(message, target_length):
70    r"""Pads the message for encryption, returning the padded message.
71
72    :return: 00 02 RANDOM_DATA 00 MESSAGE
73
74    >>> block = _pad_for_encryption(b'hello', 16)
75    >>> len(block)
76    16
77    >>> block[0:2]
78    b'\x00\x02'
79    >>> block[-6:]
80    b'\x00hello'
81
82    """
83
84    max_msglength = target_length - 11
85    msglength = len(message)
86
87    if msglength > max_msglength:
88        raise OverflowError('%i bytes needed for message, but there is only'
89                            ' space for %i' % (msglength, max_msglength))
90
91    # Get random padding
92    padding = b''
93    padding_length = target_length - msglength - 3
94
95    # We remove 0-bytes, so we'll end up with less padding than we've asked for,
96    # so keep adding data until we're at the correct length.
97    while len(padding) < padding_length:
98        needed_bytes = padding_length - len(padding)
99
100        # Always read at least 8 bytes more than we need, and trim off the rest
101        # after removing the 0-bytes. This increases the chance of getting
102        # enough bytes, especially when needed_bytes is small
103        new_padding = os.urandom(needed_bytes + 5)
104        new_padding = new_padding.replace(b'\x00', b'')
105        padding = padding + new_padding[:needed_bytes]
106
107    assert len(padding) == padding_length
108
109    return b''.join([b'\x00\x02',
110                     padding,
111                     b'\x00',
112                     message])
113
114
115def _pad_for_signing(message, target_length):
116    r"""Pads the message for signing, returning the padded message.
117
118    The padding is always a repetition of FF bytes.
119
120    :return: 00 01 PADDING 00 MESSAGE
121
122    >>> block = _pad_for_signing(b'hello', 16)
123    >>> len(block)
124    16
125    >>> block[0:2]
126    b'\x00\x01'
127    >>> block[-6:]
128    b'\x00hello'
129    >>> block[2:-6]
130    b'\xff\xff\xff\xff\xff\xff\xff\xff'
131
132    """
133
134    max_msglength = target_length - 11
135    msglength = len(message)
136
137    if msglength > max_msglength:
138        raise OverflowError('%i bytes needed for message, but there is only'
139                            ' space for %i' % (msglength, max_msglength))
140
141    padding_length = target_length - msglength - 3
142
143    return b''.join([b'\x00\x01',
144                     padding_length * b'\xff',
145                     b'\x00',
146                     message])
147
148
149def encrypt(message, pub_key):
150    """Encrypts the given message using PKCS#1 v1.5
151
152    :param message: the message to encrypt. Must be a byte string no longer than
153        ``k-11`` bytes, where ``k`` is the number of bytes needed to encode
154        the ``n`` component of the public key.
155    :param pub_key: the :py:class:`rsa.PublicKey` to encrypt with.
156    :raise OverflowError: when the message is too large to fit in the padded
157        block.
158
159    >>> from rsa import key, common
160    >>> (pub_key, priv_key) = key.newkeys(256)
161    >>> message = b'hello'
162    >>> crypto = encrypt(message, pub_key)
163
164    The crypto text should be just as long as the public key 'n' component:
165
166    >>> len(crypto) == common.byte_size(pub_key.n)
167    True
168
169    """
170
171    keylength = common.byte_size(pub_key.n)
172    padded = _pad_for_encryption(message, keylength)
173
174    payload = transform.bytes2int(padded)
175    encrypted = core.encrypt_int(payload, pub_key.e, pub_key.n)
176    block = transform.int2bytes(encrypted, keylength)
177
178    return block
179
180
181def decrypt(crypto, priv_key):
182    r"""Decrypts the given message using PKCS#1 v1.5
183
184    The decryption is considered 'failed' when the resulting cleartext doesn't
185    start with the bytes 00 02, or when the 00 byte between the padding and
186    the message cannot be found.
187
188    :param crypto: the crypto text as returned by :py:func:`rsa.encrypt`
189    :param priv_key: the :py:class:`rsa.PrivateKey` to decrypt with.
190    :raise DecryptionError: when the decryption fails. No details are given as
191        to why the code thinks the decryption fails, as this would leak
192        information about the private key.
193
194
195    >>> import rsa
196    >>> (pub_key, priv_key) = rsa.newkeys(256)
197
198    It works with strings:
199
200    >>> crypto = encrypt(b'hello', pub_key)
201    >>> decrypt(crypto, priv_key)
202    b'hello'
203
204    And with binary data:
205
206    >>> crypto = encrypt(b'\x00\x00\x00\x00\x01', pub_key)
207    >>> decrypt(crypto, priv_key)
208    b'\x00\x00\x00\x00\x01'
209
210    Altering the encrypted information will *likely* cause a
211    :py:class:`rsa.pkcs1.DecryptionError`. If you want to be *sure*, use
212    :py:func:`rsa.sign`.
213
214
215    .. warning::
216
217        Never display the stack trace of a
218        :py:class:`rsa.pkcs1.DecryptionError` exception. It shows where in the
219        code the exception occurred, and thus leaks information about the key.
220        It's only a tiny bit of information, but every bit makes cracking the
221        keys easier.
222
223    >>> crypto = encrypt(b'hello', pub_key)
224    >>> crypto = crypto[0:5] + b'X' + crypto[6:] # change a byte
225    >>> decrypt(crypto, priv_key)
226    Traceback (most recent call last):
227    ...
228    rsa.pkcs1.DecryptionError: Decryption failed
229
230    """
231
232    blocksize = common.byte_size(priv_key.n)
233    encrypted = transform.bytes2int(crypto)
234    decrypted = priv_key.blinded_decrypt(encrypted)
235    cleartext = transform.int2bytes(decrypted, blocksize)
236
237    # If we can't find the cleartext marker, decryption failed.
238    if cleartext[0:2] != b'\x00\x02':
239        raise DecryptionError('Decryption failed')
240
241    # Find the 00 separator between the padding and the message
242    try:
243        sep_idx = cleartext.index(b'\x00', 2)
244    except ValueError:
245        raise DecryptionError('Decryption failed')
246
247    return cleartext[sep_idx + 1:]
248
249
250def sign_hash(hash_value, priv_key, hash_method):
251    """Signs a precomputed hash with the private key.
252
253    Hashes the message, then signs the hash with the given key. This is known
254    as a "detached signature", because the message itself isn't altered.
255
256    :param hash_value: A precomputed hash to sign (ignores message). Should be set to
257        None if needing to hash and sign message.
258    :param priv_key: the :py:class:`rsa.PrivateKey` to sign with
259    :param hash_method: the hash method used on the message. Use 'MD5', 'SHA-1',
260        'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'.
261    :return: a message signature block.
262    :raise OverflowError: if the private key is too small to contain the
263        requested hash.
264
265    """
266
267    # Get the ASN1 code for this hash method
268    if hash_method not in HASH_ASN1:
269        raise ValueError('Invalid hash method: %s' % hash_method)
270    asn1code = HASH_ASN1[hash_method]
271
272    # Encrypt the hash with the private key
273    cleartext = asn1code + hash_value
274    keylength = common.byte_size(priv_key.n)
275    padded = _pad_for_signing(cleartext, keylength)
276
277    payload = transform.bytes2int(padded)
278    encrypted = priv_key.blinded_encrypt(payload)
279    block = transform.int2bytes(encrypted, keylength)
280
281    return block
282
283
284def sign(message, priv_key, hash_method):
285    """Signs the message with the private key.
286
287    Hashes the message, then signs the hash with the given key. This is known
288    as a "detached signature", because the message itself isn't altered.
289
290    :param message: the message to sign. Can be an 8-bit string or a file-like
291        object. If ``message`` has a ``read()`` method, it is assumed to be a
292        file-like object.
293    :param priv_key: the :py:class:`rsa.PrivateKey` to sign with
294    :param hash_method: the hash method used on the message. Use 'MD5', 'SHA-1',
295        'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'.
296    :return: a message signature block.
297    :raise OverflowError: if the private key is too small to contain the
298        requested hash.
299
300    """
301
302    msg_hash = compute_hash(message, hash_method)
303    return sign_hash(msg_hash, priv_key, hash_method)
304
305
306def verify(message, signature, pub_key):
307    """Verifies that the signature matches the message.
308
309    The hash method is detected automatically from the signature.
310
311    :param message: the signed message. Can be an 8-bit string or a file-like
312        object. If ``message`` has a ``read()`` method, it is assumed to be a
313        file-like object.
314    :param signature: the signature block, as created with :py:func:`rsa.sign`.
315    :param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message.
316    :raise VerificationError: when the signature doesn't match the message.
317    :returns: the name of the used hash.
318
319    """
320
321    keylength = common.byte_size(pub_key.n)
322    encrypted = transform.bytes2int(signature)
323    decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n)
324    clearsig = transform.int2bytes(decrypted, keylength)
325
326    # Get the hash method
327    method_name = _find_method_hash(clearsig)
328    message_hash = compute_hash(message, method_name)
329
330    # Reconstruct the expected padded hash
331    cleartext = HASH_ASN1[method_name] + message_hash
332    expected = _pad_for_signing(cleartext, keylength)
333
334    # Compare with the signed one
335    if expected != clearsig:
336        raise VerificationError('Verification failed')
337
338    return method_name
339
340
341def find_signature_hash(signature, pub_key):
342    """Returns the hash name detected from the signature.
343
344    If you also want to verify the message, use :py:func:`rsa.verify()` instead.
345    It also returns the name of the used hash.
346
347    :param signature: the signature block, as created with :py:func:`rsa.sign`.
348    :param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message.
349    :returns: the name of the used hash.
350    """
351
352    keylength = common.byte_size(pub_key.n)
353    encrypted = transform.bytes2int(signature)
354    decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n)
355    clearsig = transform.int2bytes(decrypted, keylength)
356
357    return _find_method_hash(clearsig)
358
359
360def yield_fixedblocks(infile, blocksize):
361    """Generator, yields each block of ``blocksize`` bytes in the input file.
362
363    :param infile: file to read and separate in blocks.
364    :param blocksize: block size in bytes.
365    :returns: a generator that yields the contents of each block
366    """
367
368    while True:
369        block = infile.read(blocksize)
370
371        read_bytes = len(block)
372        if read_bytes == 0:
373            break
374
375        yield block
376
377        if read_bytes < blocksize:
378            break
379
380
381def compute_hash(message, method_name):
382    """Returns the message digest.
383
384    :param message: the signed message. Can be an 8-bit string or a file-like
385        object. If ``message`` has a ``read()`` method, it is assumed to be a
386        file-like object.
387    :param method_name: the hash method, must be a key of
388        :py:const:`HASH_METHODS`.
389
390    """
391
392    if method_name not in HASH_METHODS:
393        raise ValueError('Invalid hash method: %s' % method_name)
394
395    method = HASH_METHODS[method_name]
396    hasher = method()
397
398    if hasattr(message, 'read') and hasattr(message.read, '__call__'):
399        # read as 1K blocks
400        for block in yield_fixedblocks(message, 1024):
401            hasher.update(block)
402    else:
403        # hash the message object itself.
404        hasher.update(message)
405
406    return hasher.digest()
407
408
409def _find_method_hash(clearsig):
410    """Finds the hash method.
411
412    :param clearsig: full padded ASN1 and hash.
413    :return: the used hash method.
414    :raise VerificationFailed: when the hash method cannot be found
415    """
416
417    for (hashname, asn1code) in HASH_ASN1.items():
418        if asn1code in clearsig:
419            return hashname
420
421    raise VerificationError('Verification failed')
422
423
424__all__ = ['encrypt', 'decrypt', 'sign', 'verify',
425           'DecryptionError', 'VerificationError', 'CryptoError']
426
427if __name__ == '__main__':
428    print('Running doctests 1000x or until failure')
429    import doctest
430
431    for count in range(1000):
432        (failures, tests) = doctest.testmod()
433        if failures:
434            break
435
436        if count % 100 == 0 and count:
437            print('%i times' % count)
438
439    print('Doctests done')
440