/ adafruit_rsa / pkcs1.py
pkcs1.py
  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  import os
 31  import adafruit_hashlib as hashlib
 32  from adafruit_rsa import common, transform, core
 33  
 34  __version__ = "0.0.0-auto.0"
 35  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_RSA.git"
 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\x04\x10",
 40      "SHA-1": b"\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14",
 41      "SHA-224": b"\x30\x2d\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04\x05\x00\x04\x1c",
 42      "SHA-256": b"\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20",
 43      "SHA-384": b"\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30",
 44      "SHA-512": b"\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40",
 45  }
 46  
 47  HASH_METHODS = {
 48      "MD5": hashlib.md5,
 49      "SHA-1": hashlib.sha1,
 50      "SHA-224": hashlib.sha224,
 51      "SHA-256": hashlib.sha256,
 52      "SHA-384": hashlib.sha384,
 53      "SHA-512": hashlib.sha512,
 54  }
 55  
 56  
 57  class CryptoError(Exception):
 58      """Base class for all exceptions in this module."""
 59  
 60  
 61  class DecryptionError(CryptoError):
 62      """Raised when decryption fails."""
 63  
 64  
 65  class VerificationError(CryptoError):
 66      """Raised when verification fails."""
 67  
 68  
 69  def _pad_for_encryption(message, target_length):
 70      r"""Pads the message for encryption, returning the padded message.
 71  
 72      :return: 00 02 RANDOM_DATA 00 MESSAGE
 73  
 74      >>> block = _pad_for_encryption(b'hello', 16)
 75      >>> len(block)
 76      16
 77      >>> block[0:2]
 78      b'\x00\x02'
 79      >>> block[-6:]
 80      b'\x00hello'
 81  
 82      """
 83  
 84      max_msglength = target_length - 11
 85      msglength = len(message)
 86  
 87      if msglength > max_msglength:
 88          raise OverflowError(
 89              "%i bytes needed for message, but there is only"
 90              " space for %i" % (msglength, max_msglength)
 91          )
 92  
 93      # Get random padding
 94      padding = b""
 95      padding_length = target_length - msglength - 3
 96  
 97      # We remove 0-bytes, so we'll end up with less padding than we've asked for,
 98      # so keep adding data until we're at the correct length.
 99      while len(padding) < padding_length:
100          needed_bytes = padding_length - len(padding)
101  
102          # Always read at least 8 bytes more than we need, and trim off the rest
103          # after removing the 0-bytes. This increases the chance of getting
104          # enough bytes, especially when needed_bytes is small
105          new_padding = os.urandom(needed_bytes + 5)
106          new_padding = new_padding.replace(b"\x00", b"")
107          padding = padding + new_padding[:needed_bytes]
108  
109      assert len(padding) == padding_length
110  
111      return b"".join([b"\x00\x02", padding, b"\x00", message])
112  
113  
114  def _pad_for_signing(message, target_length):
115      r"""Pads the message for signing, returning the padded message.
116  
117      The padding is always a repetition of FF bytes.
118  
119      :return: 00 01 PADDING 00 MESSAGE
120  
121      >>> block = _pad_for_signing(b'hello', 16)
122      >>> len(block)
123      16
124      >>> block[0:2]
125      b'\x00\x01'
126      >>> block[-6:]
127      b'\x00hello'
128      >>> block[2:-6]
129      b'\xff\xff\xff\xff\xff\xff\xff\xff'
130  
131      """
132  
133      max_msglength = target_length - 11
134      msglength = len(message)
135  
136      if msglength > max_msglength:
137          raise OverflowError(
138              "%i bytes needed for message, but there is only"
139              " space for %i" % (msglength, max_msglength)
140          )
141  
142      padding_length = target_length - msglength - 3
143  
144      return b"".join([b"\x00\x01", padding_length * b"\xff", b"\x00", 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:`adafruit_rsaPublicKey` to encrypt with.
154      :raise OverflowError: when the message is too large to fit in the padded
155          block.
156  
157      >>> from adafruit_rsa.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      payload = transform.bytes2int(padded)
172      encrypted = core.encrypt_int(payload, pub_key.e, pub_key.n)
173      block = transform.int2bytes(encrypted, keylength)
174  
175      return block
176  
177  
178  def decrypt(crypto, priv_key):
179      """Decrypts the given message using PKCS#1 v1.5
180  
181      The decryption is considered 'failed' when the resulting cleartext doesn't
182      start with the bytes 00 02, or when the 00 byte between the padding and
183      the message cannot be found.
184  
185      :param crypto: the crypto text as returned by :py:func:`adafruit_rsaencrypt`
186      :param priv_key: the :py:class:`adafruit_rsaPrivateKey` to decrypt with.
187      :raise DecryptionError: when the decryption fails. No details are given as
188          to why the code thinks the decryption fails, as this would leak
189          information about the private key.
190  
191  
192      >>> import adafruit_rsa.rsa
193      >>> (pub_key, priv_key) = adafruit_rsanewkeys(256)
194  
195      It works with strings:
196  
197      >>> crypto = encrypt(b'hello', pub_key)
198      >>> decrypt(crypto, priv_key)
199      b'hello'
200  
201      And with binary data:
202  
203      >>> crypto = encrypt(b'\x00\x00\x00\x00\x01', pub_key)
204      >>> decrypt(crypto, priv_key)
205      b'\x00\x00\x00\x00\x01'
206  
207      Altering the encrypted information will *likely* cause a
208      :py:class:`adafruit_rsapkcs1.DecryptionError`. If you want to be *sure*, use
209      :py:func:`adafruit_rsasign`.
210  
211  
212      .. warning::
213  
214          Never display the stack trace of a
215          :py:class:`adafruit_rsapkcs1.DecryptionError` exception. It shows where in the
216          code the exception occurred, and thus leaks information about the key.
217          It's only a tiny bit of information, but every bit makes cracking the
218          keys easier.
219  
220      >>> crypto = encrypt(b'hello', pub_key)
221      >>> crypto = crypto[0:5] + b'X' + crypto[6:] # change a byte
222      >>> decrypt(crypto, priv_key)
223      Traceback (most recent call last):
224      ...
225      adafruit_rsapkcs1.DecryptionError: Decryption failed
226  
227      """
228  
229      blocksize = common.byte_size(priv_key.n)
230      encrypted = transform.bytes2int(crypto)
231      decrypted = priv_key.blinded_decrypt(encrypted)
232      cleartext = transform.int2bytes(decrypted, blocksize)
233  
234      # Find the 00 separator between the padding and the message
235      try:
236          sep_idx = cleartext.index(b"\x00", 2)
237      except ValueError:
238          raise DecryptionError("Decryption failed")
239  
240      return cleartext[sep_idx + 1 :]
241  
242  
243  def sign_hash(hash_value, priv_key, hash_method):
244      """Signs a precomputed hash with the private key.
245  
246      Hashes the message, then signs the hash with the given key. This is known
247      as a "detached signature", because the message itself isn't altered.
248  
249      :param hash_value: A precomputed hash to sign (ignores message). Should be set to
250          None if needing to hash and sign message.
251      :param priv_key: the :py:class:`adafruit_rsaPrivateKey` to sign with
252      :param hash_method: the hash method used on the message. Use 'MD5', 'SHA-1',
253          'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'.
254      :return: a message signature block.
255      :raise OverflowError: if the private key is too small to contain the
256          requested hash.
257  
258      """
259  
260      # Get the ASN1 code for this hash method
261      if hash_method not in HASH_ASN1:
262          raise ValueError("Invalid hash method: %s" % hash_method)
263      asn1code = HASH_ASN1[hash_method]
264  
265      # Encrypt the hash with the private key
266      cleartext = asn1code + hash_value
267      keylength = common.byte_size(priv_key.n)
268      padded = _pad_for_signing(cleartext, keylength)
269  
270      payload = transform.bytes2int(padded)
271      encrypted = priv_key.blinded_encrypt(payload)
272      block = transform.int2bytes(encrypted, keylength)
273  
274      return block
275  
276  
277  def sign(message, priv_key, hash_method):
278      """Signs the message with the private key.
279  
280      Hashes the message, then signs the hash with the given key. This is known
281      as a "detached signature", because the message itself isn't altered.
282  
283      :param message: the message to sign. 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 priv_key: the :py:class:`adafruit_rsaPrivateKey` to sign with
287      :param hash_method: the hash method used on the message. Use 'MD5', 'SHA-1',
288          'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'.
289      :return: a message signature block.
290      :raise OverflowError: if the private key is too small to contain the
291          requested hash.
292  
293      """
294  
295      msg_hash = compute_hash(message, hash_method)
296      return sign_hash(msg_hash, priv_key, hash_method)
297  
298  
299  def verify(message, signature, pub_key):
300      """Verifies that the signature matches the message.
301  
302      The hash method is detected automatically from the signature.
303  
304      :param message: the signed message. Can be an 8-bit string or a file-like
305          object. If ``message`` has a ``read()`` method, it is assumed to be a
306          file-like object.
307      :param signature: the signature block, as created with :py:func:`rsa.sign`.
308      :param pub_key: the :py:class:`adafruit_rsaPublicKey` of the person signing the message.
309      :raise VerificationError: when the signature doesn't match the message.
310      :returns: the name of the used hash.
311  
312      """
313  
314      keylength = common.byte_size(pub_key.n)
315      encrypted = transform.bytes2int(signature)
316      decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n)
317      clearsig = transform.int2bytes(decrypted, keylength)
318  
319      # Get the hash method
320      method_name = _find_method_hash(clearsig)
321      message_hash = compute_hash(message, method_name)
322  
323      # Reconstruct the expected padded hash
324      cleartext = HASH_ASN1[method_name] + message_hash
325      expected = _pad_for_signing(cleartext, keylength)
326  
327      # Compare with the signed one
328      if expected != clearsig:
329          raise VerificationError("Verification failed")
330  
331      return method_name
332  
333  
334  def find_signature_hash(signature, pub_key):
335      """Returns the hash name detected from the signature.
336  
337      If you also want to verify the message, use :py:func:`adafruit_rsaverify()` instead.
338      It also returns the name of the used hash.
339  
340      :param signature: the signature block, as created with :py:func:`adafruit_rsasign`.
341      :param pub_key: the :py:class:`adafruit_rsaPublicKey` of the person signing the message.
342      :returns: the name of the used hash.
343      """
344  
345      keylength = common.byte_size(pub_key.n)
346      encrypted = transform.bytes2int(signature)
347      decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n)
348      clearsig = transform.int2bytes(decrypted, keylength)
349  
350      return _find_method_hash(clearsig)
351  
352  
353  def yield_fixedblocks(infile, blocksize):
354      """Generator, yields each block of ``blocksize`` bytes in the input file.
355  
356      :param infile: file to read and separate in blocks.
357      :param blocksize: block size in bytes.
358      :returns: a generator that yields the contents of each block
359      """
360  
361      while True:
362          block = infile.read(blocksize)
363  
364          read_bytes = len(block)
365          if read_bytes == 0:
366              break
367  
368          yield block
369  
370          if read_bytes < blocksize:
371              break
372  
373  
374  def compute_hash(message, method_name):
375      """Returns the message digest.
376  
377      :param message: the signed message. Can be an 8-bit string or a file-like
378          object. If ``message`` has a ``read()`` method, it is assumed to be a
379          file-like object.
380      :param method_name: the hash method, must be a key of
381          :py:const:`HASH_METHODS`.
382  
383      """
384  
385      if method_name not in HASH_METHODS:
386          raise ValueError("Invalid or unsupported hash method: %s" % method_name)
387  
388      method = HASH_METHODS[method_name]
389      hasher = method()
390  
391      if hasattr(message, "read") and hasattr(message.read, "__call__"):
392          # read as 1K blocks
393          for block in yield_fixedblocks(message, 1024):
394              hasher.update(block)
395      else:
396          # hash the message object itself.
397          hasher.update(message)
398  
399      return hasher.digest()
400  
401  
402  def _find_method_hash(clearsig):
403      """Finds the hash method.
404  
405      :param clearsig: full padded ASN1 and hash.
406      :return: the used hash method.
407      :raise VerificationFailed: when the hash method cannot be found
408      """
409  
410      for (hashname, asn1code) in HASH_ASN1.items():
411          if asn1code in clearsig:
412              return hashname
413  
414      raise VerificationError("Verification failed")
415  
416  
417  __all__ = [
418      "encrypt",
419      "decrypt",
420      "sign",
421      "verify",
422      "DecryptionError",
423      "VerificationError",
424      "CryptoError",
425  ]