Skip to content
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
22 changes: 17 additions & 5 deletions doc/api_ref/rng.rst
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ It can be instantiated with any HMAC but is typically used with
SHA-256, SHA-384, or SHA-512, as these are the hash functions approved
for this use by NIST.

HMAC-DRBG is implemented against the NIST SP800-90A specification, as
stated above and provides stable output across botan releases regarding
identical seeds (e.g. in KATs or unit tests).

.. note::
There is no reason to use this class directly unless your application
requires HMAC-DRBG with specific parameters or options. Usually this
Expand Down Expand Up @@ -190,21 +194,29 @@ for this use by NIST.
ChaCha_RNG
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This is a very fast userspace PRNG based on ChaCha20 and HMAC(SHA-256). The key
for ChaCha is derived by hashing entropy inputs with HMAC. Then the ChaCha
This is a very fast userspace PRNG based on ChaCha20 and HMAC(SHA-512). The key
and IV for ChaCha are derived by hashing entropy inputs with HMAC. Then the ChaCha
keystream generator is run, first to generate the new HMAC key (used for any
future entropy additions), then the desired RNG outputs.

Please note, that you should not rely on the stability of this RNG's outputs
regarding identical seeds across versions. The output changed in version 3.10.0
and will likely change again at any future time. Use ``HMAC_DRBG`` if this kind
of stability should be necessary for your application scenario.

This RNG composes two primitives thought to be secure (ChaCha and HMAC) in a
simple and well studied way (the extract-then-expand paradigm), but is still an
ad-hoc and non-standard construction. It is included because it is roughly 20x
faster then HMAC_DRBG (basically running as fast as ChaCha can generate
keystream bits), and certain applications need access to a very fast RNG.

One thing applications using ``ChaCha_RNG`` need to be aware of is that for
performance reasons, no backtracking resistance is implemented in the RNG
design. An attacker who recovers the ``ChaCha_RNG`` state can recover the output
backwards in time to the last rekey and forwards to the next rekey.
performance reasons, no backtracking resistance is enabled in the RNG by default.
An attacker who recovers the ``ChaCha_RNG`` state can recover the output
backwards in time to the last rekey and forwards to the next rekey under these
circumstances. You can enable backtracking resistance by setting ``fast_key_erasure``
to ``true`` in the respective constructor of ``ChaCha_RNG``. With this setting enabled,
the ChaCha key gets overwritten after every generate request made to the RNG.

An explicit reseeding (:cpp:func:`RandomNumberGenerator::add_entropy`) or
providing any input to the RNG
Expand Down
3 changes: 3 additions & 0 deletions src/cli/perf_rng.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ class PerfTest_Rng final : public PerfTest {
// Provide a dummy seed
Botan::ChaCha_RNG chacha_rng(Botan::secure_vector<uint8_t>(32));
bench_rng(config, chacha_rng, "ChaCha_RNG");

Botan::ChaCha_RNG chacha_rng_fke(Botan::secure_vector<uint8_t>(32), true);
bench_rng(config, chacha_rng_fke, "ChaCha_RNG (with key erasure)");
#endif

#if defined(BOTAN_HAS_AUTO_SEEDING_RNG)
Expand Down
68 changes: 44 additions & 24 deletions src/lib/rng/chacha_rng/chacha_rng.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,48 +8,57 @@
#include <botan/chacha_rng.h>

#include <botan/assert.h>
#include <botan/internal/stl_util.h>

namespace Botan {

ChaCha_RNG::ChaCha_RNG() {
m_hmac = MessageAuthenticationCode::create_or_throw("HMAC(SHA-256)");
m_chacha = StreamCipher::create_or_throw("ChaCha(20)");
ChaCha_RNG::ChaCha_RNG(bool fast_key_erasure) :
m_hmac(MessageAuthenticationCode::create_or_throw(hmac_algo)),
m_chacha(StreamCipher::create_or_throw(stream_cipher_algo)),
m_fast_key_erasure(fast_key_erasure) {
clear();
}

ChaCha_RNG::ChaCha_RNG(std::span<const uint8_t> seed) {
m_hmac = MessageAuthenticationCode::create_or_throw("HMAC(SHA-256)");
m_chacha = StreamCipher::create_or_throw("ChaCha(20)");
clear();
ChaCha_RNG::ChaCha_RNG(std::span<const uint8_t> seed, bool fast_key_erasure) : ChaCha_RNG(fast_key_erasure) {
add_entropy(seed);
}

ChaCha_RNG::ChaCha_RNG(RandomNumberGenerator& underlying_rng, size_t reseed_interval) :
Stateful_RNG(underlying_rng, reseed_interval) {
m_hmac = MessageAuthenticationCode::create_or_throw("HMAC(SHA-256)");
m_chacha = StreamCipher::create_or_throw("ChaCha(20)");
ChaCha_RNG::ChaCha_RNG(RandomNumberGenerator& underlying_rng, size_t reseed_interval, bool fast_key_erasure) :
Stateful_RNG(underlying_rng, reseed_interval),
m_hmac(MessageAuthenticationCode::create_or_throw(hmac_algo)),
m_chacha(StreamCipher::create_or_throw(stream_cipher_algo)),
m_fast_key_erasure(fast_key_erasure) {
clear();
}

ChaCha_RNG::ChaCha_RNG(RandomNumberGenerator& underlying_rng,
Entropy_Sources& entropy_sources,
size_t reseed_interval) :
Stateful_RNG(underlying_rng, entropy_sources, reseed_interval) {
m_hmac = MessageAuthenticationCode::create_or_throw("HMAC(SHA-256)");
m_chacha = StreamCipher::create_or_throw("ChaCha(20)");
size_t reseed_interval,
bool fast_key_erasure) :
Stateful_RNG(underlying_rng, entropy_sources, reseed_interval),
m_hmac(MessageAuthenticationCode::create_or_throw(hmac_algo)),
m_chacha(StreamCipher::create_or_throw(stream_cipher_algo)),
m_fast_key_erasure(fast_key_erasure) {
clear();
}

ChaCha_RNG::ChaCha_RNG(Entropy_Sources& entropy_sources, size_t reseed_interval) :
Stateful_RNG(entropy_sources, reseed_interval) {
m_hmac = MessageAuthenticationCode::create_or_throw("HMAC(SHA-256)");
m_chacha = StreamCipher::create_or_throw("ChaCha(20)");
ChaCha_RNG::ChaCha_RNG(Entropy_Sources& entropy_sources, size_t reseed_interval, bool fast_key_erasure) :
Stateful_RNG(entropy_sources, reseed_interval),
m_hmac(MessageAuthenticationCode::create_or_throw(hmac_algo)),
m_chacha(StreamCipher::create_or_throw(stream_cipher_algo)),
m_fast_key_erasure(fast_key_erasure) {
clear();
}

void ChaCha_RNG::clear_state() {
m_hmac->set_key(std::vector<uint8_t>(m_hmac->output_length(), 0x00));
m_chacha->set_key(m_hmac->final());
update_chacha_state(m_hmac->final());
}

void ChaCha_RNG::update_chacha_state(std::span<const uint8_t> key_material) {
BufferSlicer bs(key_material);
m_chacha->set_key(bs.take<chacha_key_len>());
m_chacha->set_iv(bs.take<chacha_iv_len>());
}

void ChaCha_RNG::generate_output(std::span<uint8_t> output, std::span<const uint8_t> input) {
Expand All @@ -59,17 +68,28 @@ void ChaCha_RNG::generate_output(std::span<uint8_t> output, std::span<const uint
update(input);
}

// Always generate next key material from the first blocks generated by a key
// as it removes a positional argument from a possible analysis.
const auto key_material = m_chacha->keystream_bytes(m_fast_key_erasure ? chacha_key_len + chacha_iv_len : 0);

m_chacha->write_keystream(output);

// optionally overwrite key after each output operation for backtracking resistance
if(m_fast_key_erasure) {
update_chacha_state(key_material);
}
}

void ChaCha_RNG::update(std::span<const uint8_t> input) {
m_hmac->update(input);
m_chacha->set_key(m_hmac->final());
const auto mac_key = m_chacha->keystream_bytes(m_hmac->output_length());
m_hmac->set_key(mac_key);
update_chacha_state(m_hmac->process(input));
m_hmac->set_key(m_chacha->keystream_bytes(m_hmac->output_length()));
}

size_t ChaCha_RNG::security_level() const {
/*
* as we use a 64 bit nonce as extended key and a wide enough hash
* function with SHA-512, could also be set to 256 + 64 = 320
*/
return 256;
}

Expand Down
80 changes: 72 additions & 8 deletions src/lib/rng/chacha_rng/chacha_rng.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,64 @@ class Entropy_Sources;
* ChaCha_RNG is a very fast but completely ad-hoc RNG created by
* creating a 256-bit random value and using it as a key for ChaCha20.
*
* The RNG maintains two 256-bit keys, one for HMAC_SHA256 (HK) and the
* other for ChaCha20 (CK). To compute a new key in response to
* The RNG maintains a 512-bit and a 256-bit key, one for HMAC_SHA512 (HK)
* and the other for ChaCha20 (CK). To compute a new key in response to
* reseeding request or add_entropy calls, ChaCha_RNG computes
* CK' = HMAC_SHA256(HK, input_material)
* CK' = HMAC_SHA512(HK, input_material)
* Then a new HK' is computed by running ChaCha20 with the new key to
* output 32 bytes:
* HK' = ChaCha20(CK')
*
* Now output can be produced by continuing to produce output with ChaCha20
* under CK'
*
* If fast key erasure rekeying is set in the constructor, CK gets overwritten
* by additional output from ChaCha20 after each generate operation.
* CK' = ChaCha20(CK)
* This costs performance, but can be used when backtracking resistance
* of the internal state is desired (e.g. for usage in DRG.3 or DRT.1
* context of the German AIS 20/31 scheme).
*
* The first HK (before seeding occurs) is taken as the all zero value.
*
* @warning This RNG construction is probably fine but is non-standard.
* The primary reason to use it is in cases where the other RNGs are
* not fast enough.
*
* # Short rationale of design choices:
* - (re-)seeding with HMAC(SHA-512) has the advantage, that inputs
* do not need to have a fixed length and full entropy over a uniform
* distribution, they just need to contain enough entropy (e.g. more
* than 240 bit min-entropy). Using SHA-512 over SHA-256 as the underlying
* primitive has the advantage of a wider internal width and less
* entropy loss when hashing.
* - Using ChaCha as stream cipher has the advantage of no entropy loss
* regarding its seed due to being a random permutation for each key.
* Furthermore ChaCha has a 512 bit block width, which shifts block
* collisions in a very unlikely range (regarding output block count).
* - Using ChaCha(20) instead of ChaCha(8) or ChaCha(12) has the
* advantage of being a conservative choice also taken by the
* Linux kernel, where it is already accepted as a secure CSPRNG
* implementation by many people and organizations.
* - Providing optional fast key erasure is necessary to reach
* backtracking resistance of the internal state. This costs
* performance depending on the request sizes of the user.
* It's of course more expensive to rekey on every 4 byte output,
* than let's say 1024 byte buffers.
* Because of these performance reasons, it has to be enabled explicitly.
* - Also set nonce/IV of ChaCha when (re-)seeding and rekeying, to effectively
* extend effective internal high entropy state by 64 bit.
*/
class BOTAN_PUBLIC_API(2, 3) ChaCha_RNG final : public Stateful_RNG {
private: // constants
static constexpr std::string_view stream_cipher_algo = "ChaCha(20)";
static constexpr std::string_view hmac_algo = "HMAC(SHA-512)";

// use maximum key length providing 256 bit security
static constexpr size_t chacha_key_len = 32;
// use "classic" 8 byte nonce as extended key material
static constexpr size_t chacha_iv_len = 8;

public:
/**
* Automatic reseeding is disabled completely, as it has no access to
Expand All @@ -46,8 +86,12 @@ class BOTAN_PUBLIC_API(2, 3) ChaCha_RNG final : public Stateful_RNG {
* If a fork is detected, the RNG will be unable to reseed itself
* in response. In this case, an exception will be thrown rather
* than generating duplicated output.
*
* @param fast_key_erasure overwrite state after each operation
* for backtracking resistance, costs performance depending
* on request size mix, deactivated by default
*/
ChaCha_RNG();
explicit ChaCha_RNG(bool fast_key_erasure = false);

/**
* Provide an initial seed to the RNG, without providing an
Expand All @@ -60,8 +104,11 @@ class BOTAN_PUBLIC_API(2, 3) ChaCha_RNG final : public Stateful_RNG {
* than generating duplicated output.
*
* @param seed the seed material, should be at least 256 bits
* @param fast_key_erasure overwrite state after each operation
* for backtracking resistance, costs performance depending
* on request size mix, deactivated by default
*/
BOTAN_FUTURE_EXPLICIT ChaCha_RNG(std::span<const uint8_t> seed);
BOTAN_FUTURE_EXPLICIT ChaCha_RNG(std::span<const uint8_t> seed, bool fast_key_erasure = false);

/**
* Automatic reseeding from @p underlying_rng will take place after
Expand All @@ -71,9 +118,13 @@ class BOTAN_PUBLIC_API(2, 3) ChaCha_RNG final : public Stateful_RNG {
* to perform the periodic reseeding
* @param reseed_interval specifies a limit of how many times
* the RNG will be called before automatic reseeding is performed
* @param fast_key_erasure overwrite state after each operation
* for backtracking resistance, costs performance depending
* on request size mix, deactivated by default
*/
BOTAN_FUTURE_EXPLICIT ChaCha_RNG(RandomNumberGenerator& underlying_rng,
size_t reseed_interval = RandomNumberGenerator::DefaultReseedInterval);
size_t reseed_interval = RandomNumberGenerator::DefaultReseedInterval,
bool fast_key_erasure = false);

/**
* Automatic reseeding from @p entropy_sources will take place after
Expand All @@ -82,9 +133,13 @@ class BOTAN_PUBLIC_API(2, 3) ChaCha_RNG final : public Stateful_RNG {
* @param entropy_sources will be polled to perform reseeding periodically
* @param reseed_interval specifies a limit of how many times
* the RNG will be called before automatic reseeding is performed.
* @param fast_key_erasure overwrite state after each operation
* for backtracking resistance, costs performance depending
* on request size mix, deactivated by default
*/
BOTAN_FUTURE_EXPLICIT ChaCha_RNG(Entropy_Sources& entropy_sources,
size_t reseed_interval = RandomNumberGenerator::DefaultReseedInterval);
size_t reseed_interval = RandomNumberGenerator::DefaultReseedInterval,
bool fast_key_erasure = false);

/**
* Automatic reseeding from @p underlying_rng and @p entropy_sources
Expand All @@ -96,26 +151,35 @@ class BOTAN_PUBLIC_API(2, 3) ChaCha_RNG final : public Stateful_RNG {
* @param entropy_sources will be polled to perform reseeding periodically
* @param reseed_interval specifies a limit of how many times
* the RNG will be called before automatic reseeding is performed.
* @param fast_key_erasure overwrite state after each operation
* for backtracking resistance, costs performance depending
* on request size mix, deactivated by default
*/
ChaCha_RNG(RandomNumberGenerator& underlying_rng,
Entropy_Sources& entropy_sources,
size_t reseed_interval = RandomNumberGenerator::DefaultReseedInterval);
size_t reseed_interval = RandomNumberGenerator::DefaultReseedInterval,
bool fast_key_erasure = false);

std::string name() const override { return "ChaCha_RNG"; }

size_t security_level() const override;

size_t max_number_of_bytes_per_request() const override { return 0; }

bool fast_key_erasure() const { return m_fast_key_erasure; }

private:
void update(std::span<const uint8_t> input) override;

void generate_output(std::span<uint8_t> output, std::span<const uint8_t> input) override;

void clear_state() override;

void update_chacha_state(std::span<const uint8_t> key_material);

std::unique_ptr<MessageAuthenticationCode> m_hmac;
std::unique_ptr<StreamCipher> m_chacha;
bool m_fast_key_erasure;
};

} // namespace Botan
Expand Down
2 changes: 1 addition & 1 deletion src/lib/rng/chacha_rng/info.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ name -> "ChaCha RNG"

<requires>
hmac
sha2_32
sha2_64
chacha
stateful_rng
</requires>
Expand Down
Loading
Loading