From 004377e62abbff4a8001240e04847bf39ca4cd60 Mon Sep 17 00:00:00 2001 From: dhruv <856960+dhruv@users.noreply.github.com> Date: Thu, 9 Jun 2022 15:00:55 -0700 Subject: [PATCH] Adding forward secure FSChaCha20 --- src/crypto/chacha20.cpp | 28 +++++++++++++- src/crypto/chacha20.h | 48 +++++++++++++++++++++++ src/test/crypto_tests.cpp | 63 +++++++++++++++++++++++++++++++ src/test/fuzz/crypto_chacha20.cpp | 28 ++++++++++++++ 4 files changed, 166 insertions(+), 1 deletion(-) diff --git a/src/crypto/chacha20.cpp b/src/crypto/chacha20.cpp index e44747cccc..a4709d93db 100644 --- a/src/crypto/chacha20.cpp +++ b/src/crypto/chacha20.cpp @@ -5,9 +5,11 @@ // Based on the public domain implementation 'merged' by D. J. Bernstein // See https://cr.yp.to/chacha.html. -#include #include +#include +#include + #include #include @@ -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 input, Span output) +{ + assert(input.size() == output.size()); + c20.Crypt(reinterpret_cast(input.data()), + reinterpret_cast(output.data()), input.size()); + chunk_counter++; + + if (chunk_counter % rekey_interval == 0) { + CommitToKey({(std::byte*)nullptr, 0}); + } +} + +void FSChaCha20::CommitToKey(const Span 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(key.data())); + set_nonce(); +} diff --git a/src/crypto/chacha20.h b/src/crypto/chacha20.h index 006eee7608..179967d43e 100644 --- a/src/crypto/chacha20.h +++ b/src/crypto/chacha20.h @@ -5,11 +5,19 @@ #ifndef BITCOIN_CRYPTO_CHACHA20_H #define BITCOIN_CRYPTO_CHACHA20_H +#include +#include +#include +#include + #include #include #include #include +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 */ @@ -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 key; + HashWriter rekey_hasher; + + void set_nonce() + { + std::array nonce; + WriteLE32(reinterpret_cast(nonce.data()), uint32_t{0}); + WriteLE64(reinterpret_cast(nonce.data()) + 4, chunk_counter / rekey_interval); + c20.SetRFC8439Nonce(nonce); + } + +public: + FSChaCha20() = delete; + FSChaCha20(const std::array& key, + const std::array& rekey_salt, + size_t rekey_interval) + : c20{reinterpret_cast(key.data())}, + rekey_interval{rekey_interval}, + key{key} + { + assert(rekey_interval > 0); + set_nonce(); + rekey_hasher << MakeUCharSpan(rekey_salt); + } + + void CommitToKey(const Span data); + + ~FSChaCha20() + { + memory_cleanse(key.data(), FSCHACHA20_KEYLEN); + } + + void Crypt(Span input, Span output); +}; #endif // BITCOIN_CRYPTO_CHACHA20_H diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index 9055b10165..881ee3487f 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -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 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 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(key.data())}; + + std::vector fsc20_output; + fsc20_output.resize(plaintext.size()); + + std::vector 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(new_key)); + + std::array new_nonce; + WriteLE32(reinterpret_cast(new_nonce.data()), 0); + WriteLE64(reinterpret_cast(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& 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); @@ -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) diff --git a/src/test/fuzz/crypto_chacha20.cpp b/src/test/fuzz/crypto_chacha20.cpp index d29eeb5089..3ba4081bb6 100644 --- a/src/test/fuzz/crypto_chacha20.cpp +++ b/src/test/fuzz/crypto_chacha20.cpp @@ -8,6 +8,8 @@ #include #include +#include +#include #include #include @@ -150,3 +152,29 @@ FUZZ_TARGET(chacha20_split_keystream) FuzzedDataProvider provider{buffer.data(), buffer.size()}; ChaCha20SplitFuzz(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 key; + memcpy(key.data(), key_vec.data(), FSCHACHA20_KEYLEN); + + std::array salt; + memcpy(salt.data(), salt_vec.data(), FSCHACHA20_REKEY_SALT_LEN); + + auto fsc20 = FSChaCha20{key, salt, fuzzed_data_provider.ConsumeIntegralInRange(1, 1024)}; + + LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) + { + auto input = fuzzed_data_provider.ConsumeBytes(fuzzed_data_provider.ConsumeIntegralInRange(0, 4096)); + std::vector output; + output.resize(input.size()); + fsc20.Crypt(input, output); + } +}