diff --git a/src/bench/chacha_poly_aead.cpp b/src/bench/chacha_poly_aead.cpp index e994279a4d..719ca2bee7 100644 --- a/src/bench/chacha_poly_aead.cpp +++ b/src/bench/chacha_poly_aead.cpp @@ -3,59 +3,43 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include #include #include #include // for the POLY1305_TAGLEN constant #include -#include -#include +#include /* Number of bytes to process per iteration */ static constexpr uint64_t BUFFER_SIZE_TINY = 64; static constexpr uint64_t BUFFER_SIZE_SMALL = 256; static constexpr uint64_t BUFFER_SIZE_LARGE = 1024 * 1024; -static const unsigned char k1[32] = {0}; -static const unsigned char k2[32] = {0}; - -static ChaCha20Poly1305AEAD aead(k1, 32, k2, 32); +static std::vector zero_key(32, 0x00); static void CHACHA20_POLY1305_AEAD(benchmark::Bench& bench, size_t buffersize, bool include_decryption) { - std::vector in(buffersize + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0); - std::vector out(buffersize + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0); - uint64_t seqnr_payload = 0; - uint64_t seqnr_aad = 0; - int aad_pos = 0; - uint32_t len = 0; + ChaCha20Poly1305AEAD aead_in(zero_key, zero_key); + ChaCha20Poly1305AEAD aead_out(zero_key, zero_key); + + auto plaintext_len = buffersize + CHACHA20_POLY1305_AEAD_AAD_LEN; + auto ciphertext_len = plaintext_len + POLY1305_TAGLEN; + + std::vector in(plaintext_len, 0); + std::vector out(ciphertext_len, 0); + bench.batch(buffersize).unit("byte").run([&] { // encrypt or decrypt the buffer with a static key - const bool crypt_ok_1 = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, out.data(), out.size(), in.data(), buffersize, true); + const bool crypt_ok_1 = aead_out.Crypt(out.data(), ciphertext_len, in.data(), plaintext_len, true); assert(crypt_ok_1); if (include_decryption) { - // if we decrypt, include the GetLength - const bool get_length_ok = aead.GetLength(&len, seqnr_aad, aad_pos, in.data()); - assert(get_length_ok); - const bool crypt_ok_2 = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, out.data(), out.size(), in.data(), buffersize, true); + // if we decrypt, we need to decrypt the length first and advance the keystream + (void)aead_in.DecryptLength(out.data()); + const bool crypt_ok_2 = aead_in.Crypt(in.data(), plaintext_len, out.data(), ciphertext_len, false); assert(crypt_ok_2); } - - // increase main sequence number - seqnr_payload++; - // increase aad position (position in AAD keystream) - aad_pos += CHACHA20_POLY1305_AEAD_AAD_LEN; - if (aad_pos + CHACHA20_POLY1305_AEAD_AAD_LEN > CHACHA20_ROUND_OUTPUT) { - aad_pos = 0; - seqnr_aad++; - } - if (seqnr_payload + 1 == std::numeric_limits::max()) { - // reuse of nonce+key is okay while benchmarking. - seqnr_payload = 0; - seqnr_aad = 0; - aad_pos = 0; - } }); } diff --git a/src/crypto/chacha_poly_aead.cpp b/src/crypto/chacha_poly_aead.cpp index 19087b7d75..88cce94b0e 100644 --- a/src/crypto/chacha_poly_aead.cpp +++ b/src/crypto/chacha_poly_aead.cpp @@ -4,15 +4,14 @@ #include +#include #include #include #include +#include #include -#include -#include - #ifndef HAVE_TIMINGSAFE_BCMP int timingsafe_bcmp(const unsigned char* b1, const unsigned char* b2, size_t n) @@ -27,20 +26,53 @@ int timingsafe_bcmp(const unsigned char* b1, const unsigned char* b2, size_t n) #endif // TIMINGSAFE_BCMP -ChaCha20Poly1305AEAD::ChaCha20Poly1305AEAD(const unsigned char* K_1, size_t K_1_len, const unsigned char* K_2, size_t K_2_len) +ChaCha20Forward4064::ChaCha20Forward4064(const Span key) { - assert(K_1_len == CHACHA20_POLY1305_AEAD_KEY_LEN); - assert(K_2_len == CHACHA20_POLY1305_AEAD_KEY_LEN); + assert(key.size() == 32); + m_ctx.SetKey(key.data(), key.size()); + + // Set IV to the initial sequence number 0. + m_ctx.SetIV(m_seqnr); - m_chacha_header.SetKey(K_1, CHACHA20_POLY1305_AEAD_KEY_LEN); - m_chacha_main.SetKey(K_2, CHACHA20_POLY1305_AEAD_KEY_LEN); + // precompute first chunk of keystream + m_ctx.Keystream(m_keystream, KEYSTREAM_SIZE); +} - // set the cached sequence number to uint64 max which hints for an unset cache. - // we can't hit uint64 max since the rekey rule (which resets the sequence number) is 1GB - m_cached_aad_seqnr = std::numeric_limits::max(); +void ChaCha20Forward4064::Crypt(const unsigned char* input, unsigned char* output, size_t bytes) +{ + size_t message_pos = 0; + + // TODO: speedup with a block approach (rather then looping over every byte) + while (bytes > message_pos) { + output[message_pos] = input[message_pos] ^ m_keystream[m_keystream_pos]; + m_keystream_pos++; + message_pos++; + if (m_keystream_pos == KEYSTREAM_SIZE - CHACHA20_POLY1305_AEAD_KEY_LEN) { + // we reached the end of the keystream + // rekey with the remaining and last 32 bytes and precompute the next 4096 bytes + m_ctx.SetKey(&m_keystream[m_keystream_pos], CHACHA20_POLY1305_AEAD_KEY_LEN); + + // m_ctx.SetKey() sets both IV and counter to zero, but we need the IV to increment. + m_ctx.SetIV(++m_seqnr); + m_ctx.Keystream(m_keystream, KEYSTREAM_SIZE); + // reset keystream position + m_keystream_pos = 0; + } + } } -bool ChaCha20Poly1305AEAD::Crypt(uint64_t seqnr_payload, uint64_t seqnr_aad, int aad_pos, unsigned char* dest, size_t dest_len /* length of the output buffer for sanity checks */, const unsigned char* src, size_t src_len, bool is_encrypt) +ChaCha20Forward4064::~ChaCha20Forward4064() +{ + memory_cleanse(m_keystream, KEYSTREAM_SIZE); +} + +ChaCha20Poly1305AEAD::ChaCha20Poly1305AEAD(const Span K_F, const Span K_V) : m_chacha_header(K_F), m_chacha_main(K_V) +{ + assert(K_F.size() == CHACHA20_POLY1305_AEAD_KEY_LEN); + assert(K_V.size() == CHACHA20_POLY1305_AEAD_KEY_LEN); +} + +bool ChaCha20Poly1305AEAD::Crypt(unsigned char* dest, size_t dest_len /* length of the output buffer for sanity checks */, const unsigned char* src, size_t src_len, bool is_encrypt) { // check buffer boundaries if ( @@ -53,18 +85,24 @@ bool ChaCha20Poly1305AEAD::Crypt(uint64_t seqnr_payload, uint64_t seqnr_aad, int unsigned char expected_tag[POLY1305_TAGLEN], poly_key[POLY1305_KEYLEN]; memset(poly_key, 0, sizeof(poly_key)); - m_chacha_main.SetIV(seqnr_payload); - // block counter 0 for the poly1305 key - // use lower 32bytes for the poly1305 key - // (throws away 32 unused bytes (upper 32) from this ChaCha20 round) - m_chacha_main.Seek(0); - m_chacha_main.Crypt(poly_key, poly_key, sizeof(poly_key)); + // 1. AAD (the encrypted packet length), use the header-keystream + if (is_encrypt) { + m_chacha_header.Crypt(src, dest, 3); + } else { + // we must use ChaCha20Poly1305AEAD::DecryptLength before calling ChaCha20Poly1305AEAD::Crypt + // thus the length has already been decrypted, avoid doing it again and messing up the keystream position + // keep the encrypted version of the AAD to not break verifying the MAC + memcpy(dest, src, 3); + } - // if decrypting, verify the tag prior to decryption + // 2. derive the poly1305 key from the header-keystream + m_chacha_header.Crypt(poly_key, poly_key, sizeof(poly_key)); + + // 3. if decrypting, verify the MAC prior to decryption if (!is_encrypt) { - const unsigned char* tag = src + src_len - POLY1305_TAGLEN; - poly1305_auth(expected_tag, src, src_len - POLY1305_TAGLEN, poly_key); + const unsigned char* tag = src + src_len - POLY1305_TAGLEN; //the MAC appended in the package + poly1305_auth(expected_tag, src, src_len - POLY1305_TAGLEN, poly_key); //the calculated MAC // constant time compare the calculated MAC with the provided MAC if (timingsafe_bcmp(expected_tag, tag, POLY1305_TAGLEN) != 0) { @@ -73,54 +111,35 @@ bool ChaCha20Poly1305AEAD::Crypt(uint64_t seqnr_payload, uint64_t seqnr_aad, int return false; } memory_cleanse(expected_tag, sizeof(expected_tag)); - // MAC has been successfully verified, make sure we don't covert it in decryption + // MAC has been successfully verified, make sure we don't decrypt it src_len -= POLY1305_TAGLEN; } - // calculate and cache the next 64byte keystream block if requested sequence number is not yet the cache - if (m_cached_aad_seqnr != seqnr_aad) { - m_cached_aad_seqnr = seqnr_aad; - m_chacha_header.SetIV(seqnr_aad); - m_chacha_header.Seek(0); - m_chacha_header.Keystream(m_aad_keystream_buffer, CHACHA20_ROUND_OUTPUT); - } - // crypt the AAD (3 bytes message length) with given position in AAD cipher instance keystream - dest[0] = src[0] ^ m_aad_keystream_buffer[aad_pos]; - dest[1] = src[1] ^ m_aad_keystream_buffer[aad_pos + 1]; - dest[2] = src[2] ^ m_aad_keystream_buffer[aad_pos + 2]; - - // Set the playload ChaCha instance block counter to 1 and crypt the payload - m_chacha_main.Seek(1); + // 4. crypt the payload m_chacha_main.Crypt(src + CHACHA20_POLY1305_AEAD_AAD_LEN, dest + CHACHA20_POLY1305_AEAD_AAD_LEN, src_len - CHACHA20_POLY1305_AEAD_AAD_LEN); - // If encrypting, calculate and append tag + // 5. If encrypting, calculate and append MAC if (is_encrypt) { - // the poly1305 tag expands over the AAD (3 bytes length) & encrypted payload + // the poly1305 MAC expands over the AAD (3 bytes length) & encrypted payload poly1305_auth(dest + src_len, dest, src_len, poly_key); } - // cleanse no longer required MAC and polykey + // cleanse no longer required polykey memory_cleanse(poly_key, sizeof(poly_key)); return true; } -bool ChaCha20Poly1305AEAD::GetLength(uint32_t* len24_out, uint64_t seqnr_aad, int aad_pos, const uint8_t* ciphertext) +uint32_t ChaCha20Poly1305AEAD::DecryptLength(const uint8_t* ciphertext) { - // enforce valid aad position to avoid accessing outside of the 64byte keystream cache - // (there is space for 21 times 3 bytes) - assert(aad_pos >= 0 && aad_pos < CHACHA20_ROUND_OUTPUT - CHACHA20_POLY1305_AEAD_AAD_LEN); - if (m_cached_aad_seqnr != seqnr_aad) { - // we need to calculate the 64 keystream bytes since we reached a new aad sequence number - m_cached_aad_seqnr = seqnr_aad; - m_chacha_header.SetIV(seqnr_aad); // use LE for the nonce - m_chacha_header.Seek(0); // block counter 0 - m_chacha_header.Keystream(m_aad_keystream_buffer, CHACHA20_ROUND_OUTPUT); // write keystream to the cache - } - - // decrypt the ciphertext length by XORing the right position of the 64byte keystream cache with the ciphertext - *len24_out = (ciphertext[0] ^ m_aad_keystream_buffer[aad_pos + 0]) | - (ciphertext[1] ^ m_aad_keystream_buffer[aad_pos + 1]) << 8 | - (ciphertext[2] ^ m_aad_keystream_buffer[aad_pos + 2]) << 16; - - return true; + // decrypt the length + // once we hit the re-key limit in the keystream (byte 4064) we can't go back to decrypt the length again + // we need to keep the decrypted and the encrypted version in memory to check the max packet length and + // to have the capability to verify the MAC + unsigned char length_buffer[CHACHA20_POLY1305_AEAD_AAD_LEN]; + m_chacha_header.Crypt(ciphertext, length_buffer, sizeof(length_buffer)); + + uint32_t len24_out = (length_buffer[0]) | + (length_buffer[1]) << 8 | + (length_buffer[2]) << 16; + return len24_out; } diff --git a/src/crypto/chacha_poly_aead.h b/src/crypto/chacha_poly_aead.h index 5d57b5a5e2..1c3a94d51e 100644 --- a/src/crypto/chacha_poly_aead.h +++ b/src/crypto/chacha_poly_aead.h @@ -6,13 +6,12 @@ #define BITCOIN_CRYPTO_CHACHA_POLY_AEAD_H #include - -#include +#include +#include static constexpr int CHACHA20_POLY1305_AEAD_KEY_LEN = 32; static constexpr int CHACHA20_POLY1305_AEAD_AAD_LEN = 3; /* 3 bytes length */ static constexpr int CHACHA20_ROUND_OUTPUT = 64; /* 64 bytes per round */ -static constexpr int AAD_PACKAGES_PER_ROUND = 21; /* 64 / 3 round down*/ /* A AEAD class for ChaCha20-Poly1305@bitcoin. * @@ -40,107 +39,109 @@ static constexpr int AAD_PACKAGES_PER_ROUND = 21; /* 64 / 3 round down*/ * output from the key exchange. Each key (K_1 and K_2) are used by two separate * instances of chacha20. * - * The instance keyed by K_1 is a stream cipher that is used only to encrypt the 3 - * byte packet length field and has its own sequence number. The second instance, - * keyed by K_2, is used in conjunction with poly1305 to build an AEAD - * (Authenticated Encryption with Associated Data) that is used to encrypt and - * authenticate the entire packet. + * The instance keyed by K_1 is a stream cipher that is used for the per-message + * metadata, specifically for the poly1305 authentication key as well as for the + * length encryption. The second instance, keyed by K_2, is used to encrypt the + * entire payload. * * Two separate cipher instances are used here so as to keep the packet lengths - * confidential but not create an oracle for the packet payload cipher by - * decrypting and using the packet length prior to checking the MAC. By using an - * independently-keyed cipher instance to encrypt the length, an active attacker - * seeking to exploit the packet input handling as a decryption oracle can learn - * nothing about the payload contents or its MAC (assuming key derivation, - * ChaCha20 and Poly1305 are secure). - * - * The AEAD is constructed as follows: for each packet, generate a Poly1305 key by - * taking the first 256 bits of ChaCha20 stream output generated using K_2, an IV - * consisting of the packet sequence number encoded as an LE uint64 and a ChaCha20 - * block counter of zero. The K_2 ChaCha20 block counter is then set to the - * little-endian encoding of 1 (i.e. {1, 0, 0, 0, 0, 0, 0, 0}) and this instance - * is used for encryption of the packet payload. + * confidential (best effort; for passive observing) but not create an oracle for + * the packet payload cipher by decrypting and using the packet length prior to + * checking the MAC. By using an independently-keyed cipher instance to encrypt + * the length, an active attacker seeking to exploit the packet input handling as + * a decryption oracle can learn nothing about the payload contents or its MAC + * (assuming key derivation, ChaCha20 and Poly1305 are secure). Active observers + * can still obtain the message length (ex. active ciphertext bit flipping or + * traffic semantics analysis) * - * ==== Packet Handling ==== + * The AEAD is constructed as follows: generate two ChaCha20 streams, initially + * keyed with K_1 and K_2 and sequence number 0 as IV and a block counter of 0. + * After encrypting 4064 bytes, the following 32 bytes are used to + * re-key the ChaCha20 context. * - * When receiving a packet, the length must be decrypted first. When 3 bytes of - * ciphertext length have been received, they may be decrypted. + * Byte-level forward security is possible by precomputing 4096 bytes of stream + * output, caching it, resetting the key to the final 32 bytes of the output, and + * then wiping the remaining 4064 bytes of cached data as it gets used. + * + * For each packet, use 3 bytes from the remaining ChaCha20 stream generated using + * K_1 to encrypt the length. Use additional 32 bytes of the same stream to + * generate a Poly1305 key. * - * A ChaCha20 round always calculates 64bytes which is sufficient to crypt 21 - * times a 3 bytes length field (21*3 = 63). The length field sequence number can - * thus be used 21 times (keystream caching). + * If we reach bytes 4064 on the ChaCha20 stream, use the next 32 bytes (byte + * 4065-4096) and set is as the new ChaCha20 key, reset the counter to 0 while + * incrementing the sequence number + 1 and set is as IV (little endian encoding). * - * The length field must be enc-/decrypted with the ChaCha20 keystream keyed with - * K_1 defined by block counter 0, the length field sequence number in little - * endian and a keystream position from 0 to 60. + * For the payload, use the ChaCha20 stream keyed with K_2 and apply the same + * re-key rules. + * + * + * ==== Packet Handling ==== + * + * When receiving a packet, the length must be decrypted first. When 3 bytes of + * ciphertext length have been received, they MUST be decrypted. * * Once the entire packet has been received, the MAC MUST be checked before * decryption. A per-packet Poly1305 key is generated as described above and the - * MAC tag calculated using Poly1305 with this key over the ciphertext of the + * MAC tag is calculated using Poly1305 with this key over the ciphertext of the * packet length and the payload together. The calculated MAC is then compared in * constant time with the one appended to the packet and the packet decrypted - * using ChaCha20 as described above (with K_2, the packet sequence number as - * nonce and a starting block counter of 1). + * using ChaCha20 as described above (using stream keyed with K_2). * * Detection of an invalid MAC MUST lead to immediate connection termination. * - * To send a packet, first encode the 3 byte length and encrypt it using K_1 as - * described above. Encrypt the packet payload (using K_2) and append it to the - * encrypted length. Finally, calculate a MAC tag and append it. - * - * The initiating peer MUST use K_1_A, K_2_A to encrypt messages on - * the send channel, K_1_B, K_2_B MUST be used to decrypt messages on - * the receive channel. - * - * The responding peer MUST use K_1_A, K_2_A to decrypt messages on - * the receive channel, K_1_B, K_2_B MUST be used to encrypt messages - * on the send channel. - * - * Optimized implementations of ChaCha20-Poly1305@bitcoin are relatively fast in - * general, therefore it is very likely that encrypted messages require not more - * CPU cycles per bytes then the current unencrypted p2p message format - * (ChaCha20/Poly1305 versus double SHA256). - * - * The initial packet sequence numbers are 0. - * - * K_2 ChaCha20 cipher instance (payload) must never reuse a {key, nonce} for - * encryption nor may it be used to encrypt more than 2^70 bytes under the same - * {key, nonce}. - * - * K_1 ChaCha20 cipher instance (length field/AAD) must never reuse a {key, nonce, - * position-in-keystream} for encryption nor may it be used to encrypt more than - * 2^70 bytes under the same {key, nonce}. - * - * We use message sequence numbers for both communication directions. + * To send a packet, first encode the 3 byte length and encrypt it using the + * ChaCha20 stream keyed with K_1 as described above. Encrypt the packet payload + * (using the ChaCha20 stream keyed with K_2) and append it to the encrypted + * length. Finally, calculate a MAC tag (using poly1305 key from stream keyed with K_1) + * and append it. */ +const size_t KEYSTREAM_SIZE = 4096; + +class ChaCha20Forward4064 +{ +private: + ChaCha20 m_ctx; + uint64_t m_seqnr{0}; + size_t m_keystream_pos{0}; + unsigned char m_keystream[KEYSTREAM_SIZE] = {0}; + +public: + ChaCha20Forward4064(const Span key); + ~ChaCha20Forward4064(); + void Crypt(const unsigned char* input, unsigned char* output, size_t bytes); +}; + class ChaCha20Poly1305AEAD { private: - ChaCha20 m_chacha_header; // AAD cipher instance (encrypted length) and poly1305 key-derivation cipher instance - ChaCha20 m_chacha_main; // payload - unsigned char m_aad_keystream_buffer[CHACHA20_ROUND_OUTPUT]; // aad keystream cache - uint64_t m_cached_aad_seqnr; // aad keystream cache hint + ChaCha20Forward4064 m_chacha_header; // AAD cipher instance (encrypted length) and poly1305 key-derivation cipher instance + ChaCha20Forward4064 m_chacha_main; // payload public: - ChaCha20Poly1305AEAD(const unsigned char* K_1, size_t K_1_len, const unsigned char* K_2, size_t K_2_len); + ChaCha20Poly1305AEAD(const Span K_F, const Span K_V); explicit ChaCha20Poly1305AEAD(const ChaCha20Poly1305AEAD&) = delete; /** Encrypts/decrypts a packet - seqnr_payload, the message sequence number - seqnr_aad, the messages AAD sequence number which allows reuse of the AAD keystream - aad_pos, position to use in the AAD keystream to encrypt the AAD dest, output buffer, must be of a size equal or larger then CHACHA20_POLY1305_AEAD_AAD_LEN + payload (+ POLY1305_TAG_LEN in encryption) bytes destlen, length of the destination buffer src, the AAD+payload to encrypt or the AAD+payload+MAC to decrypt src_len, the length of the source buffer is_encrypt, set to true if we encrypt (creates and appends the MAC instead of verifying it) + + Returns true if encipher succeeds. Upon failure, the data at dest should not be used. */ - bool Crypt(uint64_t seqnr_payload, uint64_t seqnr_aad, int aad_pos, unsigned char* dest, size_t dest_len, const unsigned char* src, size_t src_len, bool is_encrypt); + [[nodiscard]] bool Crypt(unsigned char* dest, size_t dest_len, const unsigned char* src, size_t src_len, bool is_encrypt); + + /** Decrypts the 3 bytes AAD data (the packet length) and decodes it into a uint32_t field + the ciphertext will not be manipulated but the AAD keystream will advance. As a result, + DecryptLength() cannot be called multiple times to get the same result. The caller must + cache the result for re-use. - /** decrypts the 3 bytes AAD data and decodes it into a uint32_t field */ - bool GetLength(uint32_t* len24_out, uint64_t seqnr_aad, int aad_pos, const uint8_t* ciphertext); + Ciphertext needs to stay encrypted due to the MAC check that will follow (requires encrypted length) + */ + [[nodiscard]] uint32_t DecryptLength(const uint8_t* ciphertext); }; #endif // BITCOIN_CRYPTO_CHACHA_POLY_AEAD_H diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index 6148edf115..6b13edbd41 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -590,37 +590,24 @@ BOOST_AUTO_TEST_CASE(hkdf_hmac_sha256_l32_tests) "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d"); } -static void TestChaCha20Poly1305AEAD(bool must_succeed, unsigned int expected_aad_length, const std::string& hex_m, const std::string& hex_k1, const std::string& hex_k2, const std::string& hex_aad_keystream, const std::string& hex_encrypted_message, const std::string& hex_encrypted_message_seq_999) +static void TestChaCha20Poly1305AEAD(bool must_succeed, unsigned int expected_aad_length, const std::string& hex_m, const std::string& hex_k1, const std::string& hex_k2, const std::string& hex_encrypted_message, const std::string& hex_encrypted_message_seq_999) { - // we need two sequence numbers, one for the payload cipher instance... - uint32_t seqnr_payload = 0; - // ... and one for the AAD (length) cipher instance - uint32_t seqnr_aad = 0; - // we need to keep track of the position in the AAD cipher instance - // keystream since we use the same 64byte output 21 times - // (21 times 3 bytes length < 64) - int aad_pos = 0; - std::vector aead_K_1 = ParseHex(hex_k1); std::vector aead_K_2 = ParseHex(hex_k2); std::vector plaintext_buf = ParseHex(hex_m); - std::vector expected_aad_keystream = ParseHex(hex_aad_keystream); std::vector expected_ciphertext_and_mac = ParseHex(hex_encrypted_message); std::vector expected_ciphertext_and_mac_sequence999 = ParseHex(hex_encrypted_message_seq_999); std::vector ciphertext_buf(plaintext_buf.size() + POLY1305_TAGLEN, 0); std::vector plaintext_buf_new(plaintext_buf.size(), 0); - std::vector cmp_ctx_buffer(64); uint32_t out_len = 0; // create the AEAD instance - ChaCha20Poly1305AEAD aead(aead_K_1.data(), aead_K_1.size(), aead_K_2.data(), aead_K_2.size()); - - // create a chacha20 instance to compare against - ChaCha20 cmp_ctx(aead_K_1.data(), 32); + ChaCha20Poly1305AEAD aead_out(aead_K_1, aead_K_2); + ChaCha20Poly1305AEAD aead_in(aead_K_1, aead_K_2); // encipher - bool res = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, ciphertext_buf.data(), ciphertext_buf.size(), plaintext_buf.data(), plaintext_buf.size(), true); + bool res = aead_out.Crypt(ciphertext_buf.data(), ciphertext_buf.size(), plaintext_buf.data(), plaintext_buf.size(), true); // make sure the operation succeeded if expected to succeed BOOST_CHECK_EQUAL(res, must_succeed); if (!res) return; @@ -629,57 +616,30 @@ static void TestChaCha20Poly1305AEAD(bool must_succeed, unsigned int expected_aa BOOST_CHECK_EQUAL(expected_ciphertext_and_mac.size(), ciphertext_buf.size()); BOOST_CHECK(memcmp(ciphertext_buf.data(), expected_ciphertext_and_mac.data(), ciphertext_buf.size()) == 0); - // manually construct the AAD keystream - cmp_ctx.SetIV(seqnr_aad); - cmp_ctx.Seek(0); - cmp_ctx.Keystream(cmp_ctx_buffer.data(), 64); - BOOST_CHECK(memcmp(expected_aad_keystream.data(), cmp_ctx_buffer.data(), expected_aad_keystream.size()) == 0); - // crypt the 3 length bytes and compare the length - uint32_t len_cmp = 0; - len_cmp = (ciphertext_buf[0] ^ cmp_ctx_buffer[aad_pos + 0]) | - (ciphertext_buf[1] ^ cmp_ctx_buffer[aad_pos + 1]) << 8 | - (ciphertext_buf[2] ^ cmp_ctx_buffer[aad_pos + 2]) << 16; - BOOST_CHECK_EQUAL(len_cmp, expected_aad_length); - - // encrypt / decrypt 1000 packets + out_len = aead_in.DecryptLength(ciphertext_buf.data()); + BOOST_CHECK_EQUAL(out_len, expected_aad_length); + res = aead_in.Crypt(plaintext_buf.data(), plaintext_buf.size(), ciphertext_buf.data(), ciphertext_buf.size(), false); + BOOST_CHECK_EQUAL(res, must_succeed); + WriteLE32(plaintext_buf.data(), out_len); + + // encrypt / decrypt the packet 1000 times for (size_t i = 0; i < 1000; ++i) { - res = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, ciphertext_buf.data(), ciphertext_buf.size(), plaintext_buf.data(), plaintext_buf.size(), true); + res = aead_out.Crypt(ciphertext_buf.data(), ciphertext_buf.size(), plaintext_buf.data(), plaintext_buf.size(), true); BOOST_CHECK(res); - BOOST_CHECK(aead.GetLength(&out_len, seqnr_aad, aad_pos, ciphertext_buf.data())); + out_len = aead_in.DecryptLength(ciphertext_buf.data()); BOOST_CHECK_EQUAL(out_len, expected_aad_length); - res = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, plaintext_buf_new.data(), plaintext_buf_new.size(), ciphertext_buf.data(), ciphertext_buf.size(), false); + res = aead_in.Crypt(plaintext_buf_new.data(), plaintext_buf_new.size(), ciphertext_buf.data(), ciphertext_buf.size(), false); BOOST_CHECK(res); - // make sure we repetitive get the same plaintext - BOOST_CHECK(memcmp(plaintext_buf.data(), plaintext_buf_new.data(), plaintext_buf.size()) == 0); + // length is not decrypted, copy it over + WriteLE32(plaintext_buf_new.data(), out_len); - // compare sequence number 999 against the test vector - if (seqnr_payload == 999) { - BOOST_CHECK(memcmp(ciphertext_buf.data(), expected_ciphertext_and_mac_sequence999.data(), expected_ciphertext_and_mac_sequence999.size()) == 0); - } - // set nonce and block counter, output the keystream - cmp_ctx.SetIV(seqnr_aad); - cmp_ctx.Seek(0); - cmp_ctx.Keystream(cmp_ctx_buffer.data(), 64); - - // crypt the 3 length bytes and compare the length - len_cmp = 0; - len_cmp = (ciphertext_buf[0] ^ cmp_ctx_buffer[aad_pos + 0]) | - (ciphertext_buf[1] ^ cmp_ctx_buffer[aad_pos + 1]) << 8 | - (ciphertext_buf[2] ^ cmp_ctx_buffer[aad_pos + 2]) << 16; - BOOST_CHECK_EQUAL(len_cmp, expected_aad_length); - - // increment the sequence number(s) - // always increment the payload sequence number - // increment the AAD keystream position by its size (3) - // increment the AAD sequence number if we would hit the 64 byte limit - seqnr_payload++; - aad_pos += CHACHA20_POLY1305_AEAD_AAD_LEN; - if (aad_pos + CHACHA20_POLY1305_AEAD_AAD_LEN > CHACHA20_ROUND_OUTPUT) { - aad_pos = 0; - seqnr_aad++; - } + // make sure we always get the same plaintext + BOOST_CHECK(memcmp(plaintext_buf.data(), plaintext_buf_new.data(), plaintext_buf.size()) == 0); } + + // compare at final value against the test vector + BOOST_CHECK(memcmp(ciphertext_buf.data(), expected_ciphertext_and_mac_sequence999.data(), expected_ciphertext_and_mac_sequence999.size()) == 0); } BOOST_AUTO_TEST_CASE(chacha20_poly1305_aead_testvector) @@ -688,31 +648,33 @@ BOOST_AUTO_TEST_CASE(chacha20_poly1305_aead_testvector) // must fail with no message TestChaCha20Poly1305AEAD(false, 0, - "", - "0000000000000000000000000000000000000000000000000000000000000000", - "0000000000000000000000000000000000000000000000000000000000000000", "", "", ""); + "", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", "", ""); - TestChaCha20Poly1305AEAD(true, 0, - /* m */ "0000000000000000000000000000000000000000000000000000000000000000", + // The expected AAD length is the length of the payload portion of the ciphertext. + TestChaCha20Poly1305AEAD(true, 29, + /* m */ "1d00000000000000000000000000000000000000000000000000000000000000", /* k1 (AAD) */ "0000000000000000000000000000000000000000000000000000000000000000", /* k2 (payload) */ "0000000000000000000000000000000000000000000000000000000000000000", - /* AAD keystream */ "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586", - /* encrypted message & MAC */ "76b8e09f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32d2fc11829c1b6c1df1f551cd6131ff08", - /* encrypted message & MAC at sequence 999 */ "b0a03d5bd2855d60699e7d3a3133fa47be740fe4e4c1f967555e2d9271f31c3aaa7aa16ec62c5e24f040c08bb20c3598"); + /* encrypted message & MAC */ "6bb8e076b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8babf71de83e6e27c82490bdc8615d0c9e", + /* encrypted message & MAC at encrypt/decrypt-loop 999 */ "d41eef105710ba88ef076f28e735cc672bde84505fbaeb0faa627ff5067a8609f829400edc18e70080d082eae6a1e2f6"); + + // If the encrypted length is wrong, the MAC will help us catch a man-in-the-middle bit flipping attack. However, if the incorrect + // length was encrypted by the sender, the cipher suite cannot help. TestChaCha20Poly1305AEAD(true, 1, - "0100000000000000000000000000000000000000000000000000000000000000", - "0000000000000000000000000000000000000000000000000000000000000000", - "0000000000000000000000000000000000000000000000000000000000000000", - "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586", - "77b8e09f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32baf0c85b6dff8602b06cf52a6aefc62e", - "b1a03d5bd2855d60699e7d3a3133fa47be740fe4e4c1f967555e2d9271f31c3a8bd94d54b5ecabbc41ffbb0c90924080"); - TestChaCha20Poly1305AEAD(true, 255, - "ff0000f195e66982105ffb640bb7757f579da31602fc93ec01ac56f85ac3c134a4547b733b46413042c9440049176905d3be59ea1c53f15916155c2be8241a38008b9a26bc35941e2444177c8ade6689de95264986d95889fb60e84629c9bd9a5acb1cc118be563eb9b3a4a472f82e09a7e778492b562ef7130e88dfe031c79db9d4f7c7a899151b9a475032b63fc385245fe054e3dd5a97a5f576fe064025d3ce042c566ab2c507b138db853e3d6959660996546cc9c4a6eafdc777c040d70eaf46f76dad3979e5c5360c3317166a1c894c94a371876a94df7628fe4eaaf2ccb27d5aaae0ad7ad0f9d4b6ad3b54098746d4524d38407a6deb3ab78fab78c9", - "ff0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", - "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", - "c640c1711e3ee904ac35c57ab9791c8a1c408603a90b77a83b54f6c844cb4b06d94e7fc6c800e165acd66147e80ec45a567f6ce66d05ec0cae679dceeb890017", - "3940c1e92da4582ff6f92a776aeb14d014d384eeb30f660dacf70a14a23fd31e91212701334e2ce1acf5199dc84f4d61ddbe6571bca5af874b4c9226c26e650995d157644e1848b96ed6c2102d5489a050e71d29a5a66ece11de5fb5c9558d54da28fe45b0bc4db4e5b88030bfc4a352b4b7068eccf656bae7ad6a35615315fc7c49d4200388d5eca67c2e822e069336c69b40db67e0f3c81209c50f3216a4b89fb3ae1b984b7851a2ec6f68ab12b101ab120e1ea7313bb93b5a0f71185c7fea017ddb92769861c29dba4fbc432280d5dff21b36d1c4c790128b22699950bb18bf74c448cdfe547d8ed4f657d8005fdc0cd7a050c2d46050a44c4376355858981fbe8b184288276e7a93eabc899c4a", - "f039c6689eaeef0456685200feaab9d54bbd9acde4410a3b6f4321296f4a8ca2604b49727d8892c57e005d799b2a38e85e809f20146e08eec75169691c8d4f54a0d51a1e1c7b381e0474eb02f994be9415ef3ffcbd2343f0601e1f3b172a1d494f838824e4df570f8e3b0c04e27966e36c82abd352d07054ef7bd36b84c63f9369afe7ed79b94f953873006b920c3fa251a771de1b63da927058ade119aa898b8c97e42a606b2f6df1e2d957c22f7593c1e2002f4252f4c9ae4bf773499e5cfcfe14dfc1ede26508953f88553bf4a76a802f6a0068d59295b01503fd9a600067624203e880fdf53933b96e1f4d9eb3f4e363dd8165a278ff667a41ee42b9892b077cefff92b93441f7be74cf10e6cd"); + "0100000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "77b8e076b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8bfb6cf9dcd7e2ee807d5ff981eb4a135a", + "c81eef105710ba88ef076f28e735cc672bde84505fbaeb0faa627ff5067a860942b2888c98e0c1003d0611e527776e88"); + + TestChaCha20Poly1305AEAD(true, 252, + "fc0000f195e66982105ffb640bb7757f579da31602fc93ec01ac56f85ac3c134a4547b733b46413042c9440049176905d3be59ea1c53f15916155c2be8241a38008b9a26bc35941e2444177c8ade6689de95264986d95889fb60e84629c9bd9a5acb1cc118be563eb9b3a4a472f82e09a7e778492b562ef7130e88dfe031c79db9d4f7c7a899151b9a475032b63fc385245fe054e3dd5a97a5f576fe064025d3ce042c566ab2c507b138db853e3d6959660996546cc9c4a6eafdc777c040d70eaf46f76dad3979e5c5360c3317166a1c894c94a371876a94df7628fe4eaaf2ccb27d5aaae0ad7ad0f9d4b6ad3b54098746d4524d38407a6deb3ab78fab78c9", + "ff0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "3a40c1c868cd145bd54691e9b6b402c78bd7ea9c3724fc50dfc69a4a96be8dec4e70e958188aa69222eaef3f47f8003f1bc13dcf9e661be8e1b671e9cf46ba705bca963e0477a5b3c2e2c66feb8207269ddb01b1372aad68563bb4aad135afb06fbe40b310b63bef578ff939f3a00a6da9e744d28ba070294e5746d2ca7bb8ac2c8e3a855ab4c9bcd0d5855e11b52cacaa2ddb34c0a26cd04f4bc10de6dc151d4ee7ced2c2b0de8ded33ff11f301e4027559e8938b69bceb1e5e259d4122056f6adbd48a0628b912f90d72838f2f3aaf6b88342cf5bac3cb688a9b0f7afc73a7e3cad8e71254c786ea000240ae7bd1df8bcfca07f3b885723a9d7f89736461917bb2791faffbe34650c8501daaef76", + "c6ab314a18d3b9eb02b7990e91adb4f005fb185d741277c066c4d002560dabea96b07009b1ae287931224e90fd70324fb02857019499f3d9ec774dd3f412a1ac13dc2f603e8b22abef71c9c7c688c1b7d835f76d32a32886f3326f70701f5b3617de21723a9d575bd572815696ad8410da643603a9a1c1a5aedc0c88ceb2c6610c685a4918e09f36f01c646f071c8ec668fd794ff4fc8bd671663a8e36a96ea8d4ea4c3d2893258237bddf7562af50785043cfb78e06cfe6d00145a46a76c9fedc450c776af4a4319ecb92ef818d2174baab3714cabb823a4c456cf51c0143a9451676db428b6b5aca7f8ff4a51fd717bc3293955aca0363ec663abdc8c8e7474f2e646d37ea226eb611c315bdee8b"); } BOOST_AUTO_TEST_CASE(countbits_tests) diff --git a/src/test/fuzz/crypto_chacha20_poly1305_aead.cpp b/src/test/fuzz/crypto_chacha20_poly1305_aead.cpp index 596614a71b..9d2e62343d 100644 --- a/src/test/fuzz/crypto_chacha20_poly1305_aead.cpp +++ b/src/test/fuzz/crypto_chacha20_poly1305_aead.cpp @@ -9,22 +9,17 @@ #include #include -#include #include -#include #include FUZZ_TARGET(crypto_chacha20_poly1305_aead) { FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; - const std::vector k1 = ConsumeFixedLengthByteVector(fuzzed_data_provider, CHACHA20_POLY1305_AEAD_KEY_LEN); - const std::vector k2 = ConsumeFixedLengthByteVector(fuzzed_data_provider, CHACHA20_POLY1305_AEAD_KEY_LEN); + std::vector k1 = ConsumeFixedLengthByteVector(fuzzed_data_provider, CHACHA20_POLY1305_AEAD_KEY_LEN); + std::vector k2 = ConsumeFixedLengthByteVector(fuzzed_data_provider, CHACHA20_POLY1305_AEAD_KEY_LEN); - ChaCha20Poly1305AEAD aead(k1.data(), k1.size(), k2.data(), k2.size()); - uint64_t seqnr_payload = 0; - uint64_t seqnr_aad = 0; - int aad_pos = 0; + ChaCha20Poly1305AEAD aead(k1, k2); size_t buffer_size = fuzzed_data_provider.ConsumeIntegralInRange(0, 4096); std::vector in(buffer_size + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0); std::vector out(buffer_size + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0); @@ -38,32 +33,10 @@ FUZZ_TARGET(crypto_chacha20_poly1305_aead) out = std::vector(buffer_size + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0); }, [&] { - (void)aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, out.data(), out.size(), in.data(), buffer_size, is_encrypt); + (void)aead.Crypt(out.data(), out.size(), in.data(), buffer_size, is_encrypt); }, [&] { - uint32_t len = 0; - const bool ok = aead.GetLength(&len, seqnr_aad, aad_pos, in.data()); - assert(ok); - }, - [&] { - if (AdditionOverflow(seqnr_payload, static_cast(1))) { - return; - } - seqnr_payload += 1; - aad_pos += CHACHA20_POLY1305_AEAD_AAD_LEN; - if (aad_pos + CHACHA20_POLY1305_AEAD_AAD_LEN > CHACHA20_ROUND_OUTPUT) { - aad_pos = 0; - if (AdditionOverflow(seqnr_aad, static_cast(1))) { - return; - } - seqnr_aad += 1; - } - }, - [&] { - seqnr_payload = fuzzed_data_provider.ConsumeIntegral(); - }, - [&] { - seqnr_aad = fuzzed_data_provider.ConsumeIntegral(); + (void)aead.DecryptLength(in.data()); }, [&] { is_encrypt = fuzzed_data_provider.ConsumeBool();