Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(284)

Side by Side Diff: third_party/google-endpoints/rsa/pkcs1.py

Issue 2666783008: Add google-endpoints to third_party/. (Closed)
Patch Set: Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « third_party/google-endpoints/rsa/pem.py ('k') | third_party/google-endpoints/rsa/prime.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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')
OLDNEW
« no previous file with comments | « third_party/google-endpoints/rsa/pem.py ('k') | third_party/google-endpoints/rsa/prime.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698