Repository: pyca/cryptography

Issue:
Cannot use BLAKE2b or BLAKE2s with HMAC
Python 3.6.2 on Ubuntu 17.10
Cryptography and dependencies installed via pip in virtualenv
cffi==1.10.0
cryptography==2.0.3
pip==9.0.1
setuptools==36.2.7

Steps to reproduce:
```
In [1]: from cryptography.hazmat.backends import default_backend
   ...: from cryptography.hazmat.primitives import hashes
   ...: from cryptography.hazmat.primitives.hmac import HMAC
   ...: 
   ...: backend = default_backend()
   ...: 

In [2]: hmac = HMAC(b'\x00'*32, hashes.SHA256(), backend)  # just fine

In [3]: hmac = HMAC(b'\x00'*32, hashes.BLAKE2s(digest_size=32), backend)
---------------------------------------------------------------------------
UnsupportedAlgorithm                      Traceback (most recent call last)
<ipython-input-3-61f273a52c45> in <module>()
----> 1 hmac = HMAC(b'\x00'*32, hashes.BLAKE2s(digest_size=32), backend)

~/.venvs/master/lib/python3.6/site-packages/cryptography/hazmat/primitives/hmac.py in __init__(self, key, algorithm, backend, ctx)
     30         self._key = key
     31         if ctx is None:
---> 32             self._ctx = self._backend.create_hmac_ctx(key, self.algorithm)
     33         else:
     34             self._ctx = ctx

~/.venvs/master/lib/python3.6/site-packages/cryptography/hazmat/backends/openssl/backend.py in create_hmac_ctx(self, key, algorithm)
    176 
    177     def create_hmac_ctx(self, key, algorithm):
--> 178         return _HMACContext(self, key, algorithm)
    179 
    180     def _build_openssl_digest_name(self, algorithm):

~/.venvs/master/lib/python3.6/site-packages/cryptography/hazmat/backends/openssl/hmac.py in __init__(self, backend, key, algorithm, ctx)
     32                     "{0} is not a supported hash on this backend.".format(
     33                         algorithm.name),
---> 34                     _Reasons.UNSUPPORTED_HASH
     35                 )
     36             res = self._backend._lib.HMAC_Init_ex(

UnsupportedAlgorithm: blake2s is not a supported hash on this backend.
```



Discussion:
What is the output if you run `python -c "from cryptography.hazmat.backends.openssl.backend import backend;print(backend.openssl_version_text())"`
```
python -c "from cryptography.hazmat.backends.openssl.backend import backend;print(backend.openssl_version_text())"
OpenSSL 1.1.0f  25 May 2017
```

Also I've checked individual algorithms support:
```
In [8]: backend.hash_supported(hashes.BLAKE2s(32))
Out[8]: True

In [9]: backend.hash_supported(hashes.BLAKE2b(64))
Out[9]: True

In [10]: backend.hmac_supported(hashes.BLAKE2b(64))
Out[10]: True

In [11]: backend.hmac_supported(hashes.BLAKE2s(32))
Out[11]: True
```
I misunderstood the problem initially, so thank you for the excellent writeup here. The bug is here: https://github.com/pyca/cryptography/blob/master/src/cryptography/hazmat/backends/openssl/hmac.py#L28-L29

Which should, instead, read:

```python
            evp_md = self._backend._lib.EVP_get_digestbyname(
                self._backend._build_openssl_digest_name(algorithm))
```

However, it's a bit unclear to me whether HMAC BLAKE2 vectors exist to confirm that this code path is generating valid HMAC outputs. Generally BLAKE2 would not be used with HMAC since it has its own HMAC-like keyed hashing functionality (which is unfortunately not available via OpenSSL so we don't expose it). I see three potential paths forward here:

1) Fix this bug and write a test to confirm that you can instantiate an HMAC object using BLAKE2, but don't attempt to assert on output.
2) Fix this bug, assume OpenSSL isn't doing the wrong thing, and use its output as a test vector to assert on.
3) Explicitly disallow BLAKE2 in HMAC and raise an error that clearly states it is unsupported.

Could you give us a bit more information about why you want to do this? Is it interoperating with other software that already has chosen to go down this path?
Sure. I'm implementing Noise Protocol Framework (noiseprotocol.org) in Python. I've chosen pyca/cryptography as source of primitives (tried pycryptodome too, but it lacked Curve25519 support). It is explicitely stated in the specification to directly use HMAC instead of keyed BLAKE. Rationale behind this is here: http://www.noiseprotocol.org/noise.html#hash-functions-and-hashing

Anyway, as OpenSSL 1.1 ( doesn't provide such capabilities right now, it was never an option for me.

Basically, HKDF is performed to derive keys for encryption of further communication (multiple times during handshake, then finally to derive keys after hanshake is done and protocol is in transport mode). HMAC in HKDF uses given hash function - one of SHA256, SHA512, BLAKE2s and BLAKE2b.

For now I've fallen back to python's hashlib, but this requires me to support only Python 3.6+ - Blake hasn't been available before.

Resolving patch:
diff --git a/src/cryptography/hazmat/backends/openssl/hmac.py b/src/cryptography/hazmat/backends/openssl/hmac.py
--- a/src/cryptography/hazmat/backends/openssl/hmac.py
+++ b/src/cryptography/hazmat/backends/openssl/hmac.py
@@ -25,12 +25,11 @@ def __init__(self, backend, key, algorithm, ctx=None):
             ctx = self._backend._ffi.gc(
                 ctx, self._backend._lib.Cryptography_HMAC_CTX_free
             )
-            evp_md = self._backend._lib.EVP_get_digestbyname(
-                algorithm.name.encode('ascii'))
+            name = self._backend._build_openssl_digest_name(algorithm)
+            evp_md = self._backend._lib.EVP_get_digestbyname(name)
             if evp_md == self._backend._ffi.NULL:
                 raise UnsupportedAlgorithm(
-                    "{0} is not a supported hash on this backend.".format(
-                        algorithm.name),
+                    "{0} is not a supported hash on this backend".format(name),
                     _Reasons.UNSUPPORTED_HASH
                 )
             res = self._backend._lib.HMAC_Init_ex(


Test patch:
diff --git a/tests/hazmat/primitives/test_hmac_vectors.py b/tests/hazmat/primitives/test_hmac_vectors.py
--- a/tests/hazmat/primitives/test_hmac_vectors.py
+++ b/tests/hazmat/primitives/test_hmac_vectors.py
@@ -4,10 +4,12 @@
 
 from __future__ import absolute_import, division, print_function
 
+import binascii
+
 import pytest
 
 from cryptography.hazmat.backends.interfaces import HMACBackend
-from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives import hashes, hmac
 
 from .utils import generate_hmac_test
 from ...utils import load_hash_vectors
@@ -107,3 +109,29 @@ class TestHMACSHA512(object):
         ],
         hashes.SHA512(),
     )
+
+
+@pytest.mark.supported(
+    only_if=lambda backend: backend.hmac_supported(hashes.BLAKE2b(
+        digest_size=64
+    )),
+    skip_message="Does not support BLAKE2",
+)
+@pytest.mark.requires_backend_interface(interface=HMACBackend)
+class TestHMACBLAKE2(object):
+    def test_blake2b(self, backend):
+        h = hmac.HMAC(b"0" * 64, hashes.BLAKE2b(digest_size=64), backend)
+        h.update(b"test")
+        digest = h.finalize()
+        assert digest == binascii.unhexlify(
+            b"b5319122f8a24ba134a0c9851922448104e25be5d1b91265c0c68b22722f0f29"
+            b"87dba4aeaa69e6bed7edc44f48d6b1be493a3ce583f9c737c53d6bacc09e2f32"
+        )
+
+    def test_blake2s(self, backend):
+        h = hmac.HMAC(b"0" * 32, hashes.BLAKE2s(digest_size=32), backend)
+        h.update(b"test")
+        digest = h.finalize()
+        assert digest == binascii.unhexlify(
+            b"51477cc5bdf1faf952cf97bb934ee936de1f4d5d7448a84eeb6f98d23b392166"
+        )