aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cryptography/hazmat/backends/openssl/backend.py85
-rw-r--r--cryptography/hazmat/primitives/cmac.py75
-rw-r--r--docs/hazmat/backends/interfaces.rst2
-rw-r--r--docs/hazmat/primitives/index.rst2
-rw-r--r--docs/hazmat/primitives/mac/cmac.rst105
-rw-r--r--docs/hazmat/primitives/mac/hmac.rst (renamed from docs/hazmat/primitives/hmac.rst)0
-rw-r--r--docs/hazmat/primitives/mac/index.rst10
-rw-r--r--docs/hazmat/primitives/symmetric-encryption.rst4
-rw-r--r--pytest.ini1
-rw-r--r--tests/conftest.py6
-rw-r--r--tests/hazmat/primitives/test_cmac.py217
11 files changed, 498 insertions, 9 deletions
diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py
index 5e13bfc1..7d73c413 100644
--- a/cryptography/hazmat/backends/openssl/backend.py
+++ b/cryptography/hazmat/backends/openssl/backend.py
@@ -25,8 +25,8 @@ from cryptography.exceptions import (
UnsupportedAlgorithm, _Reasons
)
from cryptography.hazmat.backends.interfaces import (
- CipherBackend, DSABackend, HMACBackend, HashBackend, PBKDF2HMACBackend,
- RSABackend
+ CMACBackend, CipherBackend, DSABackend, HMACBackend, HashBackend,
+ PBKDF2HMACBackend, RSABackend
)
from cryptography.hazmat.bindings.openssl.binding import Binding
from cryptography.hazmat.primitives import hashes, interfaces
@@ -47,6 +47,7 @@ _OpenSSLError = collections.namedtuple("_OpenSSLError",
@utils.register_interface(CipherBackend)
+@utils.register_interface(CMACBackend)
@utils.register_interface(DSABackend)
@utils.register_interface(HashBackend)
@utils.register_interface(HMACBackend)
@@ -554,6 +555,16 @@ class Backend(object):
return self._ffi.buffer(buf)[:res]
+ def cmac_algorithm_supported(self, algorithm):
+ return (
+ backend._lib.Cryptography_HAS_CMAC == 1
+ and backend.cipher_supported(algorithm, CBC(
+ b"\x00" * algorithm.block_size))
+ )
+
+ def create_cmac_ctx(self, algorithm):
+ return _CMACContext(self, algorithm)
+
class GetCipherByName(object):
def __init__(self, fmt):
@@ -1240,4 +1251,74 @@ class _RSAVerificationContext(object):
raise InvalidSignature
+@utils.register_interface(interfaces.CMACContext)
+class _CMACContext(object):
+ def __init__(self, backend, algorithm, ctx=None):
+ if not backend.cmac_algorithm_supported(algorithm):
+ raise UnsupportedAlgorithm("This backend does not support CMAC")
+
+ self._backend = backend
+ self._key = algorithm.key
+ self._algorithm = algorithm
+ self._output_length = algorithm.block_size // 8
+
+ if ctx is None:
+ registry = self._backend._cipher_registry
+ try:
+ adapter = registry[type(algorithm), CBC]
+ except KeyError:
+ raise UnsupportedAlgorithm(
+ "cipher {0} is not supported by this backend".format(
+ algorithm.name), _Reasons.UNSUPPORTED_CIPHER
+ )
+
+ evp_cipher = adapter(self._backend, algorithm, CBC)
+ if evp_cipher == self._backend._ffi.NULL:
+ raise UnsupportedAlgorithm(
+ "cipher {0} is not supported by this backend".format(
+ algorithm.name), _Reasons.UNSUPPORTED_CIPHER
+ )
+
+ ctx = self._backend._lib.CMAC_CTX_new()
+
+ assert ctx != self._backend._ffi.NULL
+ ctx = self._backend._ffi.gc(ctx, self._backend._lib.CMAC_CTX_free)
+
+ self._backend._lib.CMAC_Init(
+ ctx, self._key, len(self._key),
+ evp_cipher, self._backend._ffi.NULL
+ )
+
+ self._ctx = ctx
+
+ def update(self, data):
+ res = self._backend._lib.CMAC_Update(self._ctx, data, len(data))
+ assert res == 1
+
+ def finalize(self):
+ buf = self._backend._ffi.new("unsigned char[]", self._output_length)
+ length = self._backend._ffi.new("size_t *", self._output_length)
+ res = self._backend._lib.CMAC_Final(
+ self._ctx, buf, length
+ )
+ assert res == 1
+
+ self._ctx = None
+
+ return self._backend._ffi.buffer(buf)[:]
+
+ def copy(self):
+ copied_ctx = self._backend._lib.CMAC_CTX_new()
+ copied_ctx = self._backend._ffi.gc(
+ copied_ctx, self._backend._lib.CMAC_CTX_free
+ )
+ res = self._backend._lib.CMAC_CTX_copy(
+ copied_ctx, self._ctx
+ )
+ assert res == 1
+ return _CMACContext(
+ self._backend, self._algorithm, ctx=copied_ctx
+ )
+
+
backend = Backend()
diff --git a/cryptography/hazmat/primitives/cmac.py b/cryptography/hazmat/primitives/cmac.py
new file mode 100644
index 00000000..7e7f65ab
--- /dev/null
+++ b/cryptography/hazmat/primitives/cmac.py
@@ -0,0 +1,75 @@
+# 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.
+
+from __future__ import absolute_import, division, print_function
+
+import six
+
+from cryptography import utils
+from cryptography.exceptions import (
+ AlreadyFinalized, InvalidSignature, UnsupportedAlgorithm, _Reasons
+)
+from cryptography.hazmat.backends.interfaces import CMACBackend
+from cryptography.hazmat.primitives import constant_time, interfaces
+
+
+@utils.register_interface(interfaces.CMACContext)
+class CMAC(object):
+ def __init__(self, algorithm, backend, ctx=None):
+ if not isinstance(backend, CMACBackend):
+ raise UnsupportedAlgorithm(
+ "Backend object does not implement CMACBackend",
+ _Reasons.BACKEND_MISSING_INTERFACE
+ )
+
+ if not isinstance(algorithm, interfaces.BlockCipherAlgorithm):
+ raise TypeError(
+ "Expected instance of interfaces.BlockCipherAlgorithm"
+ )
+ self._algorithm = algorithm
+
+ self._backend = backend
+ if ctx is None:
+ self._ctx = self._backend.create_cmac_ctx(self._algorithm)
+ else:
+ self._ctx = ctx
+
+ def update(self, data):
+ if self._ctx is None:
+ raise AlreadyFinalized("Context was already finalized")
+ if isinstance(data, six.text_type):
+ raise TypeError("Unicode-objects must be encoded before hashing")
+ self._ctx.update(data)
+
+ def finalize(self):
+ if self._ctx is None:
+ raise AlreadyFinalized("Context was already finalized")
+ digest = self._ctx.finalize()
+ self._ctx = None
+ return digest
+
+ def verify(self, signature):
+ if isinstance(signature, six.text_type):
+ raise TypeError("Unicode-objects must be encoded before verifying")
+ digest = self.finalize()
+ if not constant_time.bytes_eq(digest, signature):
+ raise InvalidSignature("Signature did not match digest.")
+
+ def copy(self):
+ if self._ctx is None:
+ raise AlreadyFinalized("Context was already finalized")
+ return CMAC(
+ self._algorithm,
+ backend=self._backend,
+ ctx=self._ctx.copy()
+ )
diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst
index 71cd4564..3ed4cfb6 100644
--- a/docs/hazmat/backends/interfaces.rst
+++ b/docs/hazmat/backends/interfaces.rst
@@ -9,7 +9,7 @@ Backend interfaces
Backend implementations may provide a number of interfaces to support operations
such as :doc:`/hazmat/primitives/symmetric-encryption`,
:doc:`/hazmat/primitives/cryptographic-hashes`, and
-:doc:`/hazmat/primitives/hmac`.
+:doc:`/hazmat/primitives/mac/hmac`.
A specific ``backend`` may provide one or more of these interfaces.
diff --git a/docs/hazmat/primitives/index.rst b/docs/hazmat/primitives/index.rst
index 90deec8b..a9ab38a0 100644
--- a/docs/hazmat/primitives/index.rst
+++ b/docs/hazmat/primitives/index.rst
@@ -7,7 +7,7 @@ Primitives
:maxdepth: 1
cryptographic-hashes
- hmac
+ mac/index
symmetric-encryption
padding
key-derivation-functions
diff --git a/docs/hazmat/primitives/mac/cmac.rst b/docs/hazmat/primitives/mac/cmac.rst
new file mode 100644
index 00000000..8b88a3ce
--- /dev/null
+++ b/docs/hazmat/primitives/mac/cmac.rst
@@ -0,0 +1,105 @@
+.. hazmat::
+
+Cipher-based message authentication code
+========================================
+
+.. currentmodule:: cryptography.hazmat.primitives.cmac
+
+.. testsetup::
+
+ import binascii
+ key = binascii.unhexlify(b"0" * 32)
+
+`Cipher-based message authentication codes`_ (or CMACs) are a tool for calculating
+message authentication codes using a block cipher coupled with a
+secret key. You can use an CMAC to verify both the integrity and authenticity
+of a message.
+
+A subset of CMAC with the AES-128 algorithm is described in :rfc:`4493`.
+
+.. class:: CMAC(algorithm, backend)
+
+ CMAC objects take a
+ :class:`~cryptography.hazmat.primitives.interfaces.BlockCipherAlgorithm` provider.
+
+ .. code-block:: pycon
+
+ >>> from cryptography.hazmat.backends import default_backend
+ >>> from cryptography.hazmat.primitives import cmac
+ >>> from cryptography.hazmat.primitives.ciphers import algorithms
+ >>> c = cmac.CMAC(algorithms.AES(key), backend=default_backend())
+ >>> c.update(b"message to authenticate")
+ >>> c.finalize()
+ 'CT\x1d\xc8\x0e\x15\xbe4e\xdb\xb6\x84\xca\xd9Xk'
+
+ If the backend doesn't support the requested ``algorithm`` an
+ :class:`~cryptography.exceptions.UnsupportedAlgorithm` exception will be
+ raised.
+
+ If the `algorithm`` isn't a
+ :class:`~cryptography.primitives.interfaces.BlockCipherAlgorithm` provider,
+ ``TypeError`` will be raised.
+
+ To check that a given signature is correct use the :meth:`verify` method.
+ You will receive an exception if the signature is wrong:
+
+ .. code-block:: pycon
+
+ >>> c.verify(b"an incorrect signature")
+ Traceback (most recent call last):
+ ...
+ cryptography.exceptions.InvalidSignature: Signature did not match digest.
+
+ :param algorithm: An
+ :class:`~cryptography.hazmat.primitives.interfaces.BlockCipherAlgorithm`
+ provider.
+ :param backend: An
+ :class:`~cryptography.hazmat.backends.interfaces.CMACBackend`
+ provider.
+ :raises TypeError: This is raised if the provided ``algorithm`` is not an instance of
+ :class:`~cryptography.hazmat.primitives.interfaces.BlockCipherAlgorithm`
+ :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the
+ provided ``backend`` does not implement
+ :class:`~cryptography.hazmat.backends.interfaces.CMACBackend`
+
+ .. method:: update(data)
+
+ :param bytes data: The bytes to hash and authenticate.
+ :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize`
+
+ .. method:: copy()
+
+ Copy this :class:`CMAC` instance, usually so that we may call
+ :meth:`finalize` to get an intermediate value while we continue
+ to call :meth:`update` on the original instance.
+
+ :return: A new instance of :class:`CMAC` that can be updated
+ and finalized independently of the original instance.
+ :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize`
+
+ .. method:: verify(signature)
+
+ Finalize the current context and securely compare the MAC to
+ ``signature``.
+
+ :param bytes signature: The bytes to compare the current CMAC
+ against.
+ :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize`
+ :raises cryptography.exceptions.InvalidSignature: If signature does not
+ match digest
+
+ .. method:: finalize()
+
+ Finalize the current context and return the message authentication code
+ as bytes.
+
+ After ``finalize`` has been called this object can no longer be used
+ and :meth:`update`, :meth:`copy`, :meth:`verify` and :meth:`finalize`
+ will raise an :class:`~cryptography.exceptions.AlreadyFinalized`
+ exception.
+
+ :return bytes: The message authentication code as bytes.
+ :raises cryptography.exceptions.AlreadyFinalized:
+
+
+.. _`Cipher-based message authentication codes`: https://en.wikipedia.org/wiki/CMAC
diff --git a/docs/hazmat/primitives/hmac.rst b/docs/hazmat/primitives/mac/hmac.rst
index 11b10735..11b10735 100644
--- a/docs/hazmat/primitives/hmac.rst
+++ b/docs/hazmat/primitives/mac/hmac.rst
diff --git a/docs/hazmat/primitives/mac/index.rst b/docs/hazmat/primitives/mac/index.rst
new file mode 100644
index 00000000..59fb8da2
--- /dev/null
+++ b/docs/hazmat/primitives/mac/index.rst
@@ -0,0 +1,10 @@
+.. hazmat::
+
+Message Authentication Codes
+============================
+
+.. toctree::
+ :maxdepth: 1
+
+ cmac
+ hmac
diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst
index 1a4df222..c2692ae2 100644
--- a/docs/hazmat/primitives/symmetric-encryption.rst
+++ b/docs/hazmat/primitives/symmetric-encryption.rst
@@ -21,7 +21,7 @@ message but an attacker can create bogus messages and force the application to
decrypt them.
For this reason it is *strongly* recommended to combine encryption with a
-message authentication code, such as :doc:`HMAC </hazmat/primitives/hmac>`, in
+message authentication code, such as :doc:`HMAC </hazmat/primitives/mac/hmac>`, in
an "encrypt-then-MAC" formulation as `described by Colin Percival`_.
.. class:: Cipher(algorithm, mode, backend)
@@ -289,7 +289,7 @@ Modes
block cipher mode that simultaneously encrypts the message as well as
authenticating it. Additional unencrypted data may also be authenticated.
Additional means of verifying integrity such as
- :doc:`HMAC </hazmat/primitives/hmac>` are not necessary.
+ :doc:`HMAC </hazmat/primitives/mac/hmac>` are not necessary.
**This mode does not require padding.**
diff --git a/pytest.ini b/pytest.ini
index b590d0be..cb6a80a8 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -2,6 +2,7 @@
addopts = -r s
markers =
cipher: this test requires a backend providing CipherBackend
+ cmac: this test requires a backend providing CMACBackend
dsa: this test requires a backend providing DSABackend
hash: this test requires a backend providing HashBackend
hmac: this test requires a backend providing HMACBackend
diff --git a/tests/conftest.py b/tests/conftest.py
index 1ee2a993..d55e6cf6 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -17,10 +17,9 @@ import pytest
from cryptography.hazmat.backends import _available_backends
from cryptography.hazmat.backends.interfaces import (
- CipherBackend, DSABackend, HMACBackend, HashBackend, PBKDF2HMACBackend,
- RSABackend
+ CMACBackend, CipherBackend, DSABackend, HMACBackend, HashBackend,
+ PBKDF2HMACBackend, RSABackend
)
-
from .utils import check_backend_support, check_for_iface, select_backends
@@ -36,6 +35,7 @@ def pytest_generate_tests(metafunc):
def pytest_runtest_setup(item):
check_for_iface("hmac", HMACBackend, item)
check_for_iface("cipher", CipherBackend, item)
+ check_for_iface("cmac", CMACBackend, item)
check_for_iface("hash", HashBackend, item)
check_for_iface("pbkdf2hmac", PBKDF2HMACBackend, item)
check_for_iface("dsa", DSABackend, item)
diff --git a/tests/hazmat/primitives/test_cmac.py b/tests/hazmat/primitives/test_cmac.py
new file mode 100644
index 00000000..7ec4af68
--- /dev/null
+++ b/tests/hazmat/primitives/test_cmac.py
@@ -0,0 +1,217 @@
+# 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.
+
+from __future__ import absolute_import, division, print_function
+
+import binascii
+
+import pretend
+
+import pytest
+
+import six
+
+from cryptography import utils
+from cryptography.exceptions import (
+ AlreadyFinalized, InvalidSignature, _Reasons
+)
+from cryptography.hazmat.backends.interfaces import CMACBackend
+from cryptography.hazmat.primitives.ciphers.algorithms import (
+ AES, ARC4, TripleDES
+)
+from cryptography.hazmat.primitives.cmac import CMAC
+
+from tests.utils import (
+ load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm
+)
+
+vectors_aes128 = load_vectors_from_file(
+ "CMAC/nist-800-38b-aes128.txt", load_nist_vectors)
+
+vectors_aes192 = load_vectors_from_file(
+ "CMAC/nist-800-38b-aes192.txt", load_nist_vectors)
+
+vectors_aes256 = load_vectors_from_file(
+ "CMAC/nist-800-38b-aes256.txt", load_nist_vectors)
+
+vectors_aes = vectors_aes128 + vectors_aes192 + vectors_aes256
+
+vectors_3des = load_vectors_from_file(
+ "CMAC/nist-800-38b-3des.txt", load_nist_vectors)
+
+fake_key = b"\x00" * 16
+
+
+@pytest.mark.cmac
+class TestCMAC(object):
+ @pytest.mark.supported(
+ only_if=lambda backend: backend.cmac_algorithm_supported(
+ AES(fake_key)),
+ skip_message="Does not support CMAC."
+ )
+ @pytest.mark.parametrize("params", vectors_aes)
+ def test_aes_generate(self, backend, params):
+ key = params["key"]
+ message = params["message"]
+ output = params["output"]
+
+ cmac = CMAC(AES(binascii.unhexlify(key)), backend)
+ cmac.update(binascii.unhexlify(message))
+ assert binascii.hexlify(cmac.finalize()) == output
+
+ @pytest.mark.supported(
+ only_if=lambda backend: backend.cmac_algorithm_supported(
+ AES(fake_key)),
+ skip_message="Does not support CMAC."
+ )
+ @pytest.mark.parametrize("params", vectors_aes)
+ def test_aes_verify(self, backend, params):
+ key = params["key"]
+ message = params["message"]
+ output = params["output"]
+
+ cmac = CMAC(AES(binascii.unhexlify(key)), backend)
+ cmac.update(binascii.unhexlify(message))
+ assert cmac.verify(binascii.unhexlify(output)) is None
+
+ @pytest.mark.supported(
+ only_if=lambda backend: backend.cmac_algorithm_supported(
+ TripleDES(fake_key)),
+ skip_message="Does not support CMAC."
+ )
+ @pytest.mark.parametrize("params", vectors_3des)
+ def test_3des_generate(self, backend, params):
+ key1 = params["key1"]
+ key2 = params["key2"]
+ key3 = params["key3"]
+
+ key = key1 + key2 + key3
+
+ message = params["message"]
+ output = params["output"]
+
+ cmac = CMAC(TripleDES(binascii.unhexlify(key)), backend)
+ cmac.update(binascii.unhexlify(message))
+ assert binascii.hexlify(cmac.finalize()) == output
+
+ @pytest.mark.supported(
+ only_if=lambda backend: backend.cmac_algorithm_supported(
+ TripleDES(fake_key)),
+ skip_message="Does not support CMAC."
+ )
+ @pytest.mark.parametrize("params", vectors_3des)
+ def test_3des_verify(self, backend, params):
+ key1 = params["key1"]
+ key2 = params["key2"]
+ key3 = params["key3"]
+
+ key = key1 + key2 + key3
+
+ message = params["message"]
+ output = params["output"]
+
+ cmac = CMAC(TripleDES(binascii.unhexlify(key)), backend)
+ cmac.update(binascii.unhexlify(message))
+ assert cmac.verify(binascii.unhexlify(output)) is None
+
+ @pytest.mark.supported(
+ only_if=lambda backend: backend.cmac_algorithm_supported(
+ AES(fake_key)),
+ skip_message="Does not support CMAC."
+ )
+ def test_invalid_verify(self, backend):
+ key = b"2b7e151628aed2a6abf7158809cf4f3c"
+ cmac = CMAC(AES(key), backend)
+ cmac.update(b"6bc1bee22e409f96e93d7e117393172a")
+
+ with pytest.raises(InvalidSignature):
+ cmac.verify(b"foobar")
+
+ @pytest.mark.supported(
+ only_if=lambda backend: backend.cipher_supported(
+ ARC4(fake_key), None),
+ skip_message="Does not support CMAC."
+ )
+ def test_invalid_algorithm(self, backend):
+ key = b"0102030405"
+ with pytest.raises(TypeError):
+ CMAC(ARC4(key), backend)
+
+ @pytest.mark.supported(
+ only_if=lambda backend: backend.cmac_algorithm_supported(
+ AES(fake_key)),
+ skip_message="Does not support CMAC."
+ )
+ def test_raises_after_finalize(self, backend):
+ key = b"2b7e151628aed2a6abf7158809cf4f3c"
+ cmac = CMAC(AES(key), backend)
+ cmac.finalize()
+
+ with pytest.raises(AlreadyFinalized):
+ cmac.update(b"foo")
+
+ with pytest.raises(AlreadyFinalized):
+ cmac.copy()
+
+ with pytest.raises(AlreadyFinalized):
+ cmac.finalize()
+
+ @pytest.mark.supported(
+ only_if=lambda backend: backend.cmac_algorithm_supported(
+ AES(fake_key)),
+ skip_message="Does not support CMAC."
+ )
+ def test_verify_reject_unicode(self, backend):
+ key = b"2b7e151628aed2a6abf7158809cf4f3c"
+ cmac = CMAC(AES(key), backend)
+
+ with pytest.raises(TypeError):
+ cmac.update(six.u(''))
+
+ with pytest.raises(TypeError):
+ cmac.verify(six.u(''))
+
+ @pytest.mark.supported(
+ only_if=lambda backend: backend.cmac_algorithm_supported(
+ AES(fake_key)),
+ skip_message="Does not support CMAC."
+ )
+ def test_copy_with_backend(self, backend):
+ key = b"2b7e151628aed2a6abf7158809cf4f3c"
+ cmac = CMAC(AES(key), backend)
+ cmac.update(b"6bc1bee22e409f96e93d7e117393172a")
+ copy_cmac = cmac.copy()
+ assert cmac.finalize() == copy_cmac.finalize()
+
+
+def test_copy():
+ @utils.register_interface(CMACBackend)
+ class PretendBackend(object):
+ pass
+
+ pretend_backend = PretendBackend()
+ copied_ctx = pretend.stub()
+ pretend_ctx = pretend.stub(copy=lambda: copied_ctx)
+ key = b"2b7e151628aed2a6abf7158809cf4f3c"
+ cmac = CMAC(AES(key), backend=pretend_backend, ctx=pretend_ctx)
+
+ assert cmac._backend is pretend_backend
+ assert cmac.copy()._backend is pretend_backend
+
+
+def test_invalid_backend():
+ key = b"2b7e151628aed2a6abf7158809cf4f3c"
+ pretend_backend = object()
+
+ with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE):
+ CMAC(AES(key), pretend_backend)