blake2b/blake2s support (#3116)
* blake2b/blake2s support
Doesn't support keying, personalization, salting, or tree hashes so
the API is pretty simple right now.
* implement digest_size via utils.read_only_property
* un-keyed for spelling's sake
* test copying + digest_size checks
* unkeyed is too a word
* line wrap
* reword the docs
* use the evp algorithm name in the error
This will make BLAKE2 alternate digest size errors a bit less confusing
* add changelog entry and docs about supported digest_size
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 667720c..1854203 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -6,6 +6,10 @@
.. note:: This version is not yet released and is under active development.
+* Added support for :class:`~cryptography.hazmat.primitives.hashes.BLAKE2b` and
+ :class:`~cryptography.hazmat.primitives.hashes.BLAKE2s` when using OpenSSL
+ 1.1.0.
+
1.5 - 2016-08-26
~~~~~~~~~~~~~~~~
diff --git a/docs/hazmat/primitives/cryptographic-hashes.rst b/docs/hazmat/primitives/cryptographic-hashes.rst
index d0414ef..b0e9c16 100644
--- a/docs/hazmat/primitives/cryptographic-hashes.rst
+++ b/docs/hazmat/primitives/cryptographic-hashes.rst
@@ -117,6 +117,36 @@
SHA-512 is a cryptographic hash function from the SHA-2 family and is
standardized by NIST. It produces a 512-bit message digest.
+BLAKE2
+~~~~~~
+
+`BLAKE2`_ is a cryptographic hash function specified in :rfc:`7693`.
+
+.. note::
+
+ While the RFC specifies keying, personalization, and salting features,
+ these are not supported at this time due to limitations in OpenSSL 1.1.0.
+
+.. class:: BLAKE2b(digest_size)
+
+ BLAKE2b is optimized for 64-bit platforms and produces an 1 to 64-byte
+ message digest.
+
+ :param int digest_size: The desired size of the hash output in bytes. Only
+ ``64`` is supported at this time.
+
+ :raises ValueError: If the ``digest_size`` is invalid.
+
+.. class:: BLAKE2s(digest_size)
+
+ BLAKE2s is optimized for 8 to 32-bit platforms and produces a
+ 1 to 32-byte message digest.
+
+ :param int digest_size: The desired size of the hash output in bytes. Only
+ ``32`` is supported at this time.
+
+ :raises ValueError: If the ``digest_size`` is invalid.
+
RIPEMD160
~~~~~~~~~
@@ -193,3 +223,4 @@
.. _`Lifetimes of cryptographic hash functions`: http://valerieaurora.org/hash.html
+.. _`BLAKE2`: https://blake2.net
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index a1de1a8..7d16e05 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -185,8 +185,19 @@
def create_hmac_ctx(self, key, algorithm):
return _HMACContext(self, key, algorithm)
+ def _build_openssl_digest_name(self, algorithm):
+ if algorithm.name == "blake2b" or algorithm.name == "blake2s":
+ alg = "{0}{1}".format(
+ algorithm.name, algorithm.digest_size * 8
+ ).encode("ascii")
+ else:
+ alg = algorithm.name.encode("ascii")
+
+ return alg
+
def hash_supported(self, algorithm):
- digest = self._lib.EVP_get_digestbyname(algorithm.name.encode("ascii"))
+ name = self._build_openssl_digest_name(algorithm)
+ digest = self._lib.EVP_get_digestbyname(name)
return digest != self._ffi.NULL
def hmac_supported(self, algorithm):
diff --git a/src/cryptography/hazmat/backends/openssl/hashes.py b/src/cryptography/hazmat/backends/openssl/hashes.py
index 2c8fce1..92ea53b 100644
--- a/src/cryptography/hazmat/backends/openssl/hashes.py
+++ b/src/cryptography/hazmat/backends/openssl/hashes.py
@@ -22,12 +22,12 @@
ctx = self._backend._ffi.gc(
ctx, self._backend._lib.Cryptography_EVP_MD_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),
+ name),
_Reasons.UNSUPPORTED_HASH
)
res = self._backend._lib.EVP_DigestInit_ex(ctx, evp_md,
diff --git a/src/cryptography/hazmat/primitives/hashes.py b/src/cryptography/hazmat/primitives/hashes.py
index 6bc8500..0714c11 100644
--- a/src/cryptography/hazmat/primitives/hashes.py
+++ b/src/cryptography/hazmat/primitives/hashes.py
@@ -161,3 +161,45 @@
name = "md5"
digest_size = 16
block_size = 64
+
+
+@utils.register_interface(HashAlgorithm)
+class BLAKE2b(object):
+ name = "blake2b"
+ _max_digest_size = 64
+ _min_digest_size = 1
+ block_size = 128
+
+ def __init__(self, digest_size):
+ if (
+ digest_size > self._max_digest_size or
+ digest_size < self._min_digest_size
+ ):
+ raise ValueError("Digest size must be {0}-{1}".format(
+ self._min_digest_size, self._max_digest_size)
+ )
+
+ self._digest_size = digest_size
+
+ digest_size = utils.read_only_property("_digest_size")
+
+
+@utils.register_interface(HashAlgorithm)
+class BLAKE2s(object):
+ name = "blake2s"
+ block_size = 64
+ _max_digest_size = 32
+ _min_digest_size = 1
+
+ def __init__(self, digest_size):
+ if (
+ digest_size > self._max_digest_size or
+ digest_size < self._min_digest_size
+ ):
+ raise ValueError("Digest size must be {0}-{1}".format(
+ self._min_digest_size, self._max_digest_size)
+ )
+
+ self._digest_size = digest_size
+
+ digest_size = utils.read_only_property("_digest_size")
diff --git a/tests/hazmat/primitives/test_hash_vectors.py b/tests/hazmat/primitives/test_hash_vectors.py
index c6e9828..8757df2 100644
--- a/tests/hazmat/primitives/test_hash_vectors.py
+++ b/tests/hazmat/primitives/test_hash_vectors.py
@@ -158,3 +158,37 @@
],
hashes.MD5(),
)
+
+
+@pytest.mark.supported(
+ only_if=lambda backend: backend.hash_supported(
+ hashes.BLAKE2b(digest_size=64)),
+ skip_message="Does not support BLAKE2b",
+)
+@pytest.mark.requires_backend_interface(interface=HashBackend)
+class TestBLAKE2b(object):
+ test_b2b = generate_hash_test(
+ load_hash_vectors,
+ os.path.join("hashes", "blake2"),
+ [
+ "blake2b.txt",
+ ],
+ hashes.BLAKE2b(digest_size=64),
+ )
+
+
+@pytest.mark.supported(
+ only_if=lambda backend: backend.hash_supported(
+ hashes.BLAKE2s(digest_size=32)),
+ skip_message="Does not support BLAKE2s",
+)
+@pytest.mark.requires_backend_interface(interface=HashBackend)
+class TestBLAKE2s256(object):
+ test_b2s = generate_hash_test(
+ load_hash_vectors,
+ os.path.join("hashes", "blake2"),
+ [
+ "blake2s.txt",
+ ],
+ hashes.BLAKE2s(digest_size=32),
+ )
diff --git a/tests/hazmat/primitives/test_hashes.py b/tests/hazmat/primitives/test_hashes.py
index a109c21..fe96b32 100644
--- a/tests/hazmat/primitives/test_hashes.py
+++ b/tests/hazmat/primitives/test_hashes.py
@@ -159,6 +159,54 @@
)
+@pytest.mark.supported(
+ only_if=lambda backend: backend.hash_supported(
+ hashes.BLAKE2b(digest_size=64)),
+ skip_message="Does not support BLAKE2b",
+)
+@pytest.mark.requires_backend_interface(interface=HashBackend)
+class TestBLAKE2b(object):
+ test_BLAKE2b = generate_base_hash_test(
+ hashes.BLAKE2b(digest_size=64),
+ digest_size=64,
+ block_size=128,
+ )
+
+ def test_invalid_digest_size(self, backend):
+ with pytest.raises(ValueError):
+ hashes.BLAKE2b(digest_size=65)
+
+ with pytest.raises(ValueError):
+ hashes.BLAKE2b(digest_size=0)
+
+ with pytest.raises(ValueError):
+ hashes.BLAKE2b(digest_size=-1)
+
+
+@pytest.mark.supported(
+ only_if=lambda backend: backend.hash_supported(
+ hashes.BLAKE2s(digest_size=32)),
+ skip_message="Does not support BLAKE2s",
+)
+@pytest.mark.requires_backend_interface(interface=HashBackend)
+class TestBLAKE2s(object):
+ test_BLAKE2s = generate_base_hash_test(
+ hashes.BLAKE2s(digest_size=32),
+ digest_size=32,
+ block_size=64,
+ )
+
+ def test_invalid_digest_size(self, backend):
+ with pytest.raises(ValueError):
+ hashes.BLAKE2s(digest_size=33)
+
+ with pytest.raises(ValueError):
+ hashes.BLAKE2s(digest_size=0)
+
+ with pytest.raises(ValueError):
+ hashes.BLAKE2s(digest_size=-1)
+
+
def test_invalid_backend():
pretend_backend = object()