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

eXtendable Output Functions as first-class citizen #3671

Merged
merged 5 commits into from
Sep 14, 2023
Merged
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
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)));
randombit marked this conversation as resolved.
Show resolved Hide resolved
}

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