diff options
-rw-r--r-- | cryptography/bindings/openssl/api.py | 6 | ||||
-rw-r--r-- | cryptography/primitives/block/ciphers.py | 20 | ||||
-rw-r--r-- | docs/primitives/symmetric-encryption.rst | 9 | ||||
-rw-r--r-- | pytest.ini | 2 | ||||
-rw-r--r-- | tests/bindings/test_openssl.py | 3 | ||||
-rw-r--r-- | tests/primitives/test_ciphers.py | 17 | ||||
-rw-r--r-- | tests/primitives/test_cryptrec.py | 62 | ||||
-rw-r--r-- | tests/primitives/test_openssl_vectors.py | 100 |
8 files changed, 217 insertions, 2 deletions
diff --git a/cryptography/bindings/openssl/api.py b/cryptography/bindings/openssl/api.py index 56381ae9..02ba8fd4 100644 --- a/cryptography/bindings/openssl/api.py +++ b/cryptography/bindings/openssl/api.py @@ -85,6 +85,10 @@ 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 create_block_cipher_context(self, cipher, mode): ctx = self.ffi.new("EVP_CIPHER_CTX *") res = self.lib.EVP_CIPHER_CTX_init(ctx) @@ -93,7 +97,7 @@ class API(object): # 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")) assert evp_cipher != self.ffi.NULL if isinstance(mode, interfaces.ModeWithInitializationVector): diff --git a/cryptography/primitives/block/ciphers.py b/cryptography/primitives/block/ciphers.py index 01dbd027..4ac150a4 100644 --- a/cryptography/primitives/block/ciphers.py +++ b/cryptography/primitives/block/ciphers.py @@ -32,3 +32,23 @@ class AES(object): @property def key_size(self): return len(self.key) * 8 + + +class Camellia(object): + name = "camellia" + block_size = 128 + key_sizes = frozenset([128, 192, 256]) + + def __init__(self, key): + super(Camellia, self).__init__() + 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/docs/primitives/symmetric-encryption.rst b/docs/primitives/symmetric-encryption.rst index 46d7c07c..c4bbf0a5 100644 --- a/docs/primitives/symmetric-encryption.rst +++ b/docs/primitives/symmetric-encryption.rst @@ -51,6 +51,15 @@ Ciphers :param bytes key: The secret key, either ``128``, ``192``, or ``256`` bits. This must be kept secret. +.. class:: cryptography.primitives.block.ciphers.Camellia(key) + + Camellia is a block cipher approved for use by CRYPTREC and ISO/IEC. + It is considered to have comparable security and performance to AES, but + is not as widely studied or deployed. + + :param bytes key: The secret key, either ``128``, ``192``, or ``256`` bits. + This must be kept secret. + Modes ~~~~~ diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..723735ac --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +addopts = -r s diff --git a/tests/bindings/test_openssl.py b/tests/bindings/test_openssl.py index b23c4ccc..e5b78d18 100644 --- a/tests/bindings/test_openssl.py +++ b/tests/bindings/test_openssl.py @@ -28,3 +28,6 @@ class TestOpenSSL(object): for every OpenSSL. """ assert api.openssl_version_text().startswith("OpenSSL") + + def test_supports_cipher(self): + assert api.supports_cipher("not-a-real-cipher") is False diff --git a/tests/primitives/test_ciphers.py b/tests/primitives/test_ciphers.py index 5ee9f223..27d35850 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 +from cryptography.primitives.block.ciphers import AES, Camellia class TestAES(object): @@ -33,3 +33,18 @@ class TestAES(object): def test_invalid_key_size(self): with pytest.raises(ValueError): AES(binascii.unhexlify(b"0" * 12)) + + +class TestCamellia(object): + @pytest.mark.parametrize(("key", "keysize"), [ + (b"0" * 32, 128), + (b"0" * 48, 192), + (b"0" * 64, 256), + ]) + def test_key_size(self, key, keysize): + cipher = Camellia(binascii.unhexlify(key)) + assert cipher.key_size == keysize + + def test_invalid_key_size(self): + with pytest.raises(ValueError): + Camellia(binascii.unhexlify(b"0" * 12)) diff --git a/tests/primitives/test_cryptrec.py b/tests/primitives/test_cryptrec.py new file mode 100644 index 00000000..c30bda48 --- /dev/null +++ b/tests/primitives/test_cryptrec.py @@ -0,0 +1,62 @@ +# 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. + +""" +Test using the CRYPTREC (Camellia) Test Vectors +""" + +from __future__ import absolute_import, division, print_function + +import binascii +import itertools +import os + +import pytest + +from cryptography.primitives.block import BlockCipher, ciphers, modes + +from ..utils import load_cryptrec_vectors_from_file + + +def parameterize_encrypt_test(cipher, vector_type, params, fnames): + return pytest.mark.parametrize(params, + list(itertools.chain.from_iterable( + load_cryptrec_vectors_from_file( + os.path.join(cipher, vector_type, fname), + ) + for fname in fnames + )) + ) + + +class TestCamelliaECB(object): + @parameterize_encrypt_test( + "Camellia", "NTT", + ("key", "plaintext", "ciphertext"), + [ + "camellia-128-ecb.txt", + "camellia-192-ecb.txt", + "camellia-256-ecb.txt", + ] + ) + def test_NTT(self, key, plaintext, ciphertext, api): + if not api.supports_cipher("camellia-128-ecb"): + pytest.skip("Does not support Camellia ECB") # pragma: no cover + cipher = BlockCipher( + ciphers.Camellia(binascii.unhexlify(key)), + modes.ECB(), + api + ) + actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext)) + actual_ciphertext += cipher.finalize() + assert binascii.hexlify(actual_ciphertext).upper() == ciphertext diff --git a/tests/primitives/test_openssl_vectors.py b/tests/primitives/test_openssl_vectors.py new file mode 100644 index 00000000..d30efa5c --- /dev/null +++ b/tests/primitives/test_openssl_vectors.py @@ -0,0 +1,100 @@ +# 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. + +""" +Test using the OpenSSL Test Vectors +""" + +from __future__ import absolute_import, division, print_function + +import binascii +import itertools +import os + +import pytest + +from cryptography.primitives.block import BlockCipher, ciphers, modes + +from ..utils import load_openssl_vectors_from_file + + +def parameterize_encrypt_test(cipher, params, fnames): + return pytest.mark.parametrize(params, + list(itertools.chain.from_iterable( + load_openssl_vectors_from_file(os.path.join(cipher, fname)) + for fname in fnames + )) + ) + + +class TestCamelliaCBC(object): + + @parameterize_encrypt_test( + "Camellia", + ("key", "iv", "plaintext", "ciphertext"), + [ + "camellia-cbc.txt", + ] + ) + def test_OpenSSL(self, key, iv, plaintext, ciphertext, api): + if not api.supports_cipher("camellia-128-cbc"): + pytest.skip("Does not support Camellia CBC") # pragma: no cover + cipher = BlockCipher( + ciphers.Camellia(binascii.unhexlify(key)), + modes.CBC(binascii.unhexlify(iv)), + ) + actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext)) + actual_ciphertext += cipher.finalize() + assert binascii.hexlify(actual_ciphertext).upper() == ciphertext + + +class TestCamelliaOFB(object): + + @parameterize_encrypt_test( + "Camellia", + ("key", "iv", "plaintext", "ciphertext"), + [ + "camellia-ofb.txt", + ] + ) + def test_OpenSSL(self, key, iv, plaintext, ciphertext, api): + if not api.supports_cipher("camellia-128-ofb"): + pytest.skip("Does not support Camellia OFB") # pragma: no cover + cipher = BlockCipher( + ciphers.Camellia(binascii.unhexlify(key)), + modes.OFB(binascii.unhexlify(iv)), + ) + actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext)) + actual_ciphertext += cipher.finalize() + assert binascii.hexlify(actual_ciphertext).upper() == ciphertext + + +class TestCamelliaCFB(object): + + @parameterize_encrypt_test( + "Camellia", + ("key", "iv", "plaintext", "ciphertext"), + [ + "camellia-cfb.txt", + ] + ) + def test_OpenSSL(self, key, iv, plaintext, ciphertext, api): + if not api.supports_cipher("camellia-128-cfb"): + pytest.skip("Does not support Camellia CFB") # pragma: no cover + cipher = BlockCipher( + ciphers.Camellia(binascii.unhexlify(key)), + modes.CFB(binascii.unhexlify(iv)), + ) + actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext)) + actual_ciphertext += cipher.finalize() + assert binascii.hexlify(actual_ciphertext).upper() == ciphertext |