Repository: pyca/cryptography

Issue:
Bug in HKDF?
I think the computation of [`max_length`](https://github.com/pyca/cryptography/blob/66460d8f62b3f27a009bb61be6ce7675c8451b6e/src/cryptography/hazmat/primitives/kdf/hkdf.py#L70) in `src/cryptography/hazmat/primitives/kdf/hkdf.py` is wrong.

[RFC5869](https://tools.ietf.org/html/rfc5869) states on page 3 that the input `L` of the HKDF-Expand function describes the "length of output keying material in octets (<= 255*HashLen)".
An octet consists of 8 bit. 

Currently, `max_length` is computed as:

```
max_length = 255 * (algorithm.digest_size // 8)
```

The problem is, that `algorithm.digest_size` returns the size of the digest in bytes. (There are 8 bits per byte). Therefore, the division by 8 is wrong, and thus, `max_length` is unnecessarily small.

(same applies for the computation of `salt` as well ([line 33](https://github.com/pyca/cryptography/blob/66460d8f62b3f27a009bb61be6ce7675c8451b6e/src/cryptography/hazmat/primitives/kdf/hkdf.py#L33)), in the case where `salt is None`)


Discussion:
Thanks for the report. This is indeed a bug. We should allow longer output lengths. As for salt, that is a bug as well, but (fortuitously) is benign. An explanation:

* We test against a set of vectors that includes tests for the `None` salt case so we knew it was generating proper answers -- how could it be wrong?
* The HMAC RFC (2104) defines what to do when a key is too short (pad with null bytes)
* OpenSSL implements this behavior (https://github.com/openssl/openssl/blob/master/crypto/hmac/hmac.c#L50-L59)

So we passed too few null bytes and OpenSSL obligingly (and in accordance with the spec) padded it out to the correct number of null bytes, thus making the call return the correct value despite the wrong input. (We will still fix it though!)

A PR will be forthcoming.

Resolving patch:
diff --git a/src/cryptography/hazmat/primitives/kdf/hkdf.py b/src/cryptography/hazmat/primitives/kdf/hkdf.py
--- a/src/cryptography/hazmat/primitives/kdf/hkdf.py
+++ b/src/cryptography/hazmat/primitives/kdf/hkdf.py
@@ -67,7 +67,7 @@ def __init__(self, algorithm, length, info, backend):
 
         self._backend = backend
 
-        max_length = 255 * (algorithm.digest_size // 8)
+        max_length = 255 * algorithm.digest_size
 
         if length > max_length:
             raise ValueError(


Test patch:
diff --git a/tests/hazmat/primitives/test_hkdf.py b/tests/hazmat/primitives/test_hkdf.py
--- a/tests/hazmat/primitives/test_hkdf.py
+++ b/tests/hazmat/primitives/test_hkdf.py
@@ -5,6 +5,7 @@
 from __future__ import absolute_import, division, print_function
 
 import binascii
+import os
 
 import pytest
 
@@ -15,13 +16,15 @@
 from cryptography.hazmat.primitives import hashes
 from cryptography.hazmat.primitives.kdf.hkdf import HKDF, HKDFExpand
 
-from ...utils import raises_unsupported_algorithm
+from ...utils import (
+    load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm
+)
 
 
 @pytest.mark.requires_backend_interface(interface=HMACBackend)
 class TestHKDF(object):
     def test_length_limit(self, backend):
-        big_length = 255 * (hashes.SHA256().digest_size // 8) + 1
+        big_length = 255 * hashes.SHA256().digest_size + 1
 
         with pytest.raises(ValueError):
             HKDF(
@@ -153,6 +156,21 @@ def test_derive_short_output(self, backend):
 
         assert hkdf.derive(b"\x01" * 16) == b"gJ\xfb{"
 
+    def test_derive_long_output(self, backend):
+        vector = load_vectors_from_file(
+            os.path.join("KDF", "hkdf-generated.txt"), load_nist_vectors
+        )[0]
+        hkdf = HKDF(
+            hashes.SHA256(),
+            int(vector["l"]),
+            salt=vector["salt"],
+            info=vector["info"],
+            backend=backend
+        )
+        ikm = binascii.unhexlify(vector["ikm"])
+
+        assert hkdf.derive(ikm) == binascii.unhexlify(vector["okm"])
+
 
 @pytest.mark.requires_backend_interface(interface=HMACBackend)
 class TestHKDFExpand(object):