190 lines
6.9 KiB
Python
190 lines
6.9 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
#
|
||
|
# Cipher/PKCS1-v1_5.py : PKCS#1 v1.5
|
||
|
#
|
||
|
# ===================================================================
|
||
|
# The contents of this file are dedicated to the public domain. To
|
||
|
# the extent that dedication to the public domain is not available,
|
||
|
# everyone is granted a worldwide, perpetual, royalty-free,
|
||
|
# non-exclusive license to exercise all rights associated with the
|
||
|
# contents of this file for any purpose whatsoever.
|
||
|
# No rights are reserved.
|
||
|
#
|
||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||
|
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||
|
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||
|
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||
|
# SOFTWARE.
|
||
|
# ===================================================================
|
||
|
|
||
|
__all__ = ['new', 'PKCS115_Cipher']
|
||
|
|
||
|
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 ._pkcs1_oaep_decode import pkcs1_decode
|
||
|
|
||
|
|
||
|
class PKCS115_Cipher:
|
||
|
"""This cipher can perform PKCS#1 v1.5 RSA encryption or decryption.
|
||
|
Do not instantiate directly. Use :func:`Cryptodome.Cipher.PKCS1_v1_5.new` instead."""
|
||
|
|
||
|
def __init__(self, key, randfunc):
|
||
|
"""Initialize this PKCS#1 v1.5 cipher object.
|
||
|
|
||
|
:Parameters:
|
||
|
key : an RSA key object
|
||
|
If a private half is given, both encryption and decryption are possible.
|
||
|
If a public half is given, only encryption is possible.
|
||
|
randfunc : callable
|
||
|
Function that returns random bytes.
|
||
|
"""
|
||
|
|
||
|
self._key = key
|
||
|
self._randfunc = randfunc
|
||
|
|
||
|
def can_encrypt(self):
|
||
|
"""Return True if this cipher object can be used for encryption."""
|
||
|
return self._key.can_encrypt()
|
||
|
|
||
|
def can_decrypt(self):
|
||
|
"""Return True if this cipher object can be used for decryption."""
|
||
|
return self._key.can_decrypt()
|
||
|
|
||
|
def encrypt(self, message):
|
||
|
"""Produce the PKCS#1 v1.5 encryption of a message.
|
||
|
|
||
|
This function is named ``RSAES-PKCS1-V1_5-ENCRYPT``, and it is specified in
|
||
|
`section 7.2.1 of RFC8017
|
||
|
<https://tools.ietf.org/html/rfc8017#page-28>`_.
|
||
|
|
||
|
:param message:
|
||
|
The message to encrypt, also known as plaintext. It can be of
|
||
|
variable length, but not longer than the RSA modulus (in bytes) minus 11.
|
||
|
:type message: bytes/bytearray/memoryview
|
||
|
|
||
|
:Returns: A byte string, the ciphertext in which the message is encrypted.
|
||
|
It is as long as the RSA modulus (in bytes).
|
||
|
|
||
|
:Raises ValueError:
|
||
|
If the RSA key length is not sufficiently long to deal with the given
|
||
|
message.
|
||
|
"""
|
||
|
|
||
|
# See 7.2.1 in RFC8017
|
||
|
k = self._key.size_in_bytes()
|
||
|
mLen = len(message)
|
||
|
|
||
|
# Step 1
|
||
|
if mLen > k - 11:
|
||
|
raise ValueError("Plaintext is too long.")
|
||
|
# Step 2a
|
||
|
ps = []
|
||
|
while len(ps) != k - mLen - 3:
|
||
|
new_byte = self._randfunc(1)
|
||
|
if bord(new_byte[0]) == 0x00:
|
||
|
continue
|
||
|
ps.append(new_byte)
|
||
|
ps = b"".join(ps)
|
||
|
# Step 2b
|
||
|
em = b'\x00\x02' + ps + b'\x00' + _copy_bytes(None, None, message)
|
||
|
# Step 3a (OS2IP)
|
||
|
em_int = bytes_to_long(em)
|
||
|
# Step 3b (RSAEP)
|
||
|
m_int = self._key._encrypt(em_int)
|
||
|
# Step 3c (I2OSP)
|
||
|
c = long_to_bytes(m_int, k)
|
||
|
return c
|
||
|
|
||
|
def decrypt(self, ciphertext, sentinel, expected_pt_len=0):
|
||
|
r"""Decrypt a PKCS#1 v1.5 ciphertext.
|
||
|
|
||
|
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>`_.
|
||
|
|
||
|
Args:
|
||
|
ciphertext (bytes/bytearray/memoryview):
|
||
|
The ciphertext that contains the message to recover.
|
||
|
sentinel (any type):
|
||
|
The object to return whenever an error is detected.
|
||
|
expected_pt_len (integer):
|
||
|
The length the plaintext is known to have, or 0 if unknown.
|
||
|
|
||
|
Returns (byte string):
|
||
|
It is either the original message or the ``sentinel`` (in case of an error).
|
||
|
|
||
|
.. warning::
|
||
|
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.2 in RFC8017
|
||
|
k = self._key.size_in_bytes()
|
||
|
|
||
|
# Step 1
|
||
|
if len(ciphertext) != k:
|
||
|
raise ValueError("Ciphertext with incorrect length (not %d bytes)" % k)
|
||
|
|
||
|
# Step 2a (O2SIP)
|
||
|
ct_int = bytes_to_long(ciphertext)
|
||
|
|
||
|
# Step 2b (RSADP) and Step 2c (I2OSP)
|
||
|
em = self._key._decrypt_to_bytes(ct_int)
|
||
|
|
||
|
# 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):
|
||
|
"""Create a cipher for performing PKCS#1 v1.5 encryption or decryption.
|
||
|
|
||
|
:param key:
|
||
|
The key to use to encrypt or decrypt the message. This is a `Cryptodome.PublicKey.RSA` object.
|
||
|
Decryption is only possible if *key* is a private RSA key.
|
||
|
:type key: RSA key object
|
||
|
|
||
|
:param randfunc:
|
||
|
Function that return random bytes.
|
||
|
The default is :func:`Cryptodome.Random.get_random_bytes`.
|
||
|
:type randfunc: callable
|
||
|
|
||
|
:returns: A cipher object `PKCS115_Cipher`.
|
||
|
"""
|
||
|
|
||
|
if randfunc is None:
|
||
|
randfunc = Random.get_random_bytes
|
||
|
return PKCS115_Cipher(key, randfunc)
|