aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.coveragerc4
-rw-r--r--cryptography/bindings/openssl/api.py115
-rw-r--r--cryptography/bindings/openssl/evp.py5
-rw-r--r--cryptography/bindings/openssl/hmac.py32
-rw-r--r--cryptography/primitives/block/base.py66
-rw-r--r--cryptography/primitives/block/ciphers.py24
-rw-r--r--cryptography/primitives/block/modes.py15
-rw-r--r--cryptography/primitives/hashes.py8
-rw-r--r--cryptography/primitives/interfaces.py21
-rw-r--r--docs/primitives/cryptographic-hashes.rst4
-rw-r--r--docs/primitives/symmetric-encryption.rst53
-rw-r--r--setup.py5
-rw-r--r--tests/bindings/test_openssl.py10
-rw-r--r--tests/primitives/test_block.py56
-rw-r--r--tests/primitives/test_ciphers.py17
-rw-r--r--tests/primitives/test_cryptrec.py4
-rw-r--r--tests/primitives/test_hashes.py15
-rw-r--r--tests/primitives/test_nist.py90
-rw-r--r--tests/primitives/test_openssl_vectors.py16
-rw-r--r--tests/primitives/test_utils.py5
-rw-r--r--tests/primitives/utils.py13
-rw-r--r--tests/test_utils.py10
-rw-r--r--tox.ini6
23 files changed, 475 insertions, 119 deletions
diff --git a/.coveragerc b/.coveragerc
index 398ff08a..b891cb7c 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,2 +1,6 @@
[run]
branch = True
+
+[report]
+exclude_lines =
+ @abc.abstractmethod
diff --git a/cryptography/bindings/openssl/api.py b/cryptography/bindings/openssl/api.py
index f5e042e7..fedaf9cc 100644
--- a/cryptography/bindings/openssl/api.py
+++ b/cryptography/bindings/openssl/api.py
@@ -13,11 +13,23 @@
from __future__ import absolute_import, division, print_function
+import itertools
import sys
import cffi
from cryptography.primitives import interfaces
+from cryptography.primitives.block.ciphers import AES, Camellia, TripleDES
+from cryptography.primitives.block.modes import CBC, CTR, ECB, OFB, CFB
+
+
+class GetCipherByName(object):
+ def __init__(self, fmt):
+ self._fmt = fmt
+
+ def __call__(self, api, cipher, mode):
+ cipher_name = self._fmt.format(cipher=cipher, mode=mode).lower()
+ return api.lib.EVP_get_cipherbyname(cipher_name.encode("ascii"))
class API(object):
@@ -35,6 +47,7 @@ class API(object):
"engine",
"err",
"evp",
+ "hmac",
"nid",
"opensslv",
"pem",
@@ -86,6 +99,9 @@ class API(object):
self.lib.OpenSSL_add_all_algorithms()
self.lib.SSL_load_error_strings()
+ self._cipher_registry = {}
+ self._register_default_ciphers()
+
def openssl_version_text(self):
"""
Friendly string name of linked OpenSSL.
@@ -94,18 +110,64 @@ class API(object):
"""
return self.ffi.string(self.lib.OPENSSL_VERSION_TEXT).decode("ascii")
- def supports_cipher(self, ciphername):
- return (self.ffi.NULL !=
- self.lib.EVP_get_cipherbyname(ciphername.encode("ascii")))
+ def supports_cipher(self, cipher, mode):
+ try:
+ adapter = self._cipher_registry[type(cipher), type(mode)]
+ except KeyError:
+ return False
+ evp_cipher = adapter(self, cipher, mode)
+ return self.ffi.NULL != evp_cipher
+
+ def register_cipher_adapter(self, cipher_cls, mode_cls, adapter):
+ if (cipher_cls, mode_cls) in self._cipher_registry:
+ raise ValueError("Duplicate registration for: {0} {1}".format(
+ cipher_cls, mode_cls)
+ )
+ self._cipher_registry[cipher_cls, mode_cls] = adapter
+
+ def _register_default_ciphers(self):
+ for cipher_cls, mode_cls in itertools.product(
+ [AES, Camellia],
+ [CBC, CTR, ECB, OFB, CFB],
+ ):
+ self.register_cipher_adapter(
+ cipher_cls,
+ mode_cls,
+ GetCipherByName("{cipher.name}-{cipher.key_size}-{mode.name}")
+ )
+ for mode_cls in [CBC, CFB, OFB]:
+ self.register_cipher_adapter(
+ TripleDES,
+ mode_cls,
+ GetCipherByName("des-ede3-{mode.name}")
+ )
+
+ def create_block_cipher_encrypt_context(self, cipher, mode):
+ ctx, evp, iv_nonce = self._create_block_cipher_context(cipher, mode)
+ res = self.lib.EVP_EncryptInit_ex(ctx, evp, api.ffi.NULL, cipher.key,
+ iv_nonce)
+ assert res != 0
+ # We purposely disable padding here as it's handled higher up in the
+ # API.
+ self.lib.EVP_CIPHER_CTX_set_padding(ctx, 0)
+ return ctx
- def create_block_cipher_context(self, cipher, mode):
+ def create_block_cipher_decrypt_context(self, cipher, mode):
+ ctx, evp, iv_nonce = self._create_block_cipher_context(cipher, mode)
+ res = self.lib.EVP_DecryptInit_ex(ctx, evp, api.ffi.NULL, cipher.key,
+ iv_nonce)
+ assert res != 0
+ # We purposely disable padding here as it's handled higher up in the
+ # API.
+ self.lib.EVP_CIPHER_CTX_set_padding(ctx, 0)
+ return ctx
+
+ def _create_block_cipher_context(self, cipher, mode):
ctx = self.lib.EVP_CIPHER_CTX_new()
ctx = self.ffi.gc(ctx, self.lib.EVP_CIPHER_CTX_free)
- # TODO: compute name using a better algorithm
- ciphername = "{0}-{1}-{2}".format(
- cipher.name, cipher.key_size, mode.name
- ).lower()
- evp_cipher = self.lib.EVP_get_cipherbyname(ciphername.encode("ascii"))
+ evp_cipher = self._cipher_registry[type(cipher), type(mode)](
+ self, cipher, mode
+ )
assert evp_cipher != self.ffi.NULL
if isinstance(mode, interfaces.ModeWithInitializationVector):
iv_nonce = mode.initialization_vector
@@ -114,24 +176,21 @@ class API(object):
else:
iv_nonce = self.ffi.NULL
- # TODO: Sometimes this needs to be a DecryptInit, when?
- res = self.lib.EVP_EncryptInit_ex(
- ctx, evp_cipher, self.ffi.NULL, cipher.key, iv_nonce
- )
- assert res != 0
+ return (ctx, evp_cipher, iv_nonce)
- # We purposely disable padding here as it's handled higher up in the
- # API.
- self.lib.EVP_CIPHER_CTX_set_padding(ctx, 0)
- return ctx
+ def update_encrypt_context(self, ctx, data):
+ block_size = self.lib.EVP_CIPHER_CTX_block_size(ctx)
+ buf = self.ffi.new("unsigned char[]", len(data) + block_size - 1)
+ outlen = self.ffi.new("int *")
+ res = self.lib.EVP_EncryptUpdate(ctx, buf, outlen, data, len(data))
+ assert res != 0
+ return self.ffi.buffer(buf)[:outlen[0]]
- def update_encrypt_context(self, ctx, plaintext):
+ def update_decrypt_context(self, ctx, data):
block_size = self.lib.EVP_CIPHER_CTX_block_size(ctx)
- buf = self.ffi.new("unsigned char[]", len(plaintext) + block_size - 1)
+ buf = self.ffi.new("unsigned char[]", len(data) + block_size - 1)
outlen = self.ffi.new("int *")
- res = self.lib.EVP_EncryptUpdate(
- ctx, buf, outlen, plaintext, len(plaintext)
- )
+ res = self.lib.EVP_DecryptUpdate(ctx, buf, outlen, data, len(data))
assert res != 0
return self.ffi.buffer(buf)[:outlen[0]]
@@ -145,6 +204,16 @@ class API(object):
assert res == 1
return self.ffi.buffer(buf)[:outlen[0]]
+ def finalize_decrypt_context(self, ctx):
+ block_size = self.lib.EVP_CIPHER_CTX_block_size(ctx)
+ buf = self.ffi.new("unsigned char[]", block_size)
+ outlen = self.ffi.new("int *")
+ res = self.lib.EVP_DecryptFinal_ex(ctx, buf, outlen)
+ assert res != 0
+ res = self.lib.EVP_CIPHER_CTX_cleanup(ctx)
+ assert res == 1
+ return self.ffi.buffer(buf)[:outlen[0]]
+
def supports_hash(self, hash_cls):
return (self.ffi.NULL !=
self.lib.EVP_get_digestbyname(hash_cls.name.encode("ascii")))
diff --git a/cryptography/bindings/openssl/evp.py b/cryptography/bindings/openssl/evp.py
index 2bb5b0f7..41df1056 100644
--- a/cryptography/bindings/openssl/evp.py
+++ b/cryptography/bindings/openssl/evp.py
@@ -41,6 +41,11 @@ int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *, int);
int EVP_EncryptUpdate(EVP_CIPHER_CTX *, unsigned char *, int *,
const unsigned char *, int);
int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *, unsigned char *, int *);
+int EVP_DecryptInit_ex(EVP_CIPHER_CTX *, const EVP_CIPHER *, ENGINE *,
+ const unsigned char *, const unsigned char *);
+int EVP_DecryptUpdate(EVP_CIPHER_CTX *, unsigned char *, int *,
+ const unsigned char *, int);
+int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *, unsigned char *, int *);
int EVP_CIPHER_CTX_cleanup(EVP_CIPHER_CTX *);
const EVP_CIPHER *EVP_CIPHER_CTX_cipher(const EVP_CIPHER_CTX *);
int EVP_CIPHER_block_size(const EVP_CIPHER *);
diff --git a/cryptography/bindings/openssl/hmac.py b/cryptography/bindings/openssl/hmac.py
new file mode 100644
index 00000000..e97ac35e
--- /dev/null
+++ b/cryptography/bindings/openssl/hmac.py
@@ -0,0 +1,32 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+INCLUDES = """
+#include <openssl/hmac.h>
+"""
+
+TYPES = """
+typedef struct { ...; } HMAC_CTX;
+"""
+
+FUNCTIONS = """
+void HMAC_CTX_init(HMAC_CTX *);
+void HMAC_CTX_cleanup(HMAC_CTX *);
+int HMAC_Init_ex(HMAC_CTX *, const void *, int, const EVP_MD *, ENGINE *);
+int HMAC_Update(HMAC_CTX *, const unsigned char *, size_t);
+int HMAC_Final(HMAC_CTX *, unsigned char *, unsigned int *);
+int HMAC_CTX_copy(HMAC_CTX *, HMAC_CTX *);
+"""
+
+MACROS = """
+"""
diff --git a/cryptography/primitives/block/base.py b/cryptography/primitives/block/base.py
index 50e9e9e5..12b6f626 100644
--- a/cryptography/primitives/block/base.py
+++ b/cryptography/primitives/block/base.py
@@ -13,14 +13,7 @@
from __future__ import absolute_import, division, print_function
-from enum import Enum
-
-from cryptography.bindings import _default_api
-
-
-class _Operation(Enum):
- encrypt = 0
- decrypt = 1
+from cryptography.primitives import interfaces
class BlockCipher(object):
@@ -28,35 +21,54 @@ class BlockCipher(object):
super(BlockCipher, self).__init__()
if api is None:
- api = _default_api
+ from cryptography.bindings import _default_api as api
self.cipher = cipher
self.mode = mode
self._api = api
- self._ctx = api.create_block_cipher_context(cipher, mode)
- self._operation = None
- def encrypt(self, plaintext):
- if self._ctx is None:
- raise ValueError("BlockCipher was already finalized")
+ def encryptor(self):
+ return _CipherEncryptionContext(self.cipher, self.mode, self._api)
- if self._operation is None:
- self._operation = _Operation.encrypt
- elif self._operation is not _Operation.encrypt:
- raise ValueError("BlockCipher cannot encrypt when the operation is"
- " set to %s" % self._operation.name)
+ def decryptor(self):
+ return _CipherDecryptionContext(self.cipher, self.mode, self._api)
- return self._api.update_encrypt_context(self._ctx, plaintext)
+
+@interfaces.register(interfaces.CipherContext)
+class _CipherEncryptionContext(object):
+ def __init__(self, cipher, mode, api):
+ super(_CipherEncryptionContext, self).__init__()
+ self._api = api
+ self._ctx = self._api.create_block_cipher_encrypt_context(cipher, mode)
+
+ def update(self, data):
+ if self._ctx is None:
+ raise ValueError("Context was already finalized")
+ return self._api.update_encrypt_context(self._ctx, data)
def finalize(self):
if self._ctx is None:
- raise ValueError("BlockCipher was already finalized")
+ raise ValueError("Context was already finalized")
+ data = self._api.finalize_encrypt_context(self._ctx)
+ self._ctx = None
+ return data
+
- if self._operation is _Operation.encrypt:
- result = self._api.finalize_encrypt_context(self._ctx)
- else:
- raise ValueError("BlockCipher cannot finalize the unknown "
- "operation %s" % self._operation.name)
+@interfaces.register(interfaces.CipherContext)
+class _CipherDecryptionContext(object):
+ def __init__(self, cipher, mode, api):
+ super(_CipherDecryptionContext, self).__init__()
+ self._api = api
+ self._ctx = self._api.create_block_cipher_decrypt_context(cipher, mode)
+ def update(self, data):
+ if self._ctx is None:
+ raise ValueError("Context was already finalized")
+ return self._api.update_decrypt_context(self._ctx, data)
+
+ def finalize(self):
+ if self._ctx is None:
+ raise ValueError("Context was already finalized")
+ data = self._api.finalize_decrypt_context(self._ctx)
self._ctx = None
- return result
+ return data
diff --git a/cryptography/primitives/block/ciphers.py b/cryptography/primitives/block/ciphers.py
index 4ac150a4..4143b89d 100644
--- a/cryptography/primitives/block/ciphers.py
+++ b/cryptography/primitives/block/ciphers.py
@@ -52,3 +52,27 @@ class Camellia(object):
@property
def key_size(self):
return len(self.key) * 8
+
+
+class TripleDES(object):
+ name = "3DES"
+ block_size = 64
+ key_sizes = frozenset([64, 128, 192])
+
+ def __init__(self, key):
+ super(TripleDES, self).__init__()
+ if len(key) == 8:
+ key += key + key
+ elif len(key) == 16:
+ key += key[:8]
+ self.key = key
+
+ # Verify that the key size matches the expected key size
+ if self.key_size not in self.key_sizes:
+ raise ValueError("Invalid key size ({0}) for {1}".format(
+ self.key_size, self.name
+ ))
+
+ @property
+ def key_size(self):
+ return len(self.key) * 8
diff --git a/cryptography/primitives/block/modes.py b/cryptography/primitives/block/modes.py
index 43631801..a933c187 100644
--- a/cryptography/primitives/block/modes.py
+++ b/cryptography/primitives/block/modes.py
@@ -16,14 +16,7 @@ from __future__ import absolute_import, division, print_function
from cryptography.primitives import interfaces
-def register(iface):
- def register_decorator(klass):
- iface.register(klass)
- return klass
- return register_decorator
-
-
-@register(interfaces.ModeWithInitializationVector)
+@interfaces.register(interfaces.ModeWithInitializationVector)
class CBC(object):
name = "CBC"
@@ -36,7 +29,7 @@ class ECB(object):
name = "ECB"
-@register(interfaces.ModeWithInitializationVector)
+@interfaces.register(interfaces.ModeWithInitializationVector)
class OFB(object):
name = "OFB"
@@ -45,7 +38,7 @@ class OFB(object):
self.initialization_vector = initialization_vector
-@register(interfaces.ModeWithInitializationVector)
+@interfaces.register(interfaces.ModeWithInitializationVector)
class CFB(object):
name = "CFB"
@@ -54,7 +47,7 @@ class CFB(object):
self.initialization_vector = initialization_vector
-@register(interfaces.ModeWithNonce)
+@interfaces.register(interfaces.ModeWithNonce)
class CTR(object):
name = "CTR"
diff --git a/cryptography/primitives/hashes.py b/cryptography/primitives/hashes.py
index e8c1f929..7133a916 100644
--- a/cryptography/primitives/hashes.py
+++ b/cryptography/primitives/hashes.py
@@ -23,13 +23,17 @@ from cryptography.bindings import _default_api
class BaseHash(six.with_metaclass(abc.ABCMeta)):
- def __init__(self, api=None, ctx=None):
+ def __init__(self, data=None, api=None, ctx=None):
if api is None:
api = _default_api
self._api = api
self._ctx = self._api.create_hash_context(self) if ctx is None else ctx
+ if data is not None:
+ self.update(data)
def update(self, data):
+ if isinstance(data, six.text_type):
+ raise TypeError("Unicode-objects must be encoded before hashing")
self._api.update_hash_context(self._ctx, data)
def copy(self):
@@ -40,7 +44,7 @@ class BaseHash(six.with_metaclass(abc.ABCMeta)):
self.digest_size)
def hexdigest(self):
- return binascii.hexlify(self.digest()).decode("ascii")
+ return str(binascii.hexlify(self.digest()).decode("ascii"))
def _copy_ctx(self):
return self._api.copy_hash_context(self._ctx)
diff --git a/cryptography/primitives/interfaces.py b/cryptography/primitives/interfaces.py
index c1fc9910..49c19d0e 100644
--- a/cryptography/primitives/interfaces.py
+++ b/cryptography/primitives/interfaces.py
@@ -18,9 +18,30 @@ import abc
import six
+def register(iface):
+ def register_decorator(klass):
+ iface.register(klass)
+ return klass
+ return register_decorator
+
+
class ModeWithInitializationVector(six.with_metaclass(abc.ABCMeta)):
pass
class ModeWithNonce(six.with_metaclass(abc.ABCMeta)):
pass
+
+
+class CipherContext(six.with_metaclass(abc.ABCMeta)):
+ @abc.abstractmethod
+ def update(self, data):
+ """
+ update takes bytes and return bytes
+ """
+
+ @abc.abstractmethod
+ def finalize(self):
+ """
+ finalize return bytes
+ """
diff --git a/docs/primitives/cryptographic-hashes.rst b/docs/primitives/cryptographic-hashes.rst
index d4dde042..aeb30f40 100644
--- a/docs/primitives/cryptographic-hashes.rst
+++ b/docs/primitives/cryptographic-hashes.rst
@@ -1,11 +1,13 @@
Message Digests
===============
-.. class:: cryptography.primitives.hashes.BaseHash
+.. class:: cryptography.primitives.hashes.BaseHash(data=None)
Abstract base class that implements a common interface for all hash
algorithms that follow here.
+ If ``data`` is provided ``update(data)`` is called upon construction.
+
.. method:: update(data)
:param bytes data: The bytes you wish to hash.
diff --git a/docs/primitives/symmetric-encryption.rst b/docs/primitives/symmetric-encryption.rst
index 7899e67d..73d8ad37 100644
--- a/docs/primitives/symmetric-encryption.rst
+++ b/docs/primitives/symmetric-encryption.rst
@@ -15,29 +15,47 @@ where the encrypter and decrypter both use the same key.
Block ciphers work by encrypting content in chunks, often 64- or 128-bits.
They combine an underlying algorithm (such as AES), with a mode (such as
- CBC, CTR, or GCM). A simple example of encrypting content with AES is:
+ CBC, CTR, or GCM). A simple example of encrypting (and then decrypting)
+ content with AES is:
.. doctest::
>>> from cryptography.primitives.block import BlockCipher, ciphers, modes
>>> cipher = BlockCipher(ciphers.AES(key), modes.CBC(iv))
- >>> cipher.encrypt(b"a secret message") + cipher.finalize()
- '...'
+ >>> encryptor = cipher.encryptor()
+ >>> ct = encryptor.update(b"a secret message") + encryptor.finalize()
+ >>> decryptor = cipher.decryptor()
+ >>> decryptor.update(ct) + decryptor.finalize()
+ 'a secret message'
:param cipher: One of the ciphers described below.
:param mode: One of the modes described below.
- ``encrypt()`` should be called repeatedly with new plaintext, and once the
- full plaintext is fed in, ``finalize()`` should be called.
+ .. method:: encryptor()
- .. method:: encrypt(plaintext)
+ :return :class:`CipherContext`: encryption instance
- :param bytes plaintext: The text you wish to encrypt.
- :return bytes: Returns the ciphertext that was added.
+ .. method:: decryptor()
+
+ :return :class:`CipherContext`: decryption instance
+
+.. class:: cryptography.primitives.interfaces.CipherContext()
+
+ When calling ``encryptor()`` or ``decryptor()`` on a BlockCipher object you
+ will receive a return object conforming to the CipherContext interface. You
+ can then call ``update(data)`` with data until you have fed everything into
+ the context. Once that is done call ``finalize()`` to finish the operation and
+ obtain the remainder of the data.
+
+
+ .. method:: update(data)
+
+ :param bytes data: The text you wish to pass into the context.
+ :return bytes: Returns the data that was encrypted or decrypted.
.. method:: finalize()
- :return bytes: Returns the remainder of the ciphertext.
+ :return bytes: Returns the remainder of the data.
Ciphers
~~~~~~~
@@ -61,6 +79,23 @@ Ciphers
This must be kept secret.
+.. class:: cryptography.primitives.block.ciphers.TripleDES(key)
+
+ Triple DES (Data Encryption Standard), sometimes refered to as 3DES, is a
+ block cipher standardized by NIST. Triple DES has known cryptoanalytic
+ flaws, however none of them currently enable a practical attack.
+ Nonetheless, Triples DES is not reccomended for new applications because it
+ is incredibly slow; old applications should consider moving away from it.
+
+ :param bytes key: The secret key, either ``64``, ``128``, or ``192`` bits
+ (note that DES functionally uses ``56``, ``112``, or
+ ``168`` bits of the key, there is a parity byte in each
+ component of the key), in some materials these are
+ referred to as being up to three separate keys (each
+ ``56`` bits long), they can simply be concatenated to
+ produce the full key. This must be kept secret.
+
+
Modes
~~~~~
diff --git a/setup.py b/setup.py
index cbbf100a..1856cadb 100644
--- a/setup.py
+++ b/setup.py
@@ -10,8 +10,6 @@
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-import sys
-
from setuptools import setup, find_packages
@@ -32,9 +30,6 @@ setup_requires = [
CFFI_DEPENDENCY,
]
-if sys.version_info[:2] < (3, 4):
- install_requires += ["enum34"]
-
setup(
name=about["__title__"],
version=about["__version__"],
diff --git a/tests/bindings/test_openssl.py b/tests/bindings/test_openssl.py
index e5b78d18..bf201e4d 100644
--- a/tests/bindings/test_openssl.py
+++ b/tests/bindings/test_openssl.py
@@ -11,7 +11,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import pytest
+
from cryptography.bindings.openssl.api import api
+from cryptography.primitives.block.ciphers import AES
+from cryptography.primitives.block.modes import CBC
class TestOpenSSL(object):
@@ -30,4 +34,8 @@ class TestOpenSSL(object):
assert api.openssl_version_text().startswith("OpenSSL")
def test_supports_cipher(self):
- assert api.supports_cipher("not-a-real-cipher") is False
+ assert api.supports_cipher(None, None) is False
+
+ def test_register_duplicate_cipher_adapter(self):
+ with pytest.raises(ValueError):
+ api.register_cipher_adapter(AES, CBC, None)
diff --git a/tests/primitives/test_block.py b/tests/primitives/test_block.py
index 9f5905bf..5e147a79 100644
--- a/tests/primitives/test_block.py
+++ b/tests/primitives/test_block.py
@@ -15,11 +15,10 @@ from __future__ import absolute_import, division, print_function
import binascii
-import pretend
import pytest
+from cryptography.primitives import interfaces
from cryptography.primitives.block import BlockCipher, ciphers, modes
-from cryptography.primitives.block.base import _Operation
class TestBlockCipher(object):
@@ -29,40 +28,42 @@ class TestBlockCipher(object):
modes.CBC(binascii.unhexlify(b"0" * 32))
)
- def test_use_after_finalize(self, api):
+ def test_creates_encryptor(self):
cipher = BlockCipher(
ciphers.AES(binascii.unhexlify(b"0" * 32)),
- modes.CBC(binascii.unhexlify(b"0" * 32)),
- api
+ modes.CBC(binascii.unhexlify(b"0" * 32))
)
- cipher.encrypt(b"a" * 16)
- cipher.finalize()
- with pytest.raises(ValueError):
- cipher.encrypt(b"b" * 16)
- with pytest.raises(ValueError):
- cipher.finalize()
+ assert isinstance(cipher.encryptor(), interfaces.CipherContext)
- def test_encrypt_with_invalid_operation(self, api):
+ def test_creates_decryptor(self):
cipher = BlockCipher(
ciphers.AES(binascii.unhexlify(b"0" * 32)),
- modes.CBC(binascii.unhexlify(b"0" * 32)),
- api
+ modes.CBC(binascii.unhexlify(b"0" * 32))
)
- cipher._operation = _Operation.decrypt
+ assert isinstance(cipher.decryptor(), interfaces.CipherContext)
- with pytest.raises(ValueError):
- cipher.encrypt(b"b" * 16)
- def test_finalize_with_invalid_operation(self, api):
+class TestBlockCipherContext(object):
+ def test_use_after_finalize(self, api):
cipher = BlockCipher(
ciphers.AES(binascii.unhexlify(b"0" * 32)),
modes.CBC(binascii.unhexlify(b"0" * 32)),
api
)
- cipher._operation = pretend.stub(name="wat")
-
+ encryptor = cipher.encryptor()
+ encryptor.update(b"a" * 16)
+ encryptor.finalize()
+ with pytest.raises(ValueError):
+ encryptor.update(b"b" * 16)
+ with pytest.raises(ValueError):
+ encryptor.finalize()
+ decryptor = cipher.decryptor()
+ decryptor.update(b"a" * 16)
+ decryptor.finalize()
+ with pytest.raises(ValueError):
+ decryptor.update(b"b" * 16)
with pytest.raises(ValueError):
- cipher.finalize()
+ decryptor.finalize()
def test_unaligned_block_encryption(self, api):
cipher = BlockCipher(
@@ -70,7 +71,16 @@ class TestBlockCipher(object):
modes.ECB(),
api
)
- ct = cipher.encrypt(b"a" * 15)
+ encryptor = cipher.encryptor()
+ ct = encryptor.update(b"a" * 15)
assert ct == b""
- ct += cipher.encrypt(b"a" * 65)
+ ct += encryptor.update(b"a" * 65)
assert len(ct) == 80
+ ct += encryptor.finalize()
+ decryptor = cipher.decryptor()
+ pt = decryptor.update(ct[:3])
+ assert pt == b""
+ pt += decryptor.update(ct[3:])
+ assert len(pt) == 80
+ assert pt == b"a" * 80
+ decryptor.finalize()
diff --git a/tests/primitives/test_ciphers.py b/tests/primitives/test_ciphers.py
index 27d35850..17fcdbaf 100644
--- a/tests/primitives/test_ciphers.py
+++ b/tests/primitives/test_ciphers.py
@@ -17,7 +17,7 @@ import binascii
import pytest
-from cryptography.primitives.block.ciphers import AES, Camellia
+from cryptography.primitives.block.ciphers import AES, Camellia, TripleDES
class TestAES(object):
@@ -48,3 +48,18 @@ class TestCamellia(object):
def test_invalid_key_size(self):
with pytest.raises(ValueError):
Camellia(binascii.unhexlify(b"0" * 12))
+
+
+class TestTripleDES(object):
+ @pytest.mark.parametrize("key", [
+ b"0" * 16,
+ b"0" * 32,
+ b"0" * 48,
+ ])
+ def test_key_size(self, key):
+ cipher = TripleDES(binascii.unhexlify(key))
+ assert cipher.key_size == 192
+
+ def test_invalid_key_size(self):
+ with pytest.raises(ValueError):
+ TripleDES(binascii.unhexlify(b"0" * 12))
diff --git a/tests/primitives/test_cryptrec.py b/tests/primitives/test_cryptrec.py
index edf97652..02a04473 100644
--- a/tests/primitives/test_cryptrec.py
+++ b/tests/primitives/test_cryptrec.py
@@ -37,6 +37,8 @@ class TestCamelliaECB(object):
],
lambda key: ciphers.Camellia(binascii.unhexlify((key))),
lambda key: modes.ECB(),
- only_if=lambda api: api.supports_cipher("camellia-128-ecb"),
+ only_if=lambda api: api.supports_cipher(
+ ciphers.Camellia("\x00" * 16), modes.ECB()
+ ),
skip_message="Does not support Camellia ECB",
)
diff --git a/tests/primitives/test_hashes.py b/tests/primitives/test_hashes.py
index 901ddabb..03de8916 100644
--- a/tests/primitives/test_hashes.py
+++ b/tests/primitives/test_hashes.py
@@ -13,11 +13,26 @@
from __future__ import absolute_import, division, print_function
+import pytest
+
+import six
+
from cryptography.primitives import hashes
from .utils import generate_base_hash_test
+class TestBaseHash(object):
+ def test_base_hash_reject_unicode(self, api):
+ m = hashes.SHA1(api=api)
+ with pytest.raises(TypeError):
+ m.update(six.u("\u00FC"))
+
+ def test_base_hash_hexdigest_string_type(self, api):
+ m = hashes.SHA1(api=api, data=b"")
+ assert isinstance(m.hexdigest(), str)
+
+
class TestSHA1(object):
test_SHA1 = generate_base_hash_test(
hashes.SHA1,
diff --git a/tests/primitives/test_nist.py b/tests/primitives/test_nist.py
index d97b207b..2a32d1bc 100644
--- a/tests/primitives/test_nist.py
+++ b/tests/primitives/test_nist.py
@@ -164,3 +164,93 @@ class TestAES_CFB(object):
lambda key, iv: ciphers.AES(binascii.unhexlify(key)),
lambda key, iv: modes.CFB(binascii.unhexlify(iv)),
)
+
+
+class TestTripleDES_CBC(object):
+ test_KAT = generate_encrypt_test(
+ lambda path: load_nist_vectors_from_file(path, "ENCRYPT"),
+ os.path.join("3DES", "KAT"),
+ [
+ "TCBCinvperm.rsp",
+ "TCBCpermop.rsp",
+ "TCBCsubtab.rsp",
+ "TCBCvarkey.rsp",
+ "TCBCvartext.rsp",
+ ],
+ lambda keys, iv: ciphers.TripleDES(binascii.unhexlify(keys)),
+ lambda keys, iv: modes.CBC(binascii.unhexlify(iv)),
+ )
+
+ test_MMT = generate_encrypt_test(
+ lambda path: load_nist_vectors_from_file(path, "ENCRYPT"),
+ os.path.join("3DES", "MMT"),
+ [
+ "TCBCMMT1.rsp",
+ "TCBCMMT2.rsp",
+ "TCBCMMT3.rsp",
+ ],
+ lambda key1, key2, key3, iv: (
+ ciphers.TripleDES(binascii.unhexlify(key1 + key2 + key3))
+ ),
+ lambda key1, key2, key3, iv: modes.CBC(binascii.unhexlify(iv)),
+ )
+
+
+class TestTripleDES_OFB(object):
+ test_KAT = generate_encrypt_test(
+ lambda path: load_nist_vectors_from_file(path, "ENCRYPT"),
+ os.path.join("3DES", "KAT"),
+ [
+ "TOFBpermop.rsp",
+ "TOFBsubtab.rsp",
+ "TOFBvarkey.rsp",
+ "TOFBvartext.rsp",
+ "TOFBinvperm.rsp",
+ ],
+ lambda keys, iv: ciphers.TripleDES(binascii.unhexlify(keys)),
+ lambda keys, iv: modes.OFB(binascii.unhexlify(iv)),
+ )
+
+ test_MMT = generate_encrypt_test(
+ lambda path: load_nist_vectors_from_file(path, "ENCRYPT"),
+ os.path.join("3DES", "MMT"),
+ [
+ "TOFBMMT1.rsp",
+ "TOFBMMT2.rsp",
+ "TOFBMMT3.rsp",
+ ],
+ lambda key1, key2, key3, iv: (
+ ciphers.TripleDES(binascii.unhexlify(key1 + key2 + key3))
+ ),
+ lambda key1, key2, key3, iv: modes.OFB(binascii.unhexlify(iv)),
+ )
+
+
+class TestTripleDES_CFB(object):
+ test_KAT = generate_encrypt_test(
+ lambda path: load_nist_vectors_from_file(path, "ENCRYPT"),
+ os.path.join("3DES", "KAT"),
+ [
+ "TCFB64invperm.rsp",
+ "TCFB64permop.rsp",
+ "TCFB64subtab.rsp",
+ "TCFB64varkey.rsp",
+ "TCFB64vartext.rsp",
+ ],
+ lambda keys, iv: ciphers.TripleDES(binascii.unhexlify(keys)),
+ lambda keys, iv: modes.CFB(binascii.unhexlify(iv)),
+ )
+
+ test_MMT = generate_encrypt_test(
+ lambda path: load_nist_vectors_from_file(path, "ENCRYPT"),
+ os.path.join("3DES", "MMT"),
+ [
+ "TCFB64MMT1.rsp",
+ "TCFB64MMT2.rsp",
+ "TCFB64MMT3.rsp",
+ ],
+ lambda key1, key2, key3, iv: (
+ ciphers.TripleDES(binascii.unhexlify(key1 + key2 + key3))
+ ),
+ lambda key1, key2, key3, iv: modes.CFB(binascii.unhexlify(iv)),
+ )
diff --git a/tests/primitives/test_openssl_vectors.py b/tests/primitives/test_openssl_vectors.py
index 5b2be784..86ff7cad 100644
--- a/tests/primitives/test_openssl_vectors.py
+++ b/tests/primitives/test_openssl_vectors.py
@@ -32,7 +32,9 @@ class TestCamelliaCBC(object):
["camellia-cbc.txt"],
lambda key, iv: ciphers.Camellia(binascii.unhexlify(key)),
lambda key, iv: modes.CBC(binascii.unhexlify(iv)),
- only_if=lambda api: api.supports_cipher("camellia-128-cbc"),
+ only_if=lambda api: api.supports_cipher(
+ ciphers.Camellia("\x00" * 16), modes.CBC("\x00" * 16)
+ ),
skip_message="Does not support Camellia CBC",
)
@@ -44,7 +46,9 @@ class TestCamelliaOFB(object):
["camellia-ofb.txt"],
lambda key, iv: ciphers.Camellia(binascii.unhexlify(key)),
lambda key, iv: modes.OFB(binascii.unhexlify(iv)),
- only_if=lambda api: api.supports_cipher("camellia-128-ofb"),
+ only_if=lambda api: api.supports_cipher(
+ ciphers.Camellia("\x00" * 16), modes.OFB("\x00" * 16)
+ ),
skip_message="Does not support Camellia OFB",
)
@@ -56,7 +60,9 @@ class TestCamelliaCFB(object):
["camellia-cfb.txt"],
lambda key, iv: ciphers.Camellia(binascii.unhexlify(key)),
lambda key, iv: modes.CFB(binascii.unhexlify(iv)),
- only_if=lambda api: api.supports_cipher("camellia-128-cfb"),
+ only_if=lambda api: api.supports_cipher(
+ ciphers.Camellia("\x00" * 16), modes.CFB("\x00" * 16)
+ ),
skip_message="Does not support Camellia CFB",
)
@@ -68,6 +74,8 @@ class TestAESCTR(object):
["aes-128-ctr.txt", "aes-192-ctr.txt", "aes-256-ctr.txt"],
lambda key, iv: ciphers.AES(binascii.unhexlify(key)),
lambda key, iv: modes.CTR(binascii.unhexlify(iv)),
- only_if=lambda api: api.supports_cipher("aes-128-ctr"),
+ only_if=lambda api: api.supports_cipher(
+ ciphers.AES("\x00" * 16), modes.CTR("\x00" * 16)
+ ),
skip_message="Does not support AES CTR",
)
diff --git a/tests/primitives/test_utils.py b/tests/primitives/test_utils.py
index 9888309e..6e197923 100644
--- a/tests/primitives/test_utils.py
+++ b/tests/primitives/test_utils.py
@@ -1,7 +1,8 @@
import pytest
-from .utils import (base_hash_test, encrypt_test, hash_test,
- long_string_hash_test)
+from .utils import (
+ base_hash_test, encrypt_test, hash_test, long_string_hash_test
+)
class TestEncryptTest(object):
diff --git a/tests/primitives/utils.py b/tests/primitives/utils.py
index a3759b03..91ca36d8 100644
--- a/tests/primitives/utils.py
+++ b/tests/primitives/utils.py
@@ -37,9 +37,14 @@ def encrypt_test(api, cipher_factory, mode_factory, params, only_if,
mode_factory(**params),
api
)
- actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext))
- actual_ciphertext += cipher.finalize()
+ encryptor = cipher.encryptor()
+ actual_ciphertext = encryptor.update(binascii.unhexlify(plaintext))
+ actual_ciphertext += encryptor.finalize()
assert actual_ciphertext == binascii.unhexlify(ciphertext)
+ decryptor = cipher.decryptor()
+ actual_plaintext = decryptor.update(binascii.unhexlify(ciphertext))
+ actual_plaintext += decryptor.finalize()
+ assert actual_plaintext == binascii.unhexlify(plaintext)
def generate_hash_test(param_loader, path, file_names, hash_cls,
@@ -67,6 +72,8 @@ def hash_test(api, hash_cls, params, only_if, skip_message):
m = hash_cls(api=api)
m.update(binascii.unhexlify(msg))
assert m.hexdigest() == md.replace(" ", "").lower()
+ digest = hash_cls(api=api, data=binascii.unhexlify(msg)).hexdigest()
+ assert digest == md.replace(" ", "").lower()
def generate_base_hash_test(hash_cls, digest_size, block_size,
@@ -115,6 +122,6 @@ def generate_long_string_hash_test(hash_factory, md, only_if=None,
def long_string_hash_test(api, hash_factory, md, only_if, skip_message):
if only_if is not None and not only_if(api):
pytest.skip(skip_message)
- m = hash_factory(api)
+ m = hash_factory(api=api)
m.update(b"a" * 1000000)
assert m.hexdigest() == md.lower()
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 3fe9e570..f96cf004 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -15,10 +15,12 @@ import textwrap
import pytest
-from .utils import (load_nist_vectors, load_nist_vectors_from_file,
- load_cryptrec_vectors, load_cryptrec_vectors_from_file,
- load_openssl_vectors, load_openssl_vectors_from_file, load_hash_vectors,
- load_hash_vectors_from_file)
+from .utils import (
+ load_nist_vectors, load_nist_vectors_from_file, load_cryptrec_vectors,
+ load_cryptrec_vectors_from_file, load_openssl_vectors,
+ load_openssl_vectors_from_file, load_hash_vectors,
+ load_hash_vectors_from_file
+)
def test_load_nist_vectors_encrypt():
diff --git a/tox.ini b/tox.ini
index e72eb58b..b437a7a9 100644
--- a/tox.ini
+++ b/tox.ini
@@ -19,5 +19,7 @@ commands =
[testenv:pep8]
deps = flake8
-# E128 continuation line under-indented for visual indent
-commands = flake8 --ignore="E128" cryptography/ tests/ docs/
+commands = flake8 .
+
+[flake8]
+exclude = .tox,*.egg