Skip to content

Commit

Permalink
SP800-56Cr2 one-step kdm with kmac
Browse files Browse the repository at this point in the history
  • Loading branch information
FAlbertDev committed Jun 14, 2024
1 parent 927aab8 commit 58d1004
Show file tree
Hide file tree
Showing 6 changed files with 540 additions and 50 deletions.
5 changes: 3 additions & 2 deletions doc/api_ref/kdf.rst
Original file line number Diff line number Diff line change
Expand Up @@ -158,19 +158,20 @@ e.g. ``X9.42-PRF(KeyWrap.TripleDES)``, ``X9.42-PRF(1.2.840.113549.1.9.16.3.7)``
SP800-56A
~~~~~~~~~~

KDF from NIST SP 800-56A.
KDF from NIST SP 800-56Ar2 or One-Step KDF of SP 800-56Cr2.

Available if ``BOTAN_HAS_SP800_56A`` is defined.

Algorithm specification names:

- ``SP800-56A(<HashFunction>)``, e.g. ``SP800-56A(SHA-256)``
- ``SP800-56A(HMAC(<HashFunction>))``, e.g. ``SP800-56A(HMAC(SHA-256))``
- ``SP800-56A(KMAC-128)`` or ``SP800-56A(KMAC-256)``

SP800-56C
~~~~~~~~~~

KDF from NIST SP 800-56C.
Two-Step KDF from NIST SP 800-56Cr2.

Available if ``BOTAN_HAS_SP800_56C`` is defined.

Expand Down
6 changes: 6 additions & 0 deletions src/lib/kdf/kdf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ std::unique_ptr<KDF> KDF::create(std::string_view algo_spec, std::string_view pr
if(auto hash = HashFunction::create(req.arg(0))) {
return std::make_unique<SP800_56A_Hash>(std::move(hash));
}
if(req.arg(0) == "KMAC-128") {
return std::make_unique<SP800_56A_KMAC128>();
}
if(req.arg(0) == "KMAC-256") {
return std::make_unique<SP800_56A_KMAC256>();
}
if(auto mac = MessageAuthenticationCode::create(req.arg(0))) {
return std::make_unique<SP800_56A_HMAC>(std::move(mac));
}
Expand Down
1 change: 1 addition & 0 deletions src/lib/kdf/sp800_56a/info.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ name -> "NIST SP800-56A"

<requires>
hmac
kmac
</requires>
167 changes: 122 additions & 45 deletions src/lib/kdf/sp800_56a/sp800_56a.cpp
Original file line number Diff line number Diff line change
@@ -1,51 +1,78 @@
/*
* KDF defined in NIST SP 800-56a (Approved Alternative 1)
* KDF defined in NIST SP 800-56a revision 2 (Single-step key-derivation function)
* or in NIST SP 800-56C revision 2 (Section 4 - One-Step KDM)
*
* (C) 2017 Ribose Inc. Written by Krzysztof Kwiatkowski.
* (C) 2024 Fabian Albert - Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include <botan/internal/sp800_56a.h>

#include <botan/exceptn.h>
#include <botan/internal/bit_ops.h>
#include <botan/internal/fmt.h>
#include <botan/internal/kmac.h>

#include <functional>

namespace Botan {

namespace {
/**
* @brief One-Step Key Derivation as defined in SP800-56Cr2 Section 4
*/
void kdm_internal(std::span<uint8_t> output_buffer,
std::span<const uint8_t> z,
std::span<const uint8_t> fixed_info,
Buffered_Computation& h,
const std::function<void(Buffered_Computation*)>& reset_h_callback) {
size_t l = output_buffer.size() * 8;
// 1. If L > 0, then set reps = ceil(L / H_outputBits); otherwise,
// output an error indicator and exit this process without
// performing the remaining actions (i.e., omit steps 2 through 8).
BOTAN_ARG_CHECK(l > 0, "Zero KDM output length");
size_t reps = ceil_division(l, h.output_length() * 8);

// 2. If reps > (2^32 − 1), then output an error indicator and exit this
// process without performing the remaining actions
// (i.e., omit steps 3 through 8).
BOTAN_ARG_CHECK(reps <= 0xFFFFFFFF, "Too large KDM output length");

// 3. Initialize a big-endian 4-byte unsigned integer counter as
// 0x00000000, corresponding to a 32-bit binary representation of
// the number zero.
uint32_t counter = 0;

// 4. If counter || Z || FixedInfo is more than max_H_inputBits bits
// long, then output an error indicator and exit this process
// without performing any of the remaining actions (i.e., omit
// steps 5 through 8). => SHA3 and KMAC are unlimited

// 5. Initialize Result(0) as an empty bit string
// (i.e., the null string).
secure_vector<uint8_t> result;

template <class AuxiliaryFunction_t>
void SP800_56A_kdf(AuxiliaryFunction_t& auxfunc,
uint8_t key[],
size_t key_len,
const uint8_t secret[],
size_t secret_len,
const uint8_t label[],
size_t label_len) {
const uint64_t kRepsUpperBound = (1ULL << 32);

const size_t digest_len = auxfunc.output_length();

const size_t reps = key_len / digest_len + ((key_len % digest_len) ? 1 : 0);

if(reps >= kRepsUpperBound) {
// See SP-800-56A, point 5.8.1
throw Invalid_Argument("SP800-56A KDF requested output too large");
// 6. For i = 1 to reps, do the following:
for(size_t i = 1; i <= reps; i++) {
// 6.1. Increment counter by 1.
counter++;
// Reset the hash/MAC object. For MAC, also set the key (salt) and IV.
reset_h_callback(&h);

// 6.2 Compute K(i) = H(counter || Z || FixedInfo).
h.update_be(counter);
h.update(z);
h.update(fixed_info);
auto k_i = h.final();

// 6.3. Set Result(i) = Result(i−1) || K(i).
result.insert(result.end(), k_i.begin(), k_i.end());
}

uint32_t counter = 1;
secure_vector<uint8_t> result;
for(size_t i = 0; i < reps; i++) {
auxfunc.update_be(counter++);
auxfunc.update(secret, secret_len);
auxfunc.update(label, label_len);
auxfunc.final(result);

const size_t offset = digest_len * i;
const size_t len = std::min(result.size(), key_len - offset);
copy_mem(&key[offset], result.data(), len);
}
// 7. Set DerivedKeyingMaterial equal to the leftmost L bits of Result(reps).
copy_mem(output_buffer, std::span(result).subspan(0, output_buffer.size()));
}

} // namespace
Expand All @@ -59,12 +86,13 @@ void SP800_56A_Hash::kdf(uint8_t key[],
const uint8_t label[],
size_t label_len) const {
BOTAN_UNUSED(salt);
BOTAN_ARG_CHECK(salt_len == 0, "SP800_56A_Hash does not support a non-empty salt");

if(salt_len > 0) {
throw Invalid_Argument("SP800_56A_Hash does not support a non-empty salt");
}

SP800_56A_kdf(*m_hash, key, key_len, secret, secret_len, label, label_len);
kdm_internal({key, key_len}, {secret, secret_len}, {label, label_len}, *m_hash, [](Buffered_Computation* kdf) {
HashFunction* hash = dynamic_cast<HashFunction*>(kdf);
BOTAN_ASSERT_NONNULL(hash);
hash->clear();
});
}

std::string SP800_56A_Hash::name() const {
Expand All @@ -78,7 +106,7 @@ std::unique_ptr<KDF> SP800_56A_Hash::new_object() const {
SP800_56A_HMAC::SP800_56A_HMAC(std::unique_ptr<MessageAuthenticationCode> mac) : m_mac(std::move(mac)) {
// TODO: we need a MessageAuthenticationCode::is_hmac
if(!m_mac->name().starts_with("HMAC(")) {
throw Algorithm_Not_Found("Only HMAC can be used with KDF SP800-56A");
throw Algorithm_Not_Found("Only HMAC can be used with SP800_56A_HMAC");
}
}

Expand All @@ -90,15 +118,20 @@ void SP800_56A_HMAC::kdf(uint8_t key[],
size_t salt_len,
const uint8_t label[],
size_t label_len) const {
/*
* SP 800-56A specifies if the salt is empty then a block of zeros
* equal to the hash's underlying block size are used. However this
* is equivalent to setting a zero-length key, so the same call
* works for either case.
*/
m_mac->set_key(salt, salt_len);

SP800_56A_kdf(*m_mac, key, key_len, secret, secret_len, label, label_len);
kdm_internal({key, key_len}, {secret, secret_len}, {label, label_len}, *m_mac, [&](Buffered_Computation* kdf) {
MessageAuthenticationCode* kdf_mac = dynamic_cast<MessageAuthenticationCode*>(kdf);
BOTAN_ASSERT_NONNULL(kdf_mac);
kdf_mac->clear();
// 4.1 Option 2 and 3 - An implementation dependent byte string, salt,
// whose (non-null) value may be optionally provided in
// OtherInput, serves as the HMAC#/KMAC# key ..

// SP 800-56A specifies if the salt is empty then a block of zeros
// equal to the hash's underlying block size are used. However this
// is equivalent to setting a zero-length key, so the same call
// works for either case.
kdf_mac->set_key(std::span{salt, salt_len});
});
}

std::string SP800_56A_HMAC::name() const {
Expand All @@ -109,4 +142,48 @@ std::unique_ptr<KDF> SP800_56A_HMAC::new_object() const {
return std::make_unique<SP800_56A_HMAC>(m_mac->new_object());
}

// Option 3 - KMAC
void SP800_56A_KMAC_Abstract::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 {
auto mac = create_kmac_instance(key_len);
kdm_internal({key, key_len}, {secret, secret_len}, {label, label_len}, *mac, [&](Buffered_Computation* kdf) {
MessageAuthenticationCode* kdf_mac = dynamic_cast<MessageAuthenticationCode*>(kdf);
BOTAN_ASSERT_NONNULL(kdf_mac);
kdf_mac->clear();
// 4.1 Option 2 and 3 - An implementation dependent byte string, salt,
// whose (non-null) value may be optionally provided in
// OtherInput, serves as the HMAC#/KMAC# key ..

// SP 800-56A specifies if the salt is empty then a block of zeros
// equal to the hash's underlying block size are used. However this
// is equivalent to setting a zero-length key, so the same call
// works for either case.
kdf_mac->set_key(std::span{salt, salt_len});

// 4.1 Option 2 and 3 - An implementation dependent byte string, salt,
// whose (non-null) value may be optionally provided in
// OtherInput, serves as the HMAC#/KMAC# key ...
kdf_mac->set_key(std::span{salt, salt_len});

// 4.1 Option 3 - The "customization string" S shall be the byte string
// 01001011 || 01000100 || 01000110, which represents the sequence
// of characters 'K', 'D', and 'F' in 8-bit ASCII.
kdf_mac->start(std::array<uint8_t, 3>{'K', 'D', 'F'});
});
}

std::unique_ptr<MessageAuthenticationCode> SP800_56A_KMAC128::create_kmac_instance(size_t output_byte_len) const {
return std::make_unique<KMAC128>(output_byte_len * 8);
}

std::unique_ptr<MessageAuthenticationCode> SP800_56A_KMAC256::create_kmac_instance(size_t output_byte_len) const {
return std::make_unique<KMAC256>(output_byte_len * 8);
}

} // namespace Botan
69 changes: 66 additions & 3 deletions src/lib/kdf/sp800_56a/sp800_56a.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
/*
* KDF defined in NIST SP 800-56a revision 2 (Single-step key-derivation function)
* or in NIST SP 800-56C revision 2 (Section 4 - One-Step KDM)
*
* (C) 2017 Ribose Inc. Written by Krzysztof Kwiatkowski.
* (C) 2024 Fabian Albert - Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/
Expand All @@ -16,7 +18,7 @@
namespace Botan {

/**
* NIST SP 800-56A KDF using hash function
* NIST SP 800-56Cr2 One-Step KDF using hash function
* @warning This KDF ignores the provided salt value
*/
class SP800_56A_Hash final : public KDF {
Expand All @@ -26,7 +28,7 @@ class SP800_56A_Hash final : public KDF {
std::unique_ptr<KDF> new_object() const override;

/**
* Derive a key using the SP800-56A KDF.
* Derive a key using the SP800-56Cr2 One-Step KDF.
*
* The implementation hard codes the context value for the
* expansion step to the empty string.
Expand Down Expand Up @@ -61,7 +63,7 @@ class SP800_56A_Hash final : public KDF {
};

/**
* NIST SP 800-56A KDF using HMAC
* NIST SP800-56Cr2 One-Step KDF using HMAC
*/
class SP800_56A_HMAC final : public KDF {
public:
Expand Down Expand Up @@ -104,6 +106,67 @@ class SP800_56A_HMAC final : public KDF {
std::unique_ptr<MessageAuthenticationCode> m_mac;
};

/**
* NIST SP800-56Cr2 One-Step KDF using KMAC (Abstract class)
*/
class SP800_56A_KMAC_Abstract : public KDF {
public:
/**
* Derive a key using the SP800-56A KDF.
*
* The implementation hard codes the context value for the
* expansion step to the empty string.
*
* @param key derived keying material K_M
* @param key_len the desired output length in bytes
* @param secret shared secret Z
* @param secret_len size of Z in bytes
* @param salt ignored
* @param salt_len ignored
* @param label label for the expansion step
* @param label_len size of label in bytes
*
* @throws Invalid_Argument key_len > 2^32 or MAC is not a HMAC
*/
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;

protected:
virtual std::unique_ptr<MessageAuthenticationCode> create_kmac_instance(size_t output_byte_len) const = 0;
};

/**
* NIST SP800-56Cr2 One-Step KDF using KMAC-128
*/
class SP800_56A_KMAC128 final : public SP800_56A_KMAC_Abstract {
public:
std::string name() const override { return "SP800-56A(KMAC-128)"; }

std::unique_ptr<KDF> new_object() const override { return std::make_unique<SP800_56A_KMAC128>(); }

protected:
std::unique_ptr<MessageAuthenticationCode> create_kmac_instance(size_t output_byte_len) const override;
};

/**
* NIST SP800-56Cr2 One-Step KDF using KMAC-256
*/
class SP800_56A_KMAC256 final : public SP800_56A_KMAC_Abstract {
public:
std::string name() const override { return "SP800-56A(KMAC-256)"; }

std::unique_ptr<KDF> new_object() const override { return std::make_unique<SP800_56A_KMAC256>(); }

protected:
std::unique_ptr<MessageAuthenticationCode> create_kmac_instance(size_t output_byte_len) const override;
};

} // namespace Botan

#endif
342 changes: 342 additions & 0 deletions src/tests/data/kdf/sp800_56a.vec

Large diffs are not rendered by default.

0 comments on commit 58d1004

Please sign in to comment.