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