Issue #158: HKDF behavior mismatch when passing in nullptr due to OpenSSL changes.#159
Conversation
|
All contributors have signed the CLA ✍️ ✅ |
jcarrete5
left a comment
There was a problem hiding this comment.
If the issue is caused by passing NULL to EVP_PKEY_CTX_set1_hkdf_salt, can we check for NULL and, if it is NULL, skip calling EVP_PKEY_CTX_set1_hkdf_salt altogether? Do we need to always call this function or can it be omitted when we have no salt to provide?
Example:
if (salt.data() != nullptr && EVP_PKEY_CTX_set1_hkdf_salt(pctx.get(), salt.data(), salt_len) <= 0) {
ERROR("EVP_PKEY_CTX_set1_hkdf_salt failed: salt_len=%d", salt_len);
return false;
}
According to RFC 5869 (HKDF spec): Salt is an optional parameter. If not provided, it defaults to a string of zeros of the hash length (e.g., 32 zero bytes for SHA-256). OpenSSL's behavior: Calling EVP_PKEY_CTX_set1_hkdf_salt() with a zero-length salt (non-NULL pointer, length=0) explicitly sets an empty salt in consequence that brings different security implication--- These produce different cryptographic outputs: #1. Empty salt (length=0): HKDF uses 0 bytes as salt |
For info, the two approaches are cryptographically equivalent: Empty info (length=0): HKDF uses 0 bytes |
|
Hmm, was able to dig up this issue pointing to the potential regression: openssl/openssl#28200 I created a simple test: const auto key_size = 16;
std::vector<uint8_t> salt{};
std::vector<uint8_t> info{};
std::vector<uint8_t> clear_key(key_size);
std::vector<uint8_t> clear_base_key{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
ASSERT_TRUE(hkdf(clear_key, clear_base_key, salt, info, SA_DIGEST_ALGORITHM_SHA256));
const std::vector<uint8_t> expected_key{0x74, 0xb2, 0, 0x5f, 0xb0, 0x20, 0xa3, 0xe0, 0x1b, 0xe2, 0xb2, 0x9d, 0xe4, 0x29, 0xb3, 0xd5};
ASSERT_EQ(clear_key, expected_key);and modified hkdf to call
In all cases, the same key was generated and the test passes except cases 4 and 6 where the test instead fails as expected. Case 4 fails because of the potential regression mentioned earlier, and case 6 fails because the salt used is not the same as in the other cases and therefore causes the generated key to be different. |
@jcarrete5 do you think this is a good unit test to add (I.e. do you want to add it)? The behavior seems like it could change in a future OpenSSL version. |
I don't think so. This is really an openssl test as I'm not calling any secapi-specific things here. I think it should be clear in the code that this is a workaround for what appears to be a regression in openssl. Preferably with a link to the GitHub issue I mentioned. |
I think the behavior before was incorrect, and we should probably keep the unit test updated to the latest. Previously a null would result in a 32-byte salt, whereas now it results in an error, which seems like better behavior. |
This openSSL version 3.6.0 1 Oct 2025, OpenSSL's EVP_PKEY_CTX_set1_hkdf_* callers can behave poorly if passed a
NULL pointer even when the length is zero.