aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPieter Wuille <pieter@wuille.net>2023-06-28 18:20:30 -0400
committerPieter Wuille <pieter@wuille.net>2023-07-26 16:55:05 -0400
commit0fee267792eb8cbdd48ad78f1712420b5d8d905b (patch)
treebf815576f3aedf2b0ab5f3a98b6fd577110f2bc0
parent9ff0768bdcca06836ccc673eacfa648e801930cb (diff)
downloadbitcoin-0fee267792eb8cbdd48ad78f1712420b5d8d905b.tar.xz
crypto: add FSChaCha20, a rekeying wrapper around ChaCha20
This adds the FSChaCha20 stream cipher as specified in BIP324, a wrapper around the ChaCha20 stream cipher (specified in RFC8439 section 2.4) which automatically rekeys every N messages, and manages the nonces used for encryption. Co-authored-by: dhruv <856960+dhruv@users.noreply.github.com>
-rw-r--r--src/crypto/chacha20.cpp41
-rw-r--r--src/crypto/chacha20.h49
-rw-r--r--src/test/crypto_tests.cpp54
-rw-r--r--src/test/fuzz/crypto_chacha20.cpp20
4 files changed, 164 insertions, 0 deletions
diff --git a/src/crypto/chacha20.cpp b/src/crypto/chacha20.cpp
index fafd783ab1..469b280494 100644
--- a/src/crypto/chacha20.cpp
+++ b/src/crypto/chacha20.cpp
@@ -7,6 +7,7 @@
#include <crypto/common.h>
#include <crypto/chacha20.h>
+#include <support/cleanse.h>
#include <algorithm>
#include <string.h>
@@ -42,6 +43,11 @@ ChaCha20Aligned::ChaCha20Aligned()
memset(input, 0, sizeof(input));
}
+ChaCha20Aligned::~ChaCha20Aligned()
+{
+ memory_cleanse(input, sizeof(input));
+}
+
ChaCha20Aligned::ChaCha20Aligned(const unsigned char* key32)
{
SetKey32(key32);
@@ -318,3 +324,38 @@ void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes)
m_bufleft = 64 - bytes;
}
}
+
+ChaCha20::~ChaCha20()
+{
+ memory_cleanse(m_buffer, sizeof(m_buffer));
+}
+
+FSChaCha20::FSChaCha20(Span<const std::byte> key, uint32_t rekey_interval) noexcept :
+ m_chacha20(UCharCast(key.data())), m_rekey_interval(rekey_interval)
+{
+ assert(key.size() == KEYLEN);
+}
+
+void FSChaCha20::Crypt(Span<const std::byte> input, Span<std::byte> output) noexcept
+{
+ assert(input.size() == output.size());
+
+ // Invoke internal stream cipher for actual encryption/decryption.
+ m_chacha20.Crypt(UCharCast(input.data()), UCharCast(output.data()), input.size());
+
+ // Rekey after m_rekey_interval encryptions/decryptions.
+ if (++m_chunk_counter == m_rekey_interval) {
+ // Get new key from the stream cipher.
+ std::byte new_key[KEYLEN];
+ m_chacha20.Keystream(UCharCast(new_key), sizeof(new_key));
+ // Update its key.
+ m_chacha20.SetKey32(UCharCast(new_key));
+ // Wipe the key (a copy remains inside m_chacha20, where it'll be wiped on the next rekey
+ // or on destruction).
+ memory_cleanse(new_key, sizeof(new_key));
+ // Set the nonce for the new section of output.
+ m_chacha20.Seek64({0, ++m_rekey_counter}, 0);
+ // Reset the chunk counter.
+ m_chunk_counter = 0;
+ }
+}
diff --git a/src/crypto/chacha20.h b/src/crypto/chacha20.h
index f2ec21d82e..d1b2094e7e 100644
--- a/src/crypto/chacha20.h
+++ b/src/crypto/chacha20.h
@@ -5,6 +5,10 @@
#ifndef BITCOIN_CRYPTO_CHACHA20_H
#define BITCOIN_CRYPTO_CHACHA20_H
+#include <span.h>
+
+#include <array>
+#include <cstddef>
#include <cstdlib>
#include <stdint.h>
#include <utility>
@@ -29,6 +33,9 @@ public:
/** Initialize a cipher with specified 32-byte key. */
ChaCha20Aligned(const unsigned char* key32);
+ /** Destructor to clean up private memory. */
+ ~ChaCha20Aligned();
+
/** set 32-byte key. */
void SetKey32(const unsigned char* key32);
@@ -72,6 +79,9 @@ public:
/** Initialize a cipher with specified 32-byte key. */
ChaCha20(const unsigned char* key32) : m_aligned(key32) {}
+ /** Destructor to clean up private memory. */
+ ~ChaCha20();
+
/** set 32-byte key. */
void SetKey32(const unsigned char* key32)
{
@@ -98,4 +108,43 @@ public:
void Crypt(const unsigned char* input, unsigned char* output, size_t bytes);
};
+/** Forward-secure ChaCha20
+ *
+ * This implements a stream cipher that automatically transitions to a new stream with a new key
+ * and new nonce after a predefined number of encryptions or decryptions.
+ *
+ * See BIP324 for details.
+ */
+class FSChaCha20
+{
+private:
+ /** Internal stream cipher. */
+ ChaCha20 m_chacha20;
+
+ /** The number of encryptions/decryptions before a rekey happens. */
+ const uint32_t m_rekey_interval;
+
+ /** The number of encryptions/decryptions since the last rekey. */
+ uint32_t m_chunk_counter{0};
+
+ /** The number of rekey operations that have happened. */
+ uint64_t m_rekey_counter{0};
+
+public:
+ /** Length of keys expected by the constructor. */
+ static constexpr unsigned KEYLEN = 32;
+
+ // No copy or move to protect the secret.
+ FSChaCha20(const FSChaCha20&) = delete;
+ FSChaCha20(FSChaCha20&&) = delete;
+ FSChaCha20& operator=(const FSChaCha20&) = delete;
+ FSChaCha20& operator=(FSChaCha20&&) = delete;
+
+ /** Construct an FSChaCha20 cipher that rekeys every rekey_interval Crypt() calls. */
+ FSChaCha20(Span<const std::byte> key, uint32_t rekey_interval) noexcept;
+
+ /** Encrypt or decrypt a chunk. */
+ void Crypt(Span<const std::byte> input, Span<std::byte> output) noexcept;
+};
+
#endif // BITCOIN_CRYPTO_CHACHA20_H
diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp
index f62072f5bb..d50596c204 100644
--- a/src/test/crypto_tests.cpp
+++ b/src/test/crypto_tests.cpp
@@ -182,6 +182,46 @@ static void TestChaCha20(const std::string &hex_message, const std::string &hexk
}
}
+static void TestFSChaCha20(const std::string& hex_plaintext, const std::string& hexkey, uint32_t rekey_interval, const std::string& ciphertext_after_rotation)
+{
+ auto key = ParseHex<std::byte>(hexkey);
+ BOOST_CHECK_EQUAL(FSChaCha20::KEYLEN, key.size());
+
+ auto plaintext = ParseHex<std::byte>(hex_plaintext);
+
+ auto fsc20 = FSChaCha20{key, rekey_interval};
+ auto c20 = ChaCha20{UCharCast(key.data())};
+
+ std::vector<std::byte> fsc20_output;
+ fsc20_output.resize(plaintext.size());
+
+ std::vector<std::byte> c20_output;
+ c20_output.resize(plaintext.size());
+
+ for (size_t i = 0; i < rekey_interval; i++) {
+ fsc20.Crypt(plaintext, fsc20_output);
+ c20.Crypt(UCharCast(plaintext.data()), UCharCast(c20_output.data()), plaintext.size());
+ BOOST_CHECK(c20_output == fsc20_output);
+ }
+
+ // At the rotation interval, the outputs will no longer match
+ fsc20.Crypt(plaintext, fsc20_output);
+ auto c20_copy = c20;
+ c20.Crypt(UCharCast(plaintext.data()), UCharCast(c20_output.data()), plaintext.size());
+ BOOST_CHECK(c20_output != fsc20_output);
+
+ std::byte new_key[FSChaCha20::KEYLEN];
+ c20_copy.Keystream(UCharCast(new_key), sizeof(new_key));
+ c20.SetKey32(UCharCast(new_key));
+ c20.Seek64({0, 1}, 0);
+
+ // Outputs should match again after simulating key rotation
+ c20.Crypt(UCharCast(plaintext.data()), UCharCast(c20_output.data()), plaintext.size());
+ BOOST_CHECK(c20_output == fsc20_output);
+
+ BOOST_CHECK_EQUAL(HexStr(fsc20_output), ciphertext_after_rotation);
+}
+
static void TestPoly1305(const std::string &hexmessage, const std::string &hexkey, const std::string& hextag)
{
auto key = ParseHex<std::byte>(hexkey);
@@ -696,6 +736,20 @@ BOOST_AUTO_TEST_CASE(chacha20_testvector)
"fd565dea5addbdb914208fde7950f23e0385f9a727143f6a6ac51d84b1c0fb3e"
"2e3b00b63d6841a1cc6d1538b1d3a74bef1eb2f54c7b7281e36e484dba89b351"
"c8f572617e61e342879f211b0e4c515df50ea9d0771518fad96cd0baee62deb6");
+
+ // Forward secure ChaCha20
+ TestFSChaCha20("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ 256,
+ "a93df4ef03011f3db95f60d996e1785df5de38fc39bfcb663a47bb5561928349");
+ TestFSChaCha20("01",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+ 5,
+ "ea");
+ TestFSChaCha20("e93fdb5c762804b9a706816aca31e35b11d2aa3080108ef46a5b1f1508819c0a",
+ "8ec4c3ccdaea336bdeb245636970be01266509b33f3d2642504eaf412206207a",
+ 4096,
+ "8bfaa4eacff308fdb4a94a5ff25bd9d0c1f84b77f81239f67ff39d6e1ac280c9");
}
BOOST_AUTO_TEST_CASE(chacha20_midblock)
diff --git a/src/test/fuzz/crypto_chacha20.cpp b/src/test/fuzz/crypto_chacha20.cpp
index 63c7bf3b45..76370b4e57 100644
--- a/src/test/fuzz/crypto_chacha20.cpp
+++ b/src/test/fuzz/crypto_chacha20.cpp
@@ -8,6 +8,8 @@
#include <test/fuzz/util.h>
#include <test/util/xoroshiro128plusplus.h>
+#include <array>
+#include <cstddef>
#include <cstdint>
#include <vector>
@@ -151,3 +153,21 @@ 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 = fuzzed_data_provider.ConsumeBytes<std::byte>(FSChaCha20::KEYLEN);
+ key.resize(FSChaCha20::KEYLEN);
+
+ auto fsc20 = FSChaCha20{key, fuzzed_data_provider.ConsumeIntegralInRange<uint32_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);
+ }
+}