codekingpro/portable-devtools
114k
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 