diff options
-rw-r--r-- | cryptography/bindings/openssl/api.py | 8 | ||||
-rw-r--r-- | cryptography/primitives/block/ciphers.py | 24 | ||||
-rw-r--r-- | cryptography/primitives/hashes.py | 6 | ||||
-rw-r--r-- | docs/primitives/cryptographic-hashes.rst | 4 | ||||
-rw-r--r-- | docs/primitives/symmetric-encryption.rst | 17 | ||||
-rw-r--r-- | tests/primitives/test_ciphers.py | 17 | ||||
-rw-r--r-- | tests/primitives/test_hashes.py | 11 | ||||
-rw-r--r-- | tests/primitives/test_nist.py | 90 | ||||
-rw-r--r-- | tests/primitives/test_utils.py | 5 | ||||
-rw-r--r-- | tests/primitives/utils.py | 4 | ||||
-rw-r--r-- | tests/test_utils.py | 10 | ||||
-rw-r--r-- | tox.ini | 6 |
12 files changed, 189 insertions, 13 deletions
diff --git a/cryptography/bindings/openssl/api.py b/cryptography/bindings/openssl/api.py index 67d73afb..fedaf9cc 100644 --- a/cryptography/bindings/openssl/api.py +++ b/cryptography/bindings/openssl/api.py @@ -19,7 +19,7 @@ import sys import cffi from cryptography.primitives import interfaces -from cryptography.primitives.block.ciphers import AES, Camellia +from cryptography.primitives.block.ciphers import AES, Camellia, TripleDES from cryptography.primitives.block.modes import CBC, CTR, ECB, OFB, CFB @@ -135,6 +135,12 @@ class API(object): 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) 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/hashes.py b/cryptography/primitives/hashes.py index e8c1f929..c4bd8ad0 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): 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 79d712e5..73d8ad37 100644 --- a/docs/primitives/symmetric-encryption.rst +++ b/docs/primitives/symmetric-encryption.rst @@ -79,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/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_hashes.py b/tests/primitives/test_hashes.py index 901ddabb..805d992b 100644 --- a/tests/primitives/test_hashes.py +++ b/tests/primitives/test_hashes.py @@ -13,11 +13,22 @@ 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")) + + 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_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 70ece52a..91ca36d8 100644 --- a/tests/primitives/utils.py +++ b/tests/primitives/utils.py @@ -72,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, @@ -120,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(): @@ -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 |