Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modernize Key Derivation Functions #4455

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 20 additions & 24 deletions doc/api_ref/kdf.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,36 +34,32 @@ two contexts.
Create a new KDF object. Throws an exception if the named key derivation
function was not available

.. cpp:function:: void KDF::derive_key(std::span<uint8_t> key, \
std::span<const uint8_t> secret, \
std::span<const uint8_t> salt, \
std::span<const uint8_t> label) const

Performs a key derivation using ``secret`` as secret input, and ``salt``,
and ``label`` as deversifiers. The passed ``key`` buffer is fully filled
with key material derived from the inputs.

.. cpp:function:: template<concepts::resizable_byte_buffer T = secure_vector<uint8_t>> \
T derive_key(size_t key_len, \
std::span<const uint8_t> secret, \
std::span<const uint8_t> salt, \
std::span<const uint8_t> label) const
T KDF::derive_key(size_t key_len, \
std::span<const uint8_t> secret, \
std::span<const uint8_t> salt, \
std::span<const uint8_t> label) const

This version is parameterized to the output buffer type, so it can be used
to return a ``std::vector``, a ``secure_vector``, or anything else
satisfying the ``resizable_byte_buffer`` concept.

.. cpp:function:: secure_vector<uint8_t> derive_key( \
const uint8_t secret[], \
size_t secret_len, \
const uint8_t salt[], \
size_t salt_len, \
const uint8_t label[], \
size_t label_len) const

.. cpp:function:: secure_vector<uint8_t> derive_key( \
size_t key_len, const std::vector<uint8_t>& secret, \
const std::vector<uint8_t>& salt, \
const std::vector<uint8_t>& label) const

.. cpp:function:: secure_vector<uint8_t> derive_key( \
size_t key_len, const std::vector<uint8_t>& secret, \
const uint8_t* salt, size_t salt_len) const

.. cpp:function:: secure_vector<uint8_t> derive_key( \
size_t key_len, const uint8_t* secret, size_t secret_len, \
const std::string& salt) const
.. cpp:function:: template<size_t key_len> \
std::array<uint8_t, key_len> KDF::derive_key(std::span<const uint8_t> secret, \
std::span<const uint8_t> salt, \
std::span<const uint8_t> label) const

This version returns the key material as a std::array<> of ``key_len``
bytes.

All variations on the same theme. Deterministically creates a
uniform random value from *secret*, *salt*, and *label*, whose
Expand Down
120 changes: 43 additions & 77 deletions src/lib/kdf/hkdf/hkdf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* HKDF
* (C) 2013,2015,2017 Jack Lloyd
* (C) 2016 René Korthaus, Rohde & Schwarz Cybersecurity
* (C) 2024 René Meusel, Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/
Expand All @@ -11,6 +12,7 @@
#include <botan/exceptn.h>
#include <botan/internal/fmt.h>
#include <botan/internal/loadstor.h>
#include <botan/internal/stl_util.h>

namespace Botan {

Expand All @@ -22,20 +24,16 @@ std::string HKDF::name() const {
return fmt("HKDF({})", m_prf->name());
}

void HKDF::kdf(uint8_t key[],
size_t key_len,
const uint8_t secret[],
size_t secret_len,
const uint8_t salt[],
size_t salt_len,
const uint8_t label[],
size_t label_len) const {
void HKDF::perform_kdf(std::span<uint8_t> key,
std::span<const uint8_t> secret,
std::span<const uint8_t> salt,
std::span<const uint8_t> label) const {
HKDF_Extract extract(m_prf->new_object());
HKDF_Expand expand(m_prf->new_object());
secure_vector<uint8_t> prk(m_prf->output_length());

extract.kdf(prk.data(), prk.size(), secret, secret_len, salt, salt_len, nullptr, 0);
expand.kdf(key, key_len, prk.data(), prk.size(), nullptr, 0, label, label_len);
extract.derive_key(prk, secret, salt, {});
expand.derive_key(key, prk, {}, label);
}

std::unique_ptr<KDF> HKDF_Extract::new_object() const {
Expand All @@ -46,42 +44,31 @@ std::string HKDF_Extract::name() const {
return fmt("HKDF-Extract({})", m_prf->name());
}

void HKDF_Extract::kdf(uint8_t key[],
size_t key_len,
const uint8_t secret[],
size_t secret_len,
const uint8_t salt[],
size_t salt_len,
const uint8_t /*label*/[],
size_t label_len) const {
if(key_len == 0) {
void HKDF_Extract::perform_kdf(std::span<uint8_t> key,
std::span<const uint8_t> secret,
std::span<const uint8_t> salt,
std::span<const uint8_t> label) const {
if(key.empty()) {
return;
}

const size_t prf_output_len = m_prf->output_length();
BOTAN_ARG_CHECK(key.size() <= prf_output_len, "HKDF-Extract maximum output length exceeeded");
BOTAN_ARG_CHECK(label.empty(), "HKDF-Extract does not support a label input");

if(key_len > prf_output_len) {
throw Invalid_Argument("HKDF-Extract maximum output length exceeeded");
}

if(label_len > 0) {
throw Invalid_Argument("HKDF-Extract does not support a label input");
}

if(salt_len == 0) {
if(salt.empty()) {
m_prf->set_key(std::vector<uint8_t>(prf_output_len));
} else {
m_prf->set_key(salt, salt_len);
m_prf->set_key(salt);
}

m_prf->update(secret, secret_len);
m_prf->update(secret);

if(key_len == prf_output_len) {
if(key.size() == prf_output_len) {
m_prf->final(key);
} else {
secure_vector<uint8_t> prk;
m_prf->final(prk);
copy_mem(&key[0], prk.data(), key_len);
const auto prk = m_prf->final();
copy_mem(key, std::span{prk}.first(key.size()));
}
}

Expand All @@ -93,75 +80,54 @@ std::string HKDF_Expand::name() const {
return fmt("HKDF-Expand({})", m_prf->name());
}

void HKDF_Expand::kdf(uint8_t key[],
size_t key_len,
const uint8_t secret[],
size_t secret_len,
const uint8_t salt[],
size_t salt_len,
const uint8_t label[],
size_t label_len) const {
if(key_len == 0) {
void HKDF_Expand::perform_kdf(std::span<uint8_t> key,
std::span<const uint8_t> secret,
std::span<const uint8_t> salt,
std::span<const uint8_t> label) const {
if(key.empty()) {
return;
}

if(key_len > m_prf->output_length() * 255) {
throw Invalid_Argument("HKDF-Expand maximum output length exceeeded");
}

m_prf->set_key(secret, secret_len);
BOTAN_ARG_CHECK(key.size() <= m_prf->output_length() * 255, "HKDF-Expand maximum output length exceeeded");

uint8_t counter = 1;
secure_vector<uint8_t> h;
size_t offset = 0;

while(offset != key_len) {
BufferStuffer k(key);
m_prf->set_key(secret);
for(uint8_t counter = 1; !k.full(); ++counter) {
m_prf->update(h);
m_prf->update(label, label_len);
m_prf->update(salt, salt_len);
m_prf->update(counter++);
m_prf->update(label);
m_prf->update(salt);
m_prf->update(counter);
m_prf->final(h);

const size_t written = std::min(h.size(), key_len - offset);
copy_mem(&key[offset], h.data(), written);
offset += written;
const auto bytes_to_write = std::min(h.size(), k.remaining_capacity());
k.append(std::span{h}.first(bytes_to_write));
}
}

secure_vector<uint8_t> hkdf_expand_label(std::string_view hash_fn,
const uint8_t secret[],
size_t secret_len,
std::span<const uint8_t> secret,
std::string_view label,
const uint8_t hash_val[],
size_t hash_val_len,
std::span<const uint8_t> hash_val,
size_t length) {
BOTAN_ARG_CHECK(length <= 0xFFFF, "HKDF-Expand-Label requested output too large");
BOTAN_ARG_CHECK(label.size() <= 0xFF, "HKDF-Expand-Label label too long");
BOTAN_ARG_CHECK(hash_val_len <= 0xFF, "HKDF-Expand-Label hash too long");

const uint16_t length16 = static_cast<uint16_t>(length);
BOTAN_ARG_CHECK(hash_val.size() <= 0xFF, "HKDF-Expand-Label hash too long");

HKDF_Expand hkdf(MessageAuthenticationCode::create_or_throw(fmt("HMAC({})", hash_fn)));

secure_vector<uint8_t> output(length16);
std::vector<uint8_t> prefix(3 + label.size() + 1);

prefix[0] = get_byte<0>(length16);
prefix[1] = get_byte<1>(length16);
prefix[2] = static_cast<uint8_t>(label.size());

copy_mem(prefix.data() + 3, cast_char_ptr_to_uint8(label.data()), label.size());

prefix[3 + label.size()] = static_cast<uint8_t>(hash_val_len);
const auto prefix = concat<std::vector<uint8_t>>(store_be(static_cast<uint16_t>(length)),
store_be(static_cast<uint8_t>(label.size())),
std::span{cast_char_ptr_to_uint8(label.data()), label.size()},
store_be(static_cast<uint8_t>(hash_val.size())));

/*
* We do something a little dirty here to avoid copying the hash_val,
* making use of the fact that Botan's KDF interface supports label+salt,
* and knowing that our HKDF hashes first param label then param salt.
*/
hkdf.kdf(output.data(), output.size(), secret, secret_len, hash_val, hash_val_len, prefix.data(), prefix.size());

return output;
return hkdf.derive_key(length, secret, hash_val, prefix);
}

} // namespace Botan
47 changes: 17 additions & 30 deletions src/lib/kdf/hkdf/hkdf.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,11 @@ class HKDF final : public KDF {

std::string name() const override;

void kdf(uint8_t key[],
size_t key_len,
const uint8_t secret[],
size_t secret_len,
const uint8_t salt[],
size_t salt_len,
const uint8_t label[],
size_t label_len) const override;
private:
void perform_kdf(std::span<uint8_t> key,
std::span<const uint8_t> secret,
std::span<const uint8_t> salt,
std::span<const uint8_t> label) const override;

private:
std::unique_ptr<MessageAuthenticationCode> m_prf;
Expand All @@ -55,14 +52,11 @@ class HKDF_Extract final : public KDF {

std::string name() const override;

void kdf(uint8_t key[],
size_t key_len,
const uint8_t secret[],
size_t secret_len,
const uint8_t salt[],
size_t salt_len,
const uint8_t label[],
size_t label_len) const override;
private:
void perform_kdf(std::span<uint8_t> key,
std::span<const uint8_t> secret,
std::span<const uint8_t> salt,
std::span<const uint8_t> label) const override;

private:
std::unique_ptr<MessageAuthenticationCode> m_prf;
Expand All @@ -82,14 +76,11 @@ class HKDF_Expand final : public KDF {

std::string name() const override;

void kdf(uint8_t key[],
size_t key_len,
const uint8_t secret[],
size_t secret_len,
const uint8_t salt[],
size_t salt_len,
const uint8_t label[],
size_t label_len) const override;
private:
void perform_kdf(std::span<uint8_t> key,
std::span<const uint8_t> secret,
std::span<const uint8_t> salt,
std::span<const uint8_t> label) const override;

private:
std::unique_ptr<MessageAuthenticationCode> m_prf;
Expand All @@ -99,19 +90,15 @@ class HKDF_Expand final : public KDF {
* HKDF-Expand-Label from TLS 1.3/QUIC
* @param hash_fn the hash to use
* @param secret the secret bits
* @param secret_len the length of secret
* @param label the full label (no "TLS 1.3, " or "tls13 " prefix
* is applied)
* @param hash_val the previous hash value (used for chaining, may be empty)
* @param hash_val_len the length of hash_val
* @param length the desired output length
*/
secure_vector<uint8_t> BOTAN_TEST_API hkdf_expand_label(std::string_view hash_fn,
const uint8_t secret[],
size_t secret_len,
std::span<const uint8_t> secret,
std::string_view label,
const uint8_t hash_val[],
size_t hash_val_len,
std::span<const uint8_t> hash_val,
size_t length);

} // namespace Botan
Expand Down
Loading
Loading