Skip to content

Commit

Permalink
Alter the ChaCha20Poly1305@Bitcoin AEAD to the new specification
Browse files Browse the repository at this point in the history
Co-authored-by: Dhruv Mehta <[email protected]>
  • Loading branch information
jonasschnelli and dhruv committed Jan 20, 2022
1 parent e3ce019 commit 41deee4
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 275 deletions.
48 changes: 16 additions & 32 deletions src/bench/chacha_poly_aead.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,59 +3,43 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.


#include <assert.h>
#include <bench/bench.h>
#include <crypto/chacha_poly_aead.h>
#include <crypto/poly1305.h> // for the POLY1305_TAGLEN constant
#include <hash.h>

#include <assert.h>
#include <limits>
#include <vector>

/* 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<unsigned char> zero_key(32, 0x00);

static void CHACHA20_POLY1305_AEAD(benchmark::Bench& bench, size_t buffersize, bool include_decryption)
{
std::vector<unsigned char> in(buffersize + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0);
std::vector<unsigned char> 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<unsigned char> in(plaintext_len, 0);
std::vector<unsigned char> 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<uint64_t>::max()) {
// reuse of nonce+key is okay while benchmarking.
seqnr_payload = 0;
seqnr_aad = 0;
aad_pos = 0;
}
});
}

Expand Down
133 changes: 76 additions & 57 deletions src/crypto/chacha_poly_aead.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@

#include <crypto/chacha_poly_aead.h>

#include <crypto/common.h>
#include <crypto/poly1305.h>
#include <support/cleanse.h>

#include <assert.h>
#include <cstring>
#include <string.h>

#include <cstdio>
#include <limits>

#ifndef HAVE_TIMINGSAFE_BCMP

int timingsafe_bcmp(const unsigned char* b1, const unsigned char* b2, size_t n)
Expand All @@ -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<unsigned char> 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<uint64_t>::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<unsigned char> K_F, const Span<unsigned char> 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 (
Expand All @@ -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) {
Expand All @@ -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;
}
Loading

0 comments on commit 41deee4

Please sign in to comment.