aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Kehrer <paul.l.kehrer@gmail.com>2014-04-05 19:51:00 -0500
committerPaul Kehrer <paul.l.kehrer@gmail.com>2014-04-20 16:53:02 -0500
commit4c0a374dd90cd48c21267e4d8be1ddef8288b29c (patch)
treee78af314d7d64e9eb00a624465cbeedbc37dd469
parent16b953a22abf2092f6d428f04141f3e5c9513ce9 (diff)
downloadcryptography-4c0a374dd90cd48c21267e4d8be1ddef8288b29c.tar.gz
cryptography-4c0a374dd90cd48c21267e4d8be1ddef8288b29c.tar.bz2
cryptography-4c0a374dd90cd48c21267e4d8be1ddef8288b29c.zip
docs, tests, general huge improvements to RSA decryption
-rw-r--r--cryptography/hazmat/backends/interfaces.py2
-rw-r--r--cryptography/hazmat/backends/openssl/backend.py24
-rw-r--r--cryptography/hazmat/primitives/asymmetric/padding.py7
-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.rst22
-rw-r--r--tests/hazmat/backends/test_openssl.py55
-rw-r--r--tests/hazmat/primitives/test_rsa.py70
8 files changed, 178 insertions, 23 deletions
diff --git a/cryptography/hazmat/backends/interfaces.py b/cryptography/hazmat/backends/interfaces.py
index c5c5a16e..677f4c67 100644
--- a/cryptography/hazmat/backends/interfaces.py
+++ b/cryptography/hazmat/backends/interfaces.py
@@ -118,7 +118,7 @@ class RSABackend(object):
"""
@abc.abstractmethod
- def rsa_decrypt(self, private_key, ciphertext, padding):
+ def decrypt_rsa(self, private_key, ciphertext, padding):
"""
Returns decrypted bytes.
"""
diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py
index 2965c781..ca898dfd 100644
--- a/cryptography/hazmat/backends/openssl/backend.py
+++ b/cryptography/hazmat/backends/openssl/backend.py
@@ -473,14 +473,15 @@ class Backend(object):
y=self._bn_to_int(ctx.pub_key)
)
- def rsa_decrypt(self, private_key, ciphertext, padding):
+ def decrypt_rsa(self, private_key, ciphertext, padding):
if isinstance(padding, PKCS1v15):
padding_enum = self._lib.RSA_PKCS1_PADDING
elif isinstance(padding, OAEP):
padding_enum = self._lib.RSA_PKCS1_OAEP_PADDING
if not isinstance(padding._mgf, MGF1):
raise UnsupportedAlgorithm(
- "Only MGF1 is supported by this backend"
+ "Only MGF1 is supported by this backend",
+ _Reasons.UNSUPPORTED_MGF
)
if not isinstance(padding._mgf._algorithm, hashes.SHA1):
@@ -489,6 +490,16 @@ class Backend(object):
"using OAEP",
_Reasons.UNSUPPORTED_HASH
)
+
+ if padding._label is not None and padding._label != b"":
+ raise ValueError("This backend does not support OAEP labels")
+
+ if not isinstance(padding._algorithm, hashes.SHA1):
+ raise UnsupportedAlgorithm(
+ "This backend only supports SHA1 when using OAEP",
+ _Reasons.UNSUPPORTED_HASH
+ )
+
else:
raise UnsupportedAlgorithm(
"{0} is not supported by this backend".format(
@@ -519,16 +530,17 @@ class Backend(object):
ciphertext,
len(ciphertext)
)
- assert res >= 0
- if res == 0:
+ if res <= 0:
errors = self._consume_errors()
assert errors
- raise SystemError # TODO
+ raise self._unknown_error(errors[0]) # TODO
return self._ffi.buffer(buf)[:outlen[0]]
else:
rsa_cdata = self._rsa_cdata_from_private_key(private_key)
rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free)
+ res = self._lib.RSA_blinding_on(rsa_cdata, self._ffi.NULL)
+ assert res == 1
key_size = self._lib.RSA_size(rsa_cdata)
assert key_size > 0
buf = self._ffi.new("unsigned char[]", key_size)
@@ -542,7 +554,7 @@ class Backend(object):
if res < 0:
errors = self._consume_errors()
assert errors
- raise SystemError # TODO
+ raise self._unknown_error(errors[0]) # TODO
return self._ffi.buffer(buf)[:res]
diff --git a/cryptography/hazmat/primitives/asymmetric/padding.py b/cryptography/hazmat/primitives/asymmetric/padding.py
index 899fed17..9755dbcf 100644
--- a/cryptography/hazmat/primitives/asymmetric/padding.py
+++ b/cryptography/hazmat/primitives/asymmetric/padding.py
@@ -58,8 +58,13 @@ class PSS(object):
class OAEP(object):
name = "EME-OAEP"
- def __init__(self, mgf):
+ def __init__(self, mgf, algorithm, label):
self._mgf = mgf
+ if not isinstance(algorithm, interfaces.HashAlgorithm):
+ raise TypeError("Expected instance of interfaces.HashAlgorithm.")
+
+ self._algorithm = algorithm
+ self._label = label
class MGF1(object):
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..0c2f7ce7 100644
--- a/docs/hazmat/primitives/asymmetric/padding.rst
+++ b/docs/hazmat/primitives/asymmetric/padding.rst
@@ -10,6 +10,21 @@ Padding
correct padding signatures can be forged, messages decrypted, and private
keys compromised.
+.. class:: OAEP(mgf, label)
+
+ .. versionadded:: 0.4
+
+ OAEP (Optimal Asymmetric Encryption Padding) is a padding scheme defined in
+ :rfc:`3447`. It provides probabilistic encryption and is `proven secure`_
+ against several attack types. This is the `recommended padding algorithm`_
+ for RSA encryption. It cannot be used with RSA signing.
+
+ :param mgf: A mask generation function object. At this time the only
+ supported MGF is :class:`MGF1`.
+
+ :param bytes label: A label to apply. This is a rarely used field and many
+ backends do not support it.
+
.. class:: PSS(mgf, salt_length)
.. versionadded:: 0.3
@@ -19,7 +34,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 +53,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
~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -58,5 +75,6 @@ Mask generation functions
.. _`Padding is critical`: http://rdist.root.org/2009/10/06/why-rsa-encryption-padding-is-critical/
+.. _`proven secure`: http://cseweb.ucsd.edu/users/mihir/papers/oae.pdf
.. _`security proof`: http://eprint.iacr.org/2001/062.pdf
.. _`recommended padding algorithm`: http://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html
diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py
index 43d28c33..46feae46 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)
@@ -291,3 +291,54 @@ class TestOpenSSLRSA(object):
def test_unsupported_mgf1_hash_algorithm(self):
assert backend.mgf1_hash_supported(DummyHash()) is False
+
+ def test_unsupported_mgf1_hash_algorithm_decrypt(self):
+ private_key = rsa.RSAPrivateKey.generate(
+ public_exponent=65537,
+ key_size=512,
+ backend=backend
+ )
+ with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH):
+ private_key.decrypt(
+ b"ciphertext",
+ padding.OAEP(
+ mgf=padding.MGF1(algorithm=hashes.SHA256()),
+ algorithm=hashes.SHA1(),
+ label=None
+ ),
+ backend
+ )
+
+ def test_unsupported_oaep_hash_algorithm_decrypt(self):
+ private_key = rsa.RSAPrivateKey.generate(
+ public_exponent=65537,
+ key_size=512,
+ backend=backend
+ )
+ with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH):
+ private_key.decrypt(
+ b"ciphertext",
+ padding.OAEP(
+ mgf=padding.MGF1(algorithm=hashes.SHA1()),
+ algorithm=hashes.SHA256(),
+ label=None
+ ),
+ backend
+ )
+
+ def test_unsupported_oaep_label_decrypt(self):
+ private_key = rsa.RSAPrivateKey.generate(
+ public_exponent=65537,
+ key_size=512,
+ backend=backend
+ )
+ with pytest.raises(ValueError):
+ private_key.decrypt(
+ b"ciphertext",
+ padding.OAEP(
+ mgf=padding.MGF1(algorithm=hashes.SHA1()),
+ algorithm=hashes.SHA1(),
+ label=b"label"
+ ),
+ backend
+ )
diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py
index 70ae20dc..7b658b69 100644
--- a/tests/hazmat/primitives/test_rsa.py
+++ b/tests/hazmat/primitives/test_rsa.py
@@ -1227,6 +1227,17 @@ class TestMGF1(object):
assert mgf._salt_length == padding.MGF1.MAX_LENGTH
+class TestOAEP(object):
+ def test_invalid_algorithm(self):
+ mgf = padding.MGF1(hashes.SHA1())
+ with pytest.raises(TypeError):
+ padding.OAEP(
+ mgf=mgf,
+ algorithm=b"",
+ label=None
+ )
+
+
@pytest.mark.rsa
class TestRSADecryption(object):
@pytest.mark.parametrize(
@@ -1249,16 +1260,14 @@ class TestRSADecryption(object):
public_exponent=private["public_exponent"],
modulus=private["modulus"]
)
- message = backend.rsa_decrypt(
- skey,
+ message = skey.decrypt(
binascii.unhexlify(example["encryption"]),
- # TODO: handle MGF1 here
padding.OAEP(
- padding.MGF1(
- algorithm=hashes.SHA1(),
- salt_length=padding.MGF1.MAX_LENGTH
- )
- )
+ mgf=padding.MGF1(algorithm=hashes.SHA1()),
+ algorithm=hashes.SHA1(),
+ label=None
+ ),
+ backend
)
assert message == binascii.unhexlify(example["message"])
@@ -1282,9 +1291,48 @@ class TestRSADecryption(object):
public_exponent=private["public_exponent"],
modulus=private["modulus"]
)
- message = backend.rsa_decrypt(
- skey,
+ message = skey.decrypt(
binascii.unhexlify(example["encryption"]),
- padding.PKCS1v15()
+ 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_unsupported_oaep_mgf(self, backend):
+ private_key = rsa.RSAPrivateKey.generate(
+ public_exponent=65537,
+ key_size=512,
+ backend=backend
+ )
+ with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF):
+ private_key.decrypt(
+ b"ciphertext",
+ padding.OAEP(
+ mgf=DummyMGF(),
+ algorithm=hashes.SHA1(),
+ label=None
+ ),
+ backend
+ )
+
+ def test_decrypt_invalid_decrypt(self, backend):
+ private_key = rsa.RSAPrivateKey.generate(
+ public_exponent=65537,
+ key_size=512,
+ backend=backend
+ )
+ with pytest.raises(exceptions.InternalError):
+ private_key.decrypt(
+ b"\x00" * 64,
+ padding.PKCS1v15(),
+ backend
+ )