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__': |
|
|
|
|
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
|
|
|
|