Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New aead doubts #7

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 13 additions & 27 deletions src/bench/chacha_poly_aead.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,43 +19,29 @@ 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 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(k1, 32, k2, 32);
ChaCha20Poly1305AEAD aead_out(k1, 32, k2, 32);

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);
auto len = aead_in.DecryptLength(out.data());
len = 0; // handles the [[nodiscard]]
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
134 changes: 80 additions & 54 deletions src/crypto/chacha_poly_aead.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <crypto/chacha_poly_aead.h>

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

Expand All @@ -27,20 +28,58 @@ 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 unsigned char* key, size_t keylen)
{
assert(K_1_len == CHACHA20_POLY1305_AEAD_KEY_LEN);
assert(K_2_len == CHACHA20_POLY1305_AEAD_KEY_LEN);
assert(keylen == 32);
m_ctx.SetKey(key, keylen);

// set initial sequence number
m_seqnr = 0;
m_ctx.SetIV(m_seqnr);

// precompute first chunk of keystream
m_ctx.Keystream(m_keystream, KEYSTREAM_SIZE);
m_keystream_pos = 0;
}

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;
}
}
}

m_chacha_header.SetKey(K_1, CHACHA20_POLY1305_AEAD_KEY_LEN);
m_chacha_main.SetKey(K_2, CHACHA20_POLY1305_AEAD_KEY_LEN);
ChaCha20Forward4064::~ChaCha20Forward4064()
{
memory_cleanse(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();
ChaCha20Poly1305AEAD::ChaCha20Poly1305AEAD(const unsigned char* K_1,
size_t K_1_len,
const unsigned char* K_2,
size_t K_2_len) : m_chacha_header(K_1, CHACHA20_POLY1305_AEAD_KEY_LEN), m_chacha_main(K_2, CHACHA20_POLY1305_AEAD_KEY_LEN)
{
assert(K_1_len == CHACHA20_POLY1305_AEAD_KEY_LEN);
assert(K_2_len == CHACHA20_POLY1305_AEAD_KEY_LEN);
}

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)
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 +92,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);
}

// 2. derive the poly1305 key from the header-keystream
m_chacha_header.Crypt(poly_key, poly_key, sizeof(poly_key));

// if decrypting, verify the tag prior to decryption
// 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 +118,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