aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Gaynor <alex.gaynor@gmail.com>2014-04-21 12:53:47 -0700
committerAlex Gaynor <alex.gaynor@gmail.com>2014-04-21 12:53:47 -0700
commit30752cdde9c149ede7c3eec5aea4e72944d99ac4 (patch)
tree46bad2831d88981e1232ff831721a33924a85076
parent30bb5941489c7a0b1c24ca546e8f253c97a3a318 (diff)
parent8e764396471beb13d0cdfbc9a299b9445f96abb2 (diff)
downloadcryptography-30752cdde9c149ede7c3eec5aea4e72944d99ac4.tar.gz
cryptography-30752cdde9c149ede7c3eec5aea4e72944d99ac4.tar.bz2
cryptography-30752cdde9c149ede7c3eec5aea4e72944d99ac4.zip
Merge pull request #888 from reaperhulk/rsa-decrypt
RSA PKCS1v15 Decryption Support
-rw-r--r--cryptography/hazmat/backends/interfaces.py6
-rw-r--r--cryptography/hazmat/backends/openssl/backend.py81
-rw-r--r--cryptography/hazmat/bindings/openssl/err.py2
-rw-r--r--cryptography/hazmat/primitives/asymmetric/rsa.py9
-rw-r--r--docs/hazmat/backends/interfaces.rst12
-rw-r--r--docs/hazmat/primitives/asymmetric/padding.rst6
-rw-r--r--docs/hazmat/primitives/asymmetric/rsa.rst34
-rw-r--r--docs/hazmat/primitives/interfaces.rst18
-rw-r--r--tests/hazmat/backends/test_openssl.py4
-rw-r--r--tests/hazmat/primitives/test_rsa.py95
10 files changed, 261 insertions, 6 deletions
diff --git a/cryptography/hazmat/backends/interfaces.py b/cryptography/hazmat/backends/interfaces.py
index 92413d8c..677f4c67 100644
--- a/cryptography/hazmat/backends/interfaces.py
+++ b/cryptography/hazmat/backends/interfaces.py
@@ -117,6 +117,12 @@ class RSABackend(object):
Return True if the hash algorithm is supported for MGF1 in PSS.
"""
+ @abc.abstractmethod
+ def decrypt_rsa(self, private_key, ciphertext, padding):
+ """
+ Returns decrypted bytes.
+ """
+
@six.add_metaclass(abc.ABCMeta)
class DSABackend(object):
diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py
index 86fa704b..5e13bfc1 100644
--- a/cryptography/hazmat/backends/openssl/backend.py
+++ b/cryptography/hazmat/backends/openssl/backend.py
@@ -473,6 +473,87 @@ class Backend(object):
y=self._bn_to_int(ctx.pub_key)
)
+ def decrypt_rsa(self, private_key, ciphertext, padding):
+ if isinstance(padding, PKCS1v15):
+ padding_enum = self._lib.RSA_PKCS1_PADDING
+ else:
+ raise UnsupportedAlgorithm(
+ "{0} is not supported by this backend".format(
+ padding.name
+ ),
+ _Reasons.UNSUPPORTED_PADDING
+ )
+
+ key_size_bytes = int(math.ceil(private_key.key_size / 8.0))
+ if key_size_bytes < len(ciphertext):
+ raise ValueError("Ciphertext too large for key size")
+
+ if self._lib.Cryptography_HAS_PKEY_CTX:
+ return self._decrypt_rsa_pkey_ctx(private_key, ciphertext,
+ padding_enum)
+ else:
+ return self._decrypt_rsa_098(private_key, ciphertext, padding_enum)
+
+ def _decrypt_rsa_pkey_ctx(self, private_key, ciphertext, padding_enum):
+ evp_pkey = self._rsa_private_key_to_evp_pkey(private_key)
+ pkey_ctx = self._lib.EVP_PKEY_CTX_new(
+ evp_pkey, self._ffi.NULL
+ )
+ assert pkey_ctx != self._ffi.NULL
+ pkey_ctx = self._ffi.gc(pkey_ctx, self._lib.EVP_PKEY_CTX_free)
+ res = self._lib.EVP_PKEY_decrypt_init(pkey_ctx)
+ assert res == 1
+ res = self._lib.EVP_PKEY_CTX_set_rsa_padding(
+ pkey_ctx, padding_enum)
+ assert res > 0
+ buf_size = self._lib.EVP_PKEY_size(evp_pkey)
+ assert buf_size > 0
+ outlen = self._ffi.new("size_t *", buf_size)
+ buf = self._ffi.new("char[]", buf_size)
+ res = self._lib.Cryptography_EVP_PKEY_decrypt(
+ pkey_ctx,
+ buf,
+ outlen,
+ ciphertext,
+ len(ciphertext)
+ )
+ if res <= 0:
+ errors = self._consume_errors()
+ assert errors
+ assert errors[0].lib == self._lib.ERR_LIB_RSA
+ assert (
+ errors[0].reason == self._lib.RSA_R_BLOCK_TYPE_IS_NOT_01 or
+ errors[0].reason == self._lib.RSA_R_BLOCK_TYPE_IS_NOT_02
+ )
+ raise ValueError("Decryption failed")
+
+ return self._ffi.buffer(buf)[:outlen[0]]
+
+ def _decrypt_rsa_098(self, private_key, ciphertext, padding_enum):
+ rsa_cdata = self._rsa_cdata_from_private_key(private_key)
+ rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free)
+ key_size = self._lib.RSA_size(rsa_cdata)
+ assert key_size > 0
+ buf = self._ffi.new("unsigned char[]", key_size)
+ res = self._lib.RSA_private_decrypt(
+ len(ciphertext),
+ ciphertext,
+ buf,
+ rsa_cdata,
+ padding_enum
+ )
+ if res < 0:
+ errors = self._consume_errors()
+ assert errors
+ assert errors[0].lib == self._lib.ERR_LIB_RSA
+ assert (
+ errors[0].reason == self._lib.RSA_R_BLOCK_TYPE_IS_NOT_01 or
+ errors[0].reason == self._lib.RSA_R_BLOCK_TYPE_IS_NOT_02
+ )
+ raise ValueError("Decryption failed")
+
+ return self._ffi.buffer(buf)[:res]
+
class GetCipherByName(object):
def __init__(self, fmt):
diff --git a/cryptography/hazmat/bindings/openssl/err.py b/cryptography/hazmat/bindings/openssl/err.py
index f51393aa..c08c880c 100644
--- a/cryptography/hazmat/bindings/openssl/err.py
+++ b/cryptography/hazmat/bindings/openssl/err.py
@@ -216,6 +216,8 @@ static const int PEM_R_UNSUPPORTED_ENCRYPTION;
static const int RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE;
static const int RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY;
+static const int RSA_R_BLOCK_TYPE_IS_NOT_01;
+static const int RSA_R_BLOCK_TYPE_IS_NOT_02;
"""
FUNCTIONS = """
diff --git a/cryptography/hazmat/primitives/asymmetric/rsa.py b/cryptography/hazmat/primitives/asymmetric/rsa.py
index 5b15350a..cffd4e98 100644
--- a/cryptography/hazmat/primitives/asymmetric/rsa.py
+++ b/cryptography/hazmat/primitives/asymmetric/rsa.py
@@ -189,6 +189,15 @@ class RSAPrivateKey(object):
return backend.create_rsa_signature_ctx(self, padding, algorithm)
+ def decrypt(self, ciphertext, padding, backend):
+ if not isinstance(backend, RSABackend):
+ raise UnsupportedAlgorithm(
+ "Backend object does not implement RSABackend",
+ _Reasons.BACKEND_MISSING_INTERFACE
+ )
+
+ return backend.decrypt_rsa(self, ciphertext, padding)
+
@property
def key_size(self):
return utils.bit_length(self.modulus)
diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst
index 394d060b..71cd4564 100644
--- a/docs/hazmat/backends/interfaces.rst
+++ b/docs/hazmat/backends/interfaces.rst
@@ -263,6 +263,18 @@ A specific ``backend`` may provide one or more of these interfaces.
:returns: ``True`` if the specified ``algorithm`` is supported by this
backend, otherwise ``False``.
+ .. method:: decrypt_rsa(private_key, ciphertext, padding)
+
+ :param private_key: An instance of an
+ :class:`~cryptography.hazmat.primitives.interfaces.RSAPrivateKey`
+ provider.
+
+ :param bytes ciphertext: The ciphertext to decrypt.
+
+ :param padding: An instance of an
+ :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricPadding`
+ provider.
+
.. class:: OpenSSLSerializationBackend
diff --git a/docs/hazmat/primitives/asymmetric/padding.rst b/docs/hazmat/primitives/asymmetric/padding.rst
index 89af7eaa..f33ca4e2 100644
--- a/docs/hazmat/primitives/asymmetric/padding.rst
+++ b/docs/hazmat/primitives/asymmetric/padding.rst
@@ -19,7 +19,8 @@ Padding
PSS (Probabilistic Signature Scheme) is a signature scheme defined in
:rfc:`3447`. It is more complex than PKCS1 but possesses a `security proof`_.
- This is the `recommended padding algorithm`_ for RSA signatures.
+ This is the `recommended padding algorithm`_ for RSA signatures. It cannot
+ be used with RSA encryption.
:param mgf: A mask generation function object. At this time the only
supported MGF is :class:`MGF1`.
@@ -37,7 +38,8 @@ Padding
.. versionadded:: 0.3
PKCS1 v1.5 (also known as simply PKCS1) is a simple padding scheme
- developed for use with RSA keys. It is defined in :rfc:`3447`.
+ developed for use with RSA keys. It is defined in :rfc:`3447`. This padding
+ can be used for signing and encryption.
Mask generation functions
~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/hazmat/primitives/asymmetric/rsa.rst b/docs/hazmat/primitives/asymmetric/rsa.rst
index c9de2831..c282d9ef 100644
--- a/docs/hazmat/primitives/asymmetric/rsa.rst
+++ b/docs/hazmat/primitives/asymmetric/rsa.rst
@@ -116,6 +116,36 @@ RSA
:raises ValueError: This is raised when the chosen hash algorithm is
too large for the key size.
+ .. method:: decrypt(ciphertext, padding, backend)
+
+ .. versionadded:: 0.4
+
+ Decrypt data that was encrypted with the public key.
+
+ :param bytes ciphertext: The ciphertext to decrypt.
+
+ :param padding: An instance of a
+ :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricPadding`
+ provider.
+
+ :param backend: A
+ :class:`~cryptography.hazmat.backends.interfaces.RSABackend`
+ provider.
+
+ :return bytes: Decrypted data.
+
+ :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if
+ the provided ``backend`` does not implement
+ :class:`~cryptography.hazmat.backends.interfaces.RSABackend` or if
+ the backend does not support the chosen hash or padding algorithm.
+
+ :raises TypeError: This is raised when the padding is not an
+ :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricPadding`
+ provider.
+
+ :raises ValueError: This is raised when decryption fails or the chosen
+ hash algorithm is too large for the key size.
+
.. class:: RSAPublicKey(public_exponent, modulus)
@@ -221,7 +251,7 @@ If you are trying to load RSA private keys yourself you may find that not all
parameters required by ``RSAPrivateKey`` are available. In particular the
`Chinese Remainder Theorem`_ (CRT) values ``dmp1``, ``dmq1``, ``iqmp`` may be
missing or present in a different form. For example `OpenPGP`_ does not include
-the ``iqmp``, ``dmp1`` or ``dmq1`` parameters.
+the ``iqmp``, ``dmp1`` or ``dmq1`` parameters.
The following functions are provided for users who want to work with keys like
this without having to do the math themselves.
@@ -241,7 +271,7 @@ this without having to do the math themselves.
``p``.
.. function:: rsa_crt_dmq1(private_exponent, q)
-
+
.. versionadded:: 0.4
Generates the ``dmq1`` parameter from the RSA private exponent and prime
diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst
index 95fd6f9f..3b837a0d 100644
--- a/docs/hazmat/primitives/interfaces.rst
+++ b/docs/hazmat/primitives/interfaces.rst
@@ -133,6 +133,24 @@ Asymmetric interfaces
:returns:
:class:`~cryptography.hazmat.primitives.interfaces.AsymmetricSignatureContext`
+ .. method:: decrypt(ciphertext, padding, backend)
+
+ .. versionadded:: 0.4
+
+ Decrypt data that was encrypted via the public key.
+
+ :param bytes ciphertext: The ciphertext to decrypt.
+
+ :param padding: An instance of a
+ :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricPadding`
+ provider.
+
+ :param backend: A
+ :class:`~cryptography.hazmat.backends.interfaces.RSABackend`
+ provider.
+
+ :return bytes: Decrypted data.
+
.. method:: public_key()
:return: :class:`~cryptography.hazmat.primitives.interfaces.RSAPublicKey`
diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py
index 43d28c33..c589506f 100644
--- a/tests/hazmat/backends/test_openssl.py
+++ b/tests/hazmat/backends/test_openssl.py
@@ -143,8 +143,8 @@ class TestOpenSSL(object):
with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH):
backend.derive_pbkdf2_hmac(hashes.SHA256(), 10, b"", 1000, b"")
- # This test is not in the next class because to check if it's really
- # default we don't want to run the setup_method before it
+ # This test is not in the TestOpenSSLRandomEngine class because to check
+ # if it's really default we don't want to run the setup_method before it
def test_osrandom_engine_is_default(self):
e = backend._lib.ENGINE_get_default_RAND()
name = backend._lib.ENGINE_get_name(e)
diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py
index 84d0f805..a5266d57 100644
--- a/tests/hazmat/primitives/test_rsa.py
+++ b/tests/hazmat/primitives/test_rsa.py
@@ -1225,3 +1225,98 @@ class TestMGF1(object):
mgf = padding.MGF1(algorithm, padding.MGF1.MAX_LENGTH)
assert mgf._algorithm == algorithm
assert mgf._salt_length == padding.MGF1.MAX_LENGTH
+
+
+@pytest.mark.rsa
+class TestRSADecryption(object):
+ @pytest.mark.parametrize(
+ "vector",
+ _flatten_pkcs1_examples(load_vectors_from_file(
+ os.path.join(
+ "asymmetric", "RSA", "pkcs1v15crypt-vectors.txt"),
+ load_pkcs1_vectors
+ ))
+ )
+ def test_decrypt_pkcs1v15_vectors(self, vector, backend):
+ private, public, example = vector
+ skey = rsa.RSAPrivateKey(
+ p=private["p"],
+ q=private["q"],
+ private_exponent=private["private_exponent"],
+ dmp1=private["dmp1"],
+ dmq1=private["dmq1"],
+ iqmp=private["iqmp"],
+ public_exponent=private["public_exponent"],
+ modulus=private["modulus"]
+ )
+ ciphertext = binascii.unhexlify(example["encryption"])
+ assert len(ciphertext) == math.ceil(skey.key_size / 8.0)
+ message = skey.decrypt(
+ ciphertext,
+ padding.PKCS1v15(),
+ backend
+ )
+ assert message == binascii.unhexlify(example["message"])
+
+ def test_unsupported_padding(self, backend):
+ private_key = rsa.RSAPrivateKey.generate(
+ public_exponent=65537,
+ key_size=512,
+ backend=backend
+ )
+ with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING):
+ private_key.decrypt(b"somedata", DummyPadding(), backend)
+
+ def test_decrypt_invalid_decrypt(self, backend):
+ private_key = rsa.RSAPrivateKey.generate(
+ public_exponent=65537,
+ key_size=512,
+ backend=backend
+ )
+ with pytest.raises(ValueError):
+ private_key.decrypt(
+ b"\x00" * 64,
+ padding.PKCS1v15(),
+ backend
+ )
+
+ def test_decrypt_ciphertext_too_large(self, backend):
+ private_key = rsa.RSAPrivateKey.generate(
+ public_exponent=65537,
+ key_size=512,
+ backend=backend
+ )
+ with pytest.raises(ValueError):
+ private_key.decrypt(
+ b"\x00" * 65,
+ padding.PKCS1v15(),
+ backend
+ )
+
+ def test_decrypt_ciphertext_too_small(self, backend):
+ private_key = rsa.RSAPrivateKey.generate(
+ public_exponent=65537,
+ key_size=512,
+ backend=backend
+ )
+ ct = binascii.unhexlify(
+ b"50b4c14136bd198c2f3c3ed243fce036e168d56517984a263cd66492b80804f1"
+ b"69d210f2b9bdfb48b12f9ea05009c77da257cc600ccefe3a6283789d8ea0"
+ )
+ with pytest.raises(ValueError):
+ private_key.decrypt(
+ ct,
+ padding.PKCS1v15(),
+ backend
+ )
+
+ def test_rsa_decrypt_invalid_backend(self, backend):
+ pretend_backend = object()
+ private_key = rsa.RSAPrivateKey.generate(65537, 2048, backend)
+
+ with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE):
+ private_key.decrypt(
+ b"irrelevant",
+ padding.PKCS1v15(),
+ pretend_backend
+ )