Skip to content

Commit

Permalink
Merge pull request #3671 from Rohde-Schwarz/feature/xof-interface
Browse files Browse the repository at this point in the history
eXtendable Output Functions as first-class citizen
  • Loading branch information
reneme authored Sep 14, 2023
2 parents 33dee2f + e499bbf commit 43dcc8a
Show file tree
Hide file tree
Showing 16 changed files with 1,729 additions and 0 deletions.
35 changes: 35 additions & 0 deletions src/cli/speed.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
#include <botan/hash.h>
#endif

#if defined(BOTAN_HAS_XOF)
#include <botan/xof.h>
#endif

#if defined(BOTAN_HAS_CIPHER_MODES)
#include <botan/cipher_mode.h>
#endif
Expand Down Expand Up @@ -383,6 +387,10 @@ class Speed final : public Command {
"Blake2b",
"Whirlpool",

/* XOFs */
"SHAKE-128",
"SHAKE-256",

/* MACs */
"CMAC(AES-128)",
"HMAC(SHA-256)",
Expand Down Expand Up @@ -487,6 +495,12 @@ class Speed final : public Command {
algo, provider, msec, buf_sizes, std::bind(&Speed::bench_hash, this, _1, _2, _3, _4));
}
#endif
#if defined(BOTAN_HAS_XOF)
else if(!Botan::XOF::providers(algo).empty()) {
bench_providers_of<Botan::XOF>(
algo, provider, msec, buf_sizes, std::bind(&Speed::bench_xof, this, _1, _2, _3, _4));
}
#endif
#if defined(BOTAN_HAS_BLOCK_CIPHER)
else if(!Botan::BlockCipher::providers(algo).empty()) {
bench_providers_of<Botan::BlockCipher>(
Expand Down Expand Up @@ -888,6 +902,27 @@ class Speed final : public Command {
}
#endif

#if defined(BOTAN_HAS_XOF)
void bench_xof(Botan::XOF& xof,
const std::string& provider,
const std::chrono::milliseconds runtime,
const std::vector<size_t>& buf_sizes) {
for(auto buf_size : buf_sizes) {
Botan::secure_vector<uint8_t> in = rng().random_vec(buf_size);
Botan::secure_vector<uint8_t> out(buf_size);

auto in_timer = make_timer(xof.name(), in.size(), "input", provider, buf_size);
in_timer->run_until_elapsed(runtime / 2, [&]() { xof.update(in); });

auto out_timer = make_timer(xof.name(), out.size(), "output", provider, buf_size);
out_timer->run_until_elapsed(runtime / 2, [&] { xof.output(out); });

record_result(in_timer);
record_result(out_timer);
}
}
#endif

#if defined(BOTAN_HAS_MAC)
void bench_mac(Botan::MessageAuthenticationCode& mac,
const std::string& provider,
Expand Down
58 changes: 58 additions & 0 deletions src/lib/permutations/keccak_perm/keccak_helpers.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Helper functions to implement Keccak-derived functions from NIST SP.800-185
* (C) 2023 Jack Lloyd
* (C) 2023 René Meusel - Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include <botan/internal/keccak_helpers.h>

#include <botan/internal/bit_ops.h>
#include <botan/internal/loadstor.h>

#include <limits>

namespace Botan {

namespace {

size_t int_encoding_size(uint64_t x) {
BOTAN_ASSERT_NOMSG(x < std::numeric_limits<uint64_t>::max());
return ceil_tobytes(std::max(uint8_t(1), ceil_log2(x + 1)));
}

uint8_t encode(std::span<uint8_t> out, uint64_t x) {
const auto bytes_needed = int_encoding_size(x);
BOTAN_ASSERT_NOMSG(out.size() >= bytes_needed);

std::array<uint8_t, sizeof(x)> bigendian_x;
store_be(x, bigendian_x.data());

auto begin = bigendian_x.begin();
std::advance(begin, sizeof(x) - bytes_needed);
std::copy(begin, bigendian_x.end(), out.begin());

return static_cast<uint8_t>(bytes_needed);
}

} // namespace

std::span<const uint8_t> keccak_int_left_encode(std::span<uint8_t> out, size_t x) {
BOTAN_ASSERT_NOMSG(!out.empty());
out[0] = encode(out.last(out.size() - 1), x);
return out.first(out[0] + 1 /* the length tag */);
}

std::span<const uint8_t> keccak_int_right_encode(std::span<uint8_t> out, size_t x) {
const auto bytes_needed = encode(out, x);
BOTAN_ASSERT_NOMSG(out.size() >= bytes_needed + size_t(1));
out[bytes_needed] = bytes_needed;
return out.first(bytes_needed + 1 /* the length tag */);
}

size_t keccak_int_encoding_size(size_t x) {
return int_encoding_size(x) + 1 /* the length tag */;
}

} // namespace Botan
123 changes: 123 additions & 0 deletions src/lib/permutations/keccak_perm/keccak_helpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Helper functions to implement Keccak-derived functions from NIST SP.800-185
* (C) 2023 Jack Lloyd
* (C) 2023 René Meusel - Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#ifndef BOTAN_KECCAK_HELPERS_H_
#define BOTAN_KECCAK_HELPERS_H_

#include <array>
#include <cstdint>
#include <span>

#include <botan/assert.h>
#include <botan/concepts.h>

namespace Botan {

/**
* Integer encoding defined in NIST SP.800-185 that can be unambiguously
* parsed from the beginning of the string.
*
* This function does not allocate any memory and requires the caller to
* provide a sufficiently large @p buffer. For a given @p x, this will
* need exactly keccak_int_encoding_size() bytes. For an arbitrary @p x
* it will generate keccak_max_int_encoding_size() bytes at most.
*
* @param buffer buffer to write the left-encoding of @p x to.
* It is assumed that the buffer will hold at least
* keccak_int_encoding_size() bytes.
* @param x the integer to be left-encoded
* @return the byte span that represents the bytes written to @p buffer.
*/
BOTAN_TEST_API std::span<const uint8_t> keccak_int_left_encode(std::span<uint8_t> buffer, size_t x);

/**
* Integer encoding defined in NIST SP.800-185 that can be unambiguously
* parsed from the end of the string.
*
* This function does not allocate any memory and requires the caller to
* provide a sufficiently large @p buffer. For a given @p x, this will
* need exactly keccak_int_encoding_size() bytes. For an arbitrary @p x
* it will generate keccak_max_int_encoding_size() bytes at most.
*
* @param out buffer to write the right-encoding of @p x to.
* It is assumed that the buffer will hold at least
* keccak_int_encoding_size() bytes.
* @param x the integer to be right-encoded
* @return the byte span that represents the bytes written to @p buffer.
*/
BOTAN_TEST_API std::span<const uint8_t> keccak_int_right_encode(std::span<uint8_t> out, size_t x);

/**
* @returns the required bytes for encodings of keccak_int_left_encode() or
* keccak_int_right_encode() given an integer @p x
*/
BOTAN_TEST_API size_t keccak_int_encoding_size(size_t x);

/**
* @returns the maximum required bytes for encodings of keccak_int_left_encode() or
* keccak_int_right_encode()
*/
constexpr size_t keccak_max_int_encoding_size() {
return sizeof(size_t) + 1 /* the length tag */;
}

template <typename T>
concept updatable_object = requires(T& a, std::span<const uint8_t> span) { a.update(span); };

/**
* This is a combination of the functions encode_string() and bytepad() defined
* in NIST SP.800-185 Section 2.3. Additionally, the result is directly streamed
* into the provided XOF to avoid unneccessary memory allocation.
*
* @param xof the XOF to absorb the @p byte_strings into
* @param padding_mod the modulus value to create a padding for (NIST calls this 'w')
* @param byte_strings a variable-length list of byte strings to be encoded and
* absorbed into the given @p xof
* @returns the number of bytes absorbed into the @p xof
*/
template <updatable_object T, typename... Ts>
requires(concepts::constructible_from<std::span<const uint8_t>, Ts> && ...)
size_t keccak_absorb_padded_strings_encoding(T& xof, size_t padding_mod, Ts... byte_strings) {
BOTAN_ASSERT_NOMSG(padding_mod > 0);

// used as temporary storage for all integer encodings in this function
std::array<uint8_t, keccak_max_int_encoding_size()> int_encoding_buffer;

// absorbs byte strings and counts the number of absorbed bytes
size_t bytes_absorbed = 0;
auto absorb = [&](std::span<const uint8_t> bytes) {
xof.update(bytes);
bytes_absorbed += bytes.size();
};

// encodes a given string and absorbs it into the XOF straight away
auto encode_string_and_absorb = [&](std::span<const uint8_t> bytes) {
absorb(keccak_int_left_encode(int_encoding_buffer, bytes.size() * 8));
absorb(bytes);
};

// absorbs as many zero-bytes as requested into the XOF
auto absorb_padding = [&](size_t padding_bytes) {
for(size_t i = 0; i < padding_bytes; ++i) {
const uint8_t zero_byte = 0;
absorb({&zero_byte, 1});
}
};

// implementation of bytepad(encode_string(Ts) || ...) that absorbs the result
// staight into the given xof
absorb(keccak_int_left_encode(int_encoding_buffer, padding_mod));
(encode_string_and_absorb(byte_strings), ...);
absorb_padding(padding_mod - (bytes_absorbed % padding_mod));

return bytes_absorbed;
}

} // namespace Botan

#endif
75 changes: 75 additions & 0 deletions src/lib/xof/cshake_xof/cshake_xof.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* cSHAKE-128 and cSHAKE-256 as XOFs
*
* (C) 2016-2023 Jack Lloyd
* 2022-2023 René Meusel - Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include <botan/internal/cshake_xof.h>

#include <botan/exceptn.h>
#include <botan/mem_ops.h>
#include <botan/internal/keccak_helpers.h>
#include <botan/internal/loadstor.h>

namespace Botan {

cSHAKE_XOF::cSHAKE_XOF(size_t capacity, std::vector<uint8_t> function_name) :
m_keccak(capacity, 0b00, 2), m_function_name(std::move(function_name)), m_output_generated(false) {
BOTAN_ASSERT_NOMSG(capacity == 256 || capacity == 512);
}

cSHAKE_XOF::cSHAKE_XOF(size_t capacity, std::span<const uint8_t> function_name) :
cSHAKE_XOF(capacity, std::vector<uint8_t>{function_name.begin(), function_name.end()}) {}

cSHAKE_XOF::cSHAKE_XOF(size_t capacity, std::string_view function_name) :
cSHAKE_XOF(capacity,
std::vector<uint8_t>{cast_char_ptr_to_uint8(function_name.data()),
cast_char_ptr_to_uint8(function_name.data()) + function_name.size()}) {}

void cSHAKE_XOF::clear() {
m_keccak.clear();
m_output_generated = false;
}

std::string cSHAKE_XOF::provider() const {
return m_keccak.provider();
}

size_t cSHAKE_XOF::block_size() const {
return m_keccak.byte_rate();
}

bool cSHAKE_XOF::valid_salt_length(size_t salt_length) const {
// NIST SP.800-185 Section 3.2
// When N and S are both empty strings, cSHAKE(X, L, N, S) is equivalent to
// SHAKE as defined in FIPS 202.
//
// We don't implement the fallback case where N and S are empty. Hence, if
// the function name N was defined as 'empty', a salt must be provided.
return m_function_name.size() + salt_length > 0;
}

void cSHAKE_XOF::start_msg(std::span<const uint8_t> salt, std::span<const uint8_t> key) {
BOTAN_STATE_CHECK(!m_output_generated);
BOTAN_ASSERT_NOMSG(key.empty());
keccak_absorb_padded_strings_encoding(*this, block_size(), m_function_name, salt);
}

void cSHAKE_XOF::add_data(std::span<const uint8_t> input) {
BOTAN_STATE_CHECK(!m_output_generated);
m_keccak.absorb(input);
}

void cSHAKE_XOF::generate_bytes(std::span<uint8_t> output) {
if(!m_output_generated) {
m_output_generated = true;
m_keccak.finish();
}

m_keccak.squeeze(output);
}

} // namespace Botan
Loading

0 comments on commit 43dcc8a

Please sign in to comment.