Skip to content

Commit af5d1b5

Browse files
committed
Add ChaCha20Poly1305@Bitcoin AEAD implementation
1 parent 332c613 commit af5d1b5

File tree

3 files changed

+275
-1
lines changed

3 files changed

+275
-1
lines changed

src/Makefile.am

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,8 @@ crypto_libbitcoin_crypto_base_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
351351
crypto_libbitcoin_crypto_base_a_SOURCES = \
352352
crypto/aes.cpp \
353353
crypto/aes.h \
354+
crypto/chacha_poly_aead.h \
355+
crypto/chacha_poly_aead.cpp \
354356
crypto/chacha20.h \
355357
crypto/chacha20.cpp \
356358
crypto/common.h \
@@ -613,7 +615,7 @@ bitcoin_wallet_LDADD += $(BOOST_LIBS) $(BDB_LIBS) $(CRYPTO_LIBS) $(EVENT_PTHREAD
613615
# bitcoinconsensus library #
614616
if BUILD_BITCOIN_LIBS
615617
include_HEADERS = script/bitcoinconsensus.h
616-
libbitcoinconsensus_la_SOURCES = $(crypto_libbitcoin_crypto_base_a_SOURCES) $(libbitcoin_consensus_a_SOURCES)
618+
libbitcoinconsensus_la_SOURCES = support/cleanse.cpp $(crypto_libbitcoin_crypto_base_a_SOURCES) $(libbitcoin_consensus_a_SOURCES)
617619

618620
if GLIBC_BACK_COMPAT
619621
libbitcoinconsensus_la_SOURCES += compat/glibc_compat.cpp

src/crypto/chacha_poly_aead.cpp

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright (c) 2019 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <crypto/chacha_poly_aead.h>
6+
7+
#include <crypto/common.h>
8+
#include <crypto/poly1305.h>
9+
#include <support/cleanse.h>
10+
11+
#include <assert.h>
12+
#include <string.h>
13+
14+
#include <cstdio>
15+
#include <limits>
16+
17+
#ifndef HAVE_TIMINGSAFE_BCMP
18+
19+
int timingsafe_bcmp(const unsigned char* b1, const unsigned char* b2, size_t n)
20+
{
21+
const unsigned char *p1 = b1, *p2 = b2;
22+
int ret = 0;
23+
24+
for (; n > 0; n--)
25+
ret |= *p1++ ^ *p2++;
26+
return (ret != 0);
27+
}
28+
29+
#endif // TIMINGSAFE_BCMP
30+
31+
ChaCha20Poly1305AEAD::ChaCha20Poly1305AEAD(const unsigned char* K_1, size_t K_1_len, const unsigned char* K_2, size_t K_2_len)
32+
{
33+
assert(K_1_len == CHACHA20_POLY1305_AEAD_KEY_LEN);
34+
assert(K_2_len == CHACHA20_POLY1305_AEAD_KEY_LEN);
35+
m_chacha_main.SetKey(K_1, CHACHA20_POLY1305_AEAD_KEY_LEN);
36+
m_chacha_header.SetKey(K_2, CHACHA20_POLY1305_AEAD_KEY_LEN);
37+
38+
// set the cached sequence number to uint64 max which hints for an unset cache.
39+
// we can't hit uint64 max since the rekey rule (which resets the sequence number) is 1GB
40+
m_cached_aad_seqnr = std::numeric_limits<uint64_t>::max();
41+
}
42+
43+
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)
44+
{
45+
// check buffer boundaries
46+
if (
47+
// if we encrypt, make sure the source contains at least the expected AAD and the destination has at least space for the source + MAC
48+
(is_encrypt && (src_len < CHACHA20_POLY1305_AEAD_AAD_LEN || dest_len < src_len + POLY1305_TAGLEN)) ||
49+
// if we decrypt, make sure the source contains at least the expected AAD+MAC and the destination has at least space for the source - MAC
50+
(!is_encrypt && (src_len < CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN || dest_len < src_len - POLY1305_TAGLEN))) {
51+
return false;
52+
}
53+
54+
unsigned char expected_tag[POLY1305_TAGLEN], poly_key[POLY1305_KEYLEN];
55+
memset(poly_key, 0, sizeof(poly_key));
56+
m_chacha_main.SetIV(seqnr_payload);
57+
58+
// block counter 0 for the poly1305 key
59+
// use lower 32bytes for the poly1305 key
60+
// (throws away 32 unused bytes (upper 32) from this ChaCha20 round)
61+
m_chacha_main.Seek(0);
62+
m_chacha_main.Crypt(poly_key, poly_key, sizeof(poly_key));
63+
64+
// if decrypting, verify the tag prior to decryption
65+
if (!is_encrypt) {
66+
const unsigned char* tag = src + src_len - POLY1305_TAGLEN;
67+
poly1305_auth(expected_tag, src, src_len - POLY1305_TAGLEN, poly_key);
68+
69+
// constant time compare the calculated MAC with the provided MAC
70+
if (timingsafe_bcmp(expected_tag, tag, POLY1305_TAGLEN) != 0) {
71+
memory_cleanse(expected_tag, sizeof(expected_tag));
72+
memory_cleanse(poly_key, sizeof(poly_key));
73+
return false;
74+
}
75+
memory_cleanse(expected_tag, sizeof(expected_tag));
76+
// MAC has been successfully verified, make sure we don't covert it in decryption
77+
src_len -= POLY1305_TAGLEN;
78+
}
79+
80+
// calculate and cache the next 64byte keystream block if requested sequence number is not yet the cache
81+
if (m_cached_aad_seqnr != seqnr_aad) {
82+
m_cached_aad_seqnr = seqnr_aad;
83+
m_chacha_header.SetIV(seqnr_aad);
84+
m_chacha_header.Seek(0);
85+
m_chacha_header.Keystream(m_aad_keystream_buffer, CHACHA20_ROUND_OUTPUT);
86+
}
87+
// crypt the AAD (3 bytes message length) with given position in AAD cipher instance keystream
88+
dest[0] = src[0] ^ m_aad_keystream_buffer[aad_pos];
89+
dest[1] = src[1] ^ m_aad_keystream_buffer[aad_pos + 1];
90+
dest[2] = src[2] ^ m_aad_keystream_buffer[aad_pos + 2];
91+
92+
// Set the playload ChaCha instance block counter to 1 and crypt the payload
93+
m_chacha_main.Seek(1);
94+
m_chacha_main.Crypt(src + CHACHA20_POLY1305_AEAD_AAD_LEN, dest + CHACHA20_POLY1305_AEAD_AAD_LEN, src_len - CHACHA20_POLY1305_AEAD_AAD_LEN);
95+
96+
// If encrypting, calculate and append tag
97+
if (is_encrypt) {
98+
// the poly1305 tag expands over the AAD (3 bytes length) & encrypted payload
99+
poly1305_auth(dest + src_len, dest, src_len, poly_key);
100+
}
101+
102+
// cleanse no longer required MAC and polykey
103+
memory_cleanse(poly_key, sizeof(poly_key));
104+
return true;
105+
}
106+
107+
bool ChaCha20Poly1305AEAD::GetLength(uint32_t* len24_out, uint64_t seqnr_aad, int aad_pos, const uint8_t* ciphertext)
108+
{
109+
// enforce valid aad position to avoid accessing outside of the 64byte keystream cache
110+
// (there is space for 21 times 3 bytes)
111+
assert(aad_pos >= 0 && aad_pos < CHACHA20_ROUND_OUTPUT - CHACHA20_POLY1305_AEAD_AAD_LEN);
112+
if (m_cached_aad_seqnr != seqnr_aad) {
113+
// we need to calculate the 64 keystream bytes since we reached a new aad sequence number
114+
m_cached_aad_seqnr = seqnr_aad;
115+
m_chacha_header.SetIV(seqnr_aad); // use LE for the nonce
116+
m_chacha_header.Seek(0); // block counter 0
117+
m_chacha_header.Keystream(m_aad_keystream_buffer, CHACHA20_ROUND_OUTPUT); // write keystream to the cache
118+
}
119+
120+
// decrypt the ciphertext length by XORing the right position of the 64byte keystream cache with the ciphertext
121+
*len24_out = (ciphertext[0] ^ m_aad_keystream_buffer[aad_pos + 0]) |
122+
(ciphertext[1] ^ m_aad_keystream_buffer[aad_pos + 1]) << 8 |
123+
(ciphertext[2] ^ m_aad_keystream_buffer[aad_pos + 2]) << 16;
124+
125+
return true;
126+
}

src/crypto/chacha_poly_aead.h

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// Copyright (c) 2019 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_CRYPTO_CHACHA_POLY_AEAD_H
6+
#define BITCOIN_CRYPTO_CHACHA_POLY_AEAD_H
7+
8+
#include <crypto/chacha20.h>
9+
10+
#include <cmath>
11+
12+
static constexpr int CHACHA20_POLY1305_AEAD_KEY_LEN = 32;
13+
static constexpr int CHACHA20_POLY1305_AEAD_AAD_LEN = 3; /* 3 bytes length */
14+
static constexpr int CHACHA20_ROUND_OUTPUT = 64; /* 64 bytes per round */
15+
static constexpr int AAD_PACKAGES_PER_ROUND = 21; /* 64 / 3 round down*/
16+
17+
/* A AEAD class for ChaCha20-Poly1305@bitcoin.
18+
*
19+
* ChaCha20 is a stream cipher designed by Daniel Bernstein and described in
20+
* <ref>[http://cr.yp.to/chacha/chacha-20080128.pdf ChaCha20]</ref>. It operates
21+
* by permuting 128 fixed bits, 128 or 256 bits of key, a 64 bit nonce and a 64
22+
* bit counter into 64 bytes of output. This output is used as a keystream, with
23+
* any unused bytes simply discarded.
24+
*
25+
* Poly1305 <ref>[http://cr.yp.to/mac/poly1305-20050329.pdf Poly1305]</ref>, also
26+
* by Daniel Bernstein, is a one-time Carter-Wegman MAC that computes a 128 bit
27+
* integrity tag given a message and a single-use 256 bit secret key.
28+
*
29+
* The chacha20-poly1305@bitcoin combines these two primitives into an
30+
* authenticated encryption mode. The construction used is based on that proposed
31+
* for TLS by Adam Langley in
32+
* <ref>[http://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-03 "ChaCha20
33+
* and Poly1305 based Cipher Suites for TLS", Adam Langley]</ref>, but differs in
34+
* the layout of data passed to the MAC and in the addition of encryption of the
35+
* packet lengths.
36+
*
37+
* ==== Detailed Construction ====
38+
*
39+
* The chacha20-poly1305@bitcoin cipher requires two 256 bits of key material as
40+
* output from the key exchange. Each key (K_1 and K_2) are used by two separate
41+
* instances of chacha20.
42+
*
43+
* The instance keyed by K_1 is a stream cipher that is used only to encrypt the 3
44+
* byte packet length field and has its own sequence number. The second instance,
45+
* keyed by K_2, is used in conjunction with poly1305 to build an AEAD
46+
* (Authenticated Encryption with Associated Data) that is used to encrypt and
47+
* authenticate the entire packet.
48+
*
49+
* Two separate cipher instances are used here so as to keep the packet lengths
50+
* confidential but not create an oracle for the packet payload cipher by
51+
* decrypting and using the packet length prior to checking the MAC. By using an
52+
* independently-keyed cipher instance to encrypt the length, an active attacker
53+
* seeking to exploit the packet input handling as a decryption oracle can learn
54+
* nothing about the payload contents or its MAC (assuming key derivation,
55+
* ChaCha20 and Poly1305 are secure).
56+
*
57+
* The AEAD is constructed as follows: for each packet, generate a Poly1305 key by
58+
* taking the first 256 bits of ChaCha20 stream output generated using K_2, an IV
59+
* consisting of the packet sequence number encoded as an LE uint64 and a ChaCha20
60+
* block counter of zero. The K_2 ChaCha20 block counter is then set to the
61+
* little-endian encoding of 1 (i.e. {1, 0, 0, 0, 0, 0, 0, 0}) and this instance
62+
* is used for encryption of the packet payload.
63+
*
64+
* ==== Packet Handling ====
65+
*
66+
* When receiving a packet, the length must be decrypted first. When 3 bytes of
67+
* ciphertext length have been received, they may be decrypted.
68+
*
69+
* A ChaCha20 round always calculates 64bytes which is sufficient to crypt 21
70+
* times a 3 bytes length field (21*3 = 63). The length field sequence number can
71+
* thus be used 21 times (keystream caching).
72+
*
73+
* The length field must be enc-/decrypted with the ChaCha20 keystream keyed with
74+
* K_1 defined by block counter 0, the length field sequence number in little
75+
* endian and a keystream position from 0 to 60.
76+
*
77+
* Once the entire packet has been received, the MAC MUST be checked before
78+
* decryption. A per-packet Poly1305 key is generated as described above and the
79+
* MAC tag calculated using Poly1305 with this key over the ciphertext of the
80+
* packet length and the payload together. The calculated MAC is then compared in
81+
* constant time with the one appended to the packet and the packet decrypted
82+
* using ChaCha20 as described above (with K_2, the packet sequence number as
83+
* nonce and a starting block counter of 1).
84+
*
85+
* Detection of an invalid MAC MUST lead to immediate connection termination.
86+
*
87+
* To send a packet, first encode the 3 byte length and encrypt it using K_1 as
88+
* described above. Encrypt the packet payload (using K_2) and append it to the
89+
* encrypted length. Finally, calculate a MAC tag and append it.
90+
*
91+
* The initiating peer MUST use <code>K_1_A, K_2_A</code> to encrypt messages on
92+
* the send channel, <code>K_1_B, K_2_B</code> MUST be used to decrypt messages on
93+
* the receive channel.
94+
*
95+
* The responding peer MUST use <code>K_1_A, K_2_A</code> to decrypt messages on
96+
* the receive channel, <code>K_1_B, K_2_B</code> MUST be used to encrypt messages
97+
* on the send channel.
98+
*
99+
* Optimized implementations of ChaCha20-Poly1305@bitcoin are relatively fast in
100+
* general, therefore it is very likely that encrypted messages require not more
101+
* CPU cycles per bytes then the current unencrypted p2p message format
102+
* (ChaCha20/Poly1305 versus double SHA256).
103+
*
104+
* The initial packet sequence numbers are 0.
105+
*
106+
* K_2 ChaCha20 cipher instance (payload) must never reuse a {key, nonce} for
107+
* encryption nor may it be used to encrypt more than 2^70 bytes under the same
108+
* {key, nonce}.
109+
*
110+
* K_1 ChaCha20 cipher instance (length field/AAD) must never reuse a {key, nonce,
111+
* position-in-keystream} for encryption nor may it be used to encrypt more than
112+
* 2^70 bytes under the same {key, nonce}.
113+
*
114+
* We use message sequence numbers for both communication directions.
115+
*/
116+
117+
class ChaCha20Poly1305AEAD
118+
{
119+
private:
120+
ChaCha20 m_chacha_main; // payload and poly1305 key-derivation cipher instance
121+
ChaCha20 m_chacha_header; // AAD cipher instance (encrypted length)
122+
unsigned char m_aad_keystream_buffer[CHACHA20_ROUND_OUTPUT]; // aad keystream cache
123+
uint64_t m_cached_aad_seqnr; // aad keystream cache hint
124+
125+
public:
126+
ChaCha20Poly1305AEAD(const unsigned char* K_1, size_t K_1_len, const unsigned char* K_2, size_t K_2_len);
127+
128+
explicit ChaCha20Poly1305AEAD(const ChaCha20Poly1305AEAD&) = delete;
129+
130+
/** Encrypts/decrypts a packet
131+
seqnr_payload, the message sequence number
132+
seqnr_aad, the messages AAD sequence number which allows reuse of the AAD keystream
133+
aad_pos, position to use in the AAD keystream to encrypt the AAD
134+
dest, output buffer, must be of a size equal or larger then CHACHA20_POLY1305_AEAD_AAD_LEN + payload (+ POLY1305_TAG_LEN in encryption) bytes
135+
destlen, length of the destination buffer
136+
src, the AAD+payload to encrypt or the AAD+payload+MAC to decrypt
137+
src_len, the length of the source buffer
138+
is_encrypt, set to true if we encrypt (creates and appends the MAC instead of verifying it)
139+
*/
140+
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);
141+
142+
/** decrypts the 3 bytes AAD data and decodes it into a uint32_t field */
143+
bool GetLength(uint32_t* len24_out, uint64_t seqnr_aad, int aad_pos, const uint8_t* ciphertext);
144+
};
145+
146+
#endif // BITCOIN_CRYPTO_CHACHA_POLY_AEAD_H

0 commit comments

Comments
 (0)