diff options
Diffstat (limited to 'frozen_deps/Cryptodome/Cipher/PKCS1_v1_5.py')
-rw-r--r-- | frozen_deps/Cryptodome/Cipher/PKCS1_v1_5.py | 140 |
1 files changed, 79 insertions, 61 deletions
diff --git a/frozen_deps/Cryptodome/Cipher/PKCS1_v1_5.py b/frozen_deps/Cryptodome/Cipher/PKCS1_v1_5.py index 1fd1626..17ef9eb 100644 --- a/frozen_deps/Cryptodome/Cipher/PKCS1_v1_5.py +++ b/frozen_deps/Cryptodome/Cipher/PKCS1_v1_5.py @@ -20,12 +20,37 @@ # SOFTWARE. # =================================================================== -__all__ = [ 'new', 'PKCS115_Cipher' ] +__all__ = ['new', 'PKCS115_Cipher'] -from Cryptodome.Util.number import ceil_div, bytes_to_long, long_to_bytes -from Cryptodome.Util.py3compat import bord, _copy_bytes -import Cryptodome.Util.number from Cryptodome import Random +from Cryptodome.Util.number import bytes_to_long, long_to_bytes +from Cryptodome.Util.py3compat import bord, is_bytes, _copy_bytes + +from Cryptodome.Util._raw_api import (load_pycryptodome_raw_lib, c_size_t, + c_uint8_ptr) + + +_raw_pkcs1_decode = load_pycryptodome_raw_lib("Cryptodome.Cipher._pkcs1_decode", + """ + int pkcs1_decode(const uint8_t *em, size_t len_em, + const uint8_t *sentinel, size_t len_sentinel, + size_t expected_pt_len, + uint8_t *output); + """) + + +def _pkcs1_decode(em, sentinel, expected_pt_len, output): + if len(em) != len(output): + raise ValueError("Incorrect output length") + + ret = _raw_pkcs1_decode.pkcs1_decode(c_uint8_ptr(em), + c_size_t(len(em)), + c_uint8_ptr(sentinel), + c_size_t(len(sentinel)), + c_size_t(expected_pt_len), + c_uint8_ptr(output)) + return ret + class PKCS115_Cipher: """This cipher can perform PKCS#1 v1.5 RSA encryption or decryption. @@ -74,8 +99,7 @@ class PKCS115_Cipher: """ # See 7.2.1 in RFC8017 - modBits = Cryptodome.Util.number.size(self._key.n) - k = ceil_div(modBits,8) # Convert from bits to bytes + k = self._key.size_in_bytes() mLen = len(message) # Step 1 @@ -100,81 +124,76 @@ class PKCS115_Cipher: c = long_to_bytes(m_int, k) return c - def decrypt(self, ciphertext, sentinel): + def decrypt(self, ciphertext, sentinel, expected_pt_len=0): r"""Decrypt a PKCS#1 v1.5 ciphertext. - This function is named ``RSAES-PKCS1-V1_5-DECRYPT``, and is specified in + This is the function ``RSAES-PKCS1-V1_5-DECRYPT`` specified in `section 7.2.2 of RFC8017 <https://tools.ietf.org/html/rfc8017#page-29>`_. - :param ciphertext: + Args: + ciphertext (bytes/bytearray/memoryview): The ciphertext that contains the message to recover. - :type ciphertext: bytes/bytearray/memoryview - - :param sentinel: + sentinel (any type): The object to return whenever an error is detected. - :type sentinel: any type - - :Returns: A byte string. It is either the original message or the ``sentinel`` (in case of an error). + expected_pt_len (integer): + The length the plaintext is known to have, or 0 if unknown. - :Raises ValueError: - If the ciphertext length is incorrect - :Raises TypeError: - If the RSA key has no private half (i.e. it cannot be used for - decyption). + Returns (byte string): + It is either the original message or the ``sentinel`` (in case of an error). .. warning:: - You should **never** let the party who submitted the ciphertext know that - this function returned the ``sentinel`` value. - Armed with such knowledge (for a fair amount of carefully crafted but invalid ciphertexts), - an attacker is able to recontruct the plaintext of any other encryption that were carried out - with the same RSA public key (see `Bleichenbacher's`__ attack). - - In general, it should not be possible for the other party to distinguish - whether processing at the server side failed because the value returned - was a ``sentinel`` as opposed to a random, invalid message. - - In fact, the second option is not that unlikely: encryption done according to PKCS#1 v1.5 - embeds no good integrity check. There is roughly one chance - in 2\ :sup:`16` for a random ciphertext to be returned as a valid message - (although random looking). - - It is therefore advisabled to: - - 1. Select as ``sentinel`` a value that resembles a plausable random, invalid message. - 2. Not report back an error as soon as you detect a ``sentinel`` value. - Put differently, you should not explicitly check if the returned value is the ``sentinel`` or not. - 3. Cover all possible errors with a single, generic error indicator. - 4. Embed into the definition of ``message`` (at the protocol level) a digest (e.g. ``SHA-1``). - It is recommended for it to be the rightmost part ``message``. - 5. Where possible, monitor the number of errors due to ciphertexts originating from the same party, - and slow down the rate of the requests from such party (or even blacklist it altogether). - - **If you are designing a new protocol, consider using the more robust PKCS#1 OAEP.** - - .. __: http://www.bell-labs.com/user/bleichen/papers/pkcs.ps - + PKCS#1 v1.5 decryption is intrinsically vulnerable to timing + attacks (see `Bleichenbacher's`__ attack). + **Use PKCS#1 OAEP instead**. + + This implementation attempts to mitigate the risk + with some constant-time constructs. + However, they are not sufficient by themselves: the type of protocol you + implement and the way you handle errors make a big difference. + + Specifically, you should make it very hard for the (malicious) + party that submitted the ciphertext to quickly understand if decryption + succeeded or not. + + To this end, it is recommended that your protocol only encrypts + plaintexts of fixed length (``expected_pt_len``), + that ``sentinel`` is a random byte string of the same length, + and that processing continues for as long + as possible even if ``sentinel`` is returned (i.e. in case of + incorrect decryption). + + .. __: https://dx.doi.org/10.1007/BFb0055716 """ - # See 7.2.1 in RFC3447 - modBits = Cryptodome.Util.number.size(self._key.n) - k = ceil_div(modBits,8) # Convert from bits to bytes + # See 7.2.2 in RFC8017 + k = self._key.size_in_bytes() # Step 1 if len(ciphertext) != k: - raise ValueError("Ciphertext with incorrect length.") + raise ValueError("Ciphertext with incorrect length (not %d bytes)" % k) + # Step 2a (O2SIP) ct_int = bytes_to_long(ciphertext) + # Step 2b (RSADP) m_int = self._key._decrypt(ct_int) + # Complete step 2c (I2OSP) em = long_to_bytes(m_int, k) - # Step 3 - sep = em.find(b'\x00', 2) - if not em.startswith(b'\x00\x02') or sep < 10: - return sentinel - # Step 4 - return em[sep + 1:] + + # Step 3 (not constant time when the sentinel is not a byte string) + output = bytes(bytearray(k)) + if not is_bytes(sentinel) or len(sentinel) > k: + size = _pkcs1_decode(em, b'', expected_pt_len, output) + if size < 0: + return sentinel + else: + return output[size:] + + # Step 3 (somewhat constant time) + size = _pkcs1_decode(em, sentinel, expected_pt_len, output) + return output[size:] def new(key, randfunc=None): @@ -196,4 +215,3 @@ def new(key, randfunc=None): if randfunc is None: randfunc = Random.get_random_bytes return PKCS115_Cipher(key, randfunc) - |