Skip to content

Commit

Permalink
Adding forward secure FSChaCha20
Browse files Browse the repository at this point in the history
  • Loading branch information
dhruv committed Dec 7, 2022
1 parent ed0e8d8 commit 004377e
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 1 deletion.
28 changes: 27 additions & 1 deletion src/crypto/chacha20.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
// Based on the public domain implementation 'merged' by D. J. Bernstein
// See https://cr.yp.to/chacha.html.

#include <crypto/common.h>
#include <crypto/chacha20.h>

#include <crypto/common.h>
#include <crypto/sha256.h>

#include <algorithm>
#include <string.h>

Expand Down Expand Up @@ -337,3 +339,27 @@ void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes)
m_bufleft = 64 - bytes;
}
}

void FSChaCha20::Crypt(Span<const std::byte> input, Span<std::byte> output)
{
assert(input.size() == output.size());
c20.Crypt(reinterpret_cast<const unsigned char*>(input.data()),
reinterpret_cast<unsigned char*>(output.data()), input.size());
chunk_counter++;

if (chunk_counter % rekey_interval == 0) {
CommitToKey({(std::byte*)nullptr, 0});
}
}

void FSChaCha20::CommitToKey(const Span<const std::byte> data)
{
assert(CSHA256::OUTPUT_SIZE == FSCHACHA20_KEYLEN);
auto hasher = rekey_hasher;
hasher << MakeUCharSpan(data) << MakeUCharSpan(key);
auto new_key = hasher.GetSHA256();
memory_cleanse(key.data(), key.size());
memcpy(key.data(), new_key.data(), FSCHACHA20_KEYLEN);
c20.SetKey32(reinterpret_cast<unsigned char*>(key.data()));
set_nonce();
}
48 changes: 48 additions & 0 deletions src/crypto/chacha20.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,19 @@
#ifndef BITCOIN_CRYPTO_CHACHA20_H
#define BITCOIN_CRYPTO_CHACHA20_H

#include <crypto/common.h>
#include <hash.h>
#include <span.h>
#include <support/cleanse.h>

#include <array>
#include <cstddef>
#include <cstdlib>
#include <stdint.h>

static constexpr size_t FSCHACHA20_KEYLEN = 32; // bytes
static constexpr size_t FSCHACHA20_REKEY_SALT_LEN = 23; // bytes

// classes for ChaCha20 256-bit stream cipher developed by Daniel J. Bernstein
// https://cr.yp.to/chacha/chacha-20080128.pdf */

Expand Down Expand Up @@ -97,4 +105,44 @@ class ChaCha20
void Crypt(const unsigned char* input, unsigned char* output, size_t bytes);
};

class FSChaCha20
{
private:
ChaCha20 c20;
size_t rekey_interval;
uint32_t chunk_counter{0};
std::array<std::byte, FSCHACHA20_KEYLEN> key;
HashWriter rekey_hasher;

void set_nonce()
{
std::array<std::byte, 12> nonce;
WriteLE32(reinterpret_cast<unsigned char*>(nonce.data()), uint32_t{0});
WriteLE64(reinterpret_cast<unsigned char*>(nonce.data()) + 4, chunk_counter / rekey_interval);
c20.SetRFC8439Nonce(nonce);
}

public:
FSChaCha20() = delete;
FSChaCha20(const std::array<std::byte, FSCHACHA20_KEYLEN>& key,
const std::array<std::byte, FSCHACHA20_REKEY_SALT_LEN>& rekey_salt,
size_t rekey_interval)
: c20{reinterpret_cast<const unsigned char*>(key.data())},
rekey_interval{rekey_interval},
key{key}
{
assert(rekey_interval > 0);
set_nonce();
rekey_hasher << MakeUCharSpan(rekey_salt);
}

void CommitToKey(const Span<const std::byte> data);

~FSChaCha20()
{
memory_cleanse(key.data(), FSCHACHA20_KEYLEN);
}

void Crypt(Span<const std::byte> input, Span<std::byte> output);
};
#endif // BITCOIN_CRYPTO_CHACHA20_H
63 changes: 63 additions & 0 deletions src/test/crypto_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,57 @@ static void TestChaCha20(const std::string &hex_message, const std::string &hexk
}
}

static void TestFSChaCha20(const std::string& hex_plaintext, const std::string& hexkey, const std::string& hex_rekey_salt, size_t rekey_interval, const std::string& ciphertext_after_rotation)
{
auto key_vec = ParseHex(hexkey);
BOOST_CHECK_EQUAL(FSCHACHA20_KEYLEN, key_vec.size());
std::array<std::byte, FSCHACHA20_KEYLEN> key;
memcpy(key.data(), key_vec.data(), FSCHACHA20_KEYLEN);

auto salt_vec = ParseHex(hex_rekey_salt);
BOOST_CHECK_EQUAL(FSCHACHA20_REKEY_SALT_LEN, salt_vec.size());
std::array<std::byte, FSCHACHA20_REKEY_SALT_LEN> rekey_salt;
memcpy(rekey_salt.data(), salt_vec.data(), FSCHACHA20_REKEY_SALT_LEN);

auto plaintext = ParseHex(hex_plaintext);

auto fsc20 = FSChaCha20{key, rekey_salt, rekey_interval};
auto c20 = ChaCha20{reinterpret_cast<const unsigned char*>(key.data())};

std::vector<std::byte> fsc20_output;
fsc20_output.resize(plaintext.size());

std::vector<unsigned char> c20_output;
c20_output.resize(plaintext.size());

for (size_t i = 0; i < rekey_interval; i++) {
fsc20.Crypt(MakeByteSpan(plaintext), fsc20_output);
c20.Crypt(plaintext.data(), c20_output.data(), plaintext.size());
BOOST_CHECK_EQUAL(0, memcmp(c20_output.data(), fsc20_output.data(), plaintext.size()));
}

// At the rotation interval, the outputs will no longer match
fsc20.Crypt(MakeByteSpan(plaintext), fsc20_output);
c20.Crypt(plaintext.data(), c20_output.data(), plaintext.size());
BOOST_CHECK(memcmp(c20_output.data(), fsc20_output.data(), plaintext.size()) != 0);

unsigned char new_key[FSCHACHA20_KEYLEN];
auto hasher = CSHA256().Write(UCharCast(rekey_salt.data()), rekey_salt.size());
hasher.Write(UCharCast(key.data()), key.size()).Finalize(new_key);
c20.SetKey32(reinterpret_cast<unsigned char*>(new_key));

std::array<std::byte, 12> new_nonce;
WriteLE32(reinterpret_cast<unsigned char*>(new_nonce.data()), 0);
WriteLE64(reinterpret_cast<unsigned char*>(new_nonce.data()) + 4, 1);
c20.SetRFC8439Nonce(new_nonce);

// Outputs should match again after simulating key rotation
c20.Crypt(plaintext.data(), c20_output.data(), plaintext.size());
BOOST_CHECK_EQUAL(0, memcmp(c20_output.data(), fsc20_output.data(), plaintext.size()));

BOOST_CHECK_EQUAL(HexStr(fsc20_output), ciphertext_after_rotation);
}

static void TestChaCha20RFC8439(const std::string& hex_key, const std::array<std::byte, 12>& nonce, uint32_t seek, const std::string& hex_expected_keystream, const std::string& hex_input, const std::string& hex_expected_output)
{
auto key = ParseHex(hex_key);
Expand Down Expand Up @@ -658,6 +709,18 @@ BOOST_AUTO_TEST_CASE(chacha20_testvector)
rfc8439_nonce2, 0, "ecfa254f845f647473d3cb140da9e87606cb33066c447b87bc2666dde3fbb739", "", "");
TestChaCha20RFC8439("1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0",
rfc8439_nonce2, 0, "965e3bc6f9ec7ed9560808f4d229f94b137ff275ca9b3fcbdd59deaad23310ae", "", "");

// Forward secure ChaCha20
TestFSChaCha20("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000", 256,
"4cf63894c8507adffa163a742db5fdcc9a861187b1c94a5a4c002d1bb7f2223c");
TestFSChaCha20("01", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"0000000000000000000000000000000000000000000000", 5, "e0");
TestFSChaCha20("e93fdb5c762804b9a706816aca31e35b11d2aa3080108ef46a5b1f1508819c0a",
"8ec4c3ccdaea336bdeb245636970be01266509b33f3d2642504eaf412206207a",
"8bb571662db12d38ee4e2630d4434f6f626cb0e6007e3c", 4096,
"30a36b4833331bf83bc16fcff408c771044e239b80472edd2e89ba9eb1845f34");
}

BOOST_AUTO_TEST_CASE(chacha20_midblock)
Expand Down
28 changes: 28 additions & 0 deletions src/test/fuzz/crypto_chacha20.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include <test/fuzz/util.h>
#include <test/util/xoroshiro128plusplus.h>

#include <array>
#include <cstddef>
#include <cstdint>
#include <vector>

Expand Down Expand Up @@ -150,3 +152,29 @@ FUZZ_TARGET(chacha20_split_keystream)
FuzzedDataProvider provider{buffer.data(), buffer.size()};
ChaCha20SplitFuzz<false>(provider);
}

FUZZ_TARGET(crypto_fschacha20)
{
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};

auto key_vec = ConsumeFixedLengthByteVector(fuzzed_data_provider, FSCHACHA20_KEYLEN);
key_vec.resize(FSCHACHA20_KEYLEN);
auto salt_vec = ConsumeFixedLengthByteVector(fuzzed_data_provider, FSCHACHA20_KEYLEN);
salt_vec.resize(FSCHACHA20_KEYLEN);

std::array<std::byte, FSCHACHA20_KEYLEN> key;
memcpy(key.data(), key_vec.data(), FSCHACHA20_KEYLEN);

std::array<std::byte, FSCHACHA20_REKEY_SALT_LEN> salt;
memcpy(salt.data(), salt_vec.data(), FSCHACHA20_REKEY_SALT_LEN);

auto fsc20 = FSChaCha20{key, salt, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 1024)};

LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000)
{
auto input = fuzzed_data_provider.ConsumeBytes<std::byte>(fuzzed_data_provider.ConsumeIntegralInRange(0, 4096));
std::vector<std::byte> output;
output.resize(input.size());
fsc20.Crypt(input, output);
}
}

0 comments on commit 004377e

Please sign in to comment.