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