crypto: refactor ChaCha20 classes to use Span<std::byte> interface

This commit is contained in:
Pieter Wuille
2023-07-18 10:11:49 -04:00
parent 6ce5e8f475
commit 3da636e08b
10 changed files with 200 additions and 178 deletions

View File

@@ -8,6 +8,7 @@
#include <crypto/common.h>
#include <crypto/chacha20.h>
#include <support/cleanse.h>
#include <span.h>
#include <algorithm>
#include <string.h>
@@ -22,23 +23,24 @@ constexpr static inline uint32_t rotl32(uint32_t v, int c) { return (v << c) | (
#define REPEAT10(a) do { {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; } while(0)
void ChaCha20Aligned::SetKey32(const unsigned char* k)
void ChaCha20Aligned::SetKey(Span<const std::byte> key) noexcept
{
input[0] = ReadLE32(k + 0);
input[1] = ReadLE32(k + 4);
input[2] = ReadLE32(k + 8);
input[3] = ReadLE32(k + 12);
input[4] = ReadLE32(k + 16);
input[5] = ReadLE32(k + 20);
input[6] = ReadLE32(k + 24);
input[7] = ReadLE32(k + 28);
assert(key.size() == KEYLEN);
input[0] = ReadLE32(UCharCast(key.data() + 0));
input[1] = ReadLE32(UCharCast(key.data() + 4));
input[2] = ReadLE32(UCharCast(key.data() + 8));
input[3] = ReadLE32(UCharCast(key.data() + 12));
input[4] = ReadLE32(UCharCast(key.data() + 16));
input[5] = ReadLE32(UCharCast(key.data() + 20));
input[6] = ReadLE32(UCharCast(key.data() + 24));
input[7] = ReadLE32(UCharCast(key.data() + 28));
input[8] = 0;
input[9] = 0;
input[10] = 0;
input[11] = 0;
}
ChaCha20Aligned::ChaCha20Aligned()
ChaCha20Aligned::ChaCha20Aligned() noexcept
{
memset(input, 0, sizeof(input));
}
@@ -48,12 +50,12 @@ ChaCha20Aligned::~ChaCha20Aligned()
memory_cleanse(input, sizeof(input));
}
ChaCha20Aligned::ChaCha20Aligned(const unsigned char* key32)
ChaCha20Aligned::ChaCha20Aligned(Span<const std::byte> key) noexcept
{
SetKey32(key32);
SetKey(key);
}
void ChaCha20Aligned::Seek64(Nonce96 nonce, uint32_t block_counter)
void ChaCha20Aligned::Seek(Nonce96 nonce, uint32_t block_counter) noexcept
{
input[8] = block_counter;
input[9] = nonce.first;
@@ -61,8 +63,12 @@ void ChaCha20Aligned::Seek64(Nonce96 nonce, uint32_t block_counter)
input[11] = nonce.second >> 32;
}
inline void ChaCha20Aligned::Keystream64(unsigned char* c, size_t blocks)
inline void ChaCha20Aligned::Keystream(Span<std::byte> output) noexcept
{
unsigned char* c = UCharCast(output.data());
size_t blocks = output.size() / BLOCKLEN;
assert(blocks * BLOCKLEN == output.size());
uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
@@ -154,12 +160,18 @@ inline void ChaCha20Aligned::Keystream64(unsigned char* c, size_t blocks)
return;
}
blocks -= 1;
c += 64;
c += BLOCKLEN;
}
}
inline void ChaCha20Aligned::Crypt64(const unsigned char* m, unsigned char* c, size_t blocks)
inline void ChaCha20Aligned::Crypt(Span<const std::byte> in_bytes, Span<std::byte> out_bytes) noexcept
{
assert(in_bytes.size() == out_bytes.size());
const unsigned char* m = UCharCast(in_bytes.data());
unsigned char* c = UCharCast(out_bytes.data());
size_t blocks = out_bytes.size() / BLOCKLEN;
assert(blocks * BLOCKLEN == out_bytes.size());
uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
@@ -268,70 +280,68 @@ inline void ChaCha20Aligned::Crypt64(const unsigned char* m, unsigned char* c, s
return;
}
blocks -= 1;
c += 64;
m += 64;
c += BLOCKLEN;
m += BLOCKLEN;
}
}
void ChaCha20::Keystream(unsigned char* c, size_t bytes)
void ChaCha20::Keystream(Span<std::byte> out) noexcept
{
if (!bytes) return;
if (out.empty()) return;
if (m_bufleft) {
unsigned reuse = std::min<size_t>(m_bufleft, bytes);
memcpy(c, m_buffer + 64 - m_bufleft, reuse);
unsigned reuse = std::min<size_t>(m_bufleft, out.size());
std::copy(m_buffer.end() - m_bufleft, m_buffer.end() - m_bufleft + reuse, out.begin());
m_bufleft -= reuse;
bytes -= reuse;
c += reuse;
out = out.subspan(reuse);
}
if (bytes >= 64) {
size_t blocks = bytes / 64;
m_aligned.Keystream64(c, blocks);
c += blocks * 64;
bytes -= blocks * 64;
if (out.size() >= m_aligned.BLOCKLEN) {
size_t blocks = out.size() / m_aligned.BLOCKLEN;
m_aligned.Keystream(out.first(blocks * m_aligned.BLOCKLEN));
out = out.subspan(blocks * m_aligned.BLOCKLEN);
}
if (bytes) {
m_aligned.Keystream64(m_buffer, 1);
memcpy(c, m_buffer, bytes);
m_bufleft = 64 - bytes;
if (!out.empty()) {
m_aligned.Keystream(m_buffer);
std::copy(m_buffer.begin(), m_buffer.begin() + out.size(), out.begin());
m_bufleft = m_aligned.BLOCKLEN - out.size();
}
}
void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes)
void ChaCha20::Crypt(Span<const std::byte> input, Span<std::byte> output) noexcept
{
if (!bytes) return;
assert(input.size() == output.size());
if (!input.size()) return;
if (m_bufleft) {
unsigned reuse = std::min<size_t>(m_bufleft, bytes);
unsigned reuse = std::min<size_t>(m_bufleft, input.size());
for (unsigned i = 0; i < reuse; i++) {
c[i] = m[i] ^ m_buffer[64 - m_bufleft + i];
output[i] = input[i] ^ m_buffer[m_aligned.BLOCKLEN - m_bufleft + i];
}
m_bufleft -= reuse;
bytes -= reuse;
c += reuse;
m += reuse;
output = output.subspan(reuse);
input = input.subspan(reuse);
}
if (bytes >= 64) {
size_t blocks = bytes / 64;
m_aligned.Crypt64(m, c, blocks);
c += blocks * 64;
m += blocks * 64;
bytes -= blocks * 64;
if (input.size() >= m_aligned.BLOCKLEN) {
size_t blocks = input.size() / m_aligned.BLOCKLEN;
m_aligned.Crypt(input.first(blocks * m_aligned.BLOCKLEN), output.first(blocks * m_aligned.BLOCKLEN));
output = output.subspan(blocks * m_aligned.BLOCKLEN);
input = input.subspan(blocks * m_aligned.BLOCKLEN);
}
if (bytes) {
m_aligned.Keystream64(m_buffer, 1);
for (unsigned i = 0; i < bytes; i++) {
c[i] = m[i] ^ m_buffer[i];
if (!input.empty()) {
m_aligned.Keystream(m_buffer);
for (unsigned i = 0; i < input.size(); i++) {
output[i] = input[i] ^ m_buffer[i];
}
m_bufleft = 64 - bytes;
m_bufleft = m_aligned.BLOCKLEN - input.size();
}
}
ChaCha20::~ChaCha20()
{
memory_cleanse(m_buffer, sizeof(m_buffer));
memory_cleanse(m_buffer.data(), m_buffer.size());
}
FSChaCha20::FSChaCha20(Span<const std::byte> key, uint32_t rekey_interval) noexcept :
m_chacha20(UCharCast(key.data())), m_rekey_interval(rekey_interval)
m_chacha20(key), m_rekey_interval(rekey_interval)
{
assert(key.size() == KEYLEN);
}
@@ -341,20 +351,20 @@ void FSChaCha20::Crypt(Span<const std::byte> input, Span<std::byte> output) noex
assert(input.size() == output.size());
// Invoke internal stream cipher for actual encryption/decryption.
m_chacha20.Crypt(UCharCast(input.data()), UCharCast(output.data()), input.size());
m_chacha20.Crypt(input, output);
// 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));
m_chacha20.Keystream(new_key);
// Update its key.
m_chacha20.SetKey32(UCharCast(new_key));
m_chacha20.SetKey(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);
m_chacha20.Seek({0, ++m_rekey_counter}, 0);
// Reset the chunk counter.
m_chunk_counter = 0;
}

View File

@@ -28,16 +28,22 @@ private:
uint32_t input[12];
public:
ChaCha20Aligned();
/** Expected key length in constructor and SetKey. */
static constexpr unsigned KEYLEN{32};
/** Block size (inputs/outputs to Keystream / Crypt should be multiples of this). */
static constexpr unsigned BLOCKLEN{64};
ChaCha20Aligned() noexcept;
/** Initialize a cipher with specified 32-byte key. */
ChaCha20Aligned(const unsigned char* key32);
ChaCha20Aligned(Span<const std::byte> key) noexcept;
/** Destructor to clean up private memory. */
~ChaCha20Aligned();
/** set 32-byte key. */
void SetKey32(const unsigned char* key32);
/** Set 32-byte key, and seek to nonce 0 and block position 0. */
void SetKey(Span<const std::byte> key) noexcept;
/** Type for 96-bit nonces used by the Set function below.
*
@@ -51,18 +57,19 @@ public:
/** Set the 96-bit nonce and 32-bit block counter.
*
* Block_counter selects a position to seek to (to byte 64*block_counter). After 256 GiB, the
* block counter overflows, and nonce.first is incremented.
* Block_counter selects a position to seek to (to byte BLOCKLEN*block_counter). After 256 GiB,
* the block counter overflows, and nonce.first is incremented.
*/
void Seek64(Nonce96 nonce, uint32_t block_counter);
void Seek(Nonce96 nonce, uint32_t block_counter) noexcept;
/** outputs the keystream of size <64*blocks> into <c> */
void Keystream64(unsigned char* c, size_t blocks);
/** outputs the keystream into out, whose length must be a multiple of BLOCKLEN. */
void Keystream(Span<std::byte> out) noexcept;
/** enciphers the message <input> of length <64*blocks> and write the enciphered representation into <output>
* Used for encryption and decryption (XOR)
/** en/deciphers the message <input> and write the result into <output>
*
* The size of input and output must be equal, and be a multiple of BLOCKLEN.
*/
void Crypt64(const unsigned char* input, unsigned char* output, size_t blocks);
void Crypt(Span<const std::byte> input, Span<std::byte> output) noexcept;
};
/** Unrestricted ChaCha20 cipher. */
@@ -70,42 +77,46 @@ class ChaCha20
{
private:
ChaCha20Aligned m_aligned;
unsigned char m_buffer[64] = {0};
std::array<std::byte, ChaCha20Aligned::BLOCKLEN> m_buffer;
unsigned m_bufleft{0};
public:
ChaCha20() = default;
/** Expected key length in constructor and SetKey. */
static constexpr unsigned KEYLEN = ChaCha20Aligned::KEYLEN;
ChaCha20() noexcept = default;
/** Initialize a cipher with specified 32-byte key. */
ChaCha20(const unsigned char* key32) : m_aligned(key32) {}
ChaCha20(Span<const std::byte> key) noexcept : m_aligned(key) {}
/** Destructor to clean up private memory. */
~ChaCha20();
/** set 32-byte key. */
void SetKey32(const unsigned char* key32)
/** Set 32-byte key, and seek to nonce 0 and block position 0. */
void SetKey(Span<const std::byte> key) noexcept
{
m_aligned.SetKey32(key32);
m_aligned.SetKey(key);
m_bufleft = 0;
}
/** 96-bit nonce type. */
using Nonce96 = ChaCha20Aligned::Nonce96;
/** Set the 96-bit nonce and 32-bit block counter. */
void Seek64(Nonce96 nonce, uint32_t block_counter)
/** Set the 96-bit nonce and 32-bit block counter. See ChaCha20Aligned::Seek. */
void Seek(Nonce96 nonce, uint32_t block_counter) noexcept
{
m_aligned.Seek64(nonce, block_counter);
m_aligned.Seek(nonce, block_counter);
m_bufleft = 0;
}
/** outputs the keystream of size <bytes> into <c> */
void Keystream(unsigned char* c, size_t bytes);
/** enciphers the message <input> of length <bytes> and write the enciphered representation into <output>
* Used for encryption and decryption (XOR)
/** en/deciphers the message <in_bytes> and write the result into <out_bytes>
*
* The size of in_bytes and out_bytes must be equal.
*/
void Crypt(const unsigned char* input, unsigned char* output, size_t bytes);
void Crypt(Span<const std::byte> in_bytes, Span<std::byte> out_bytes) noexcept;
/** outputs the keystream to out. */
void Keystream(Span<std::byte> out) noexcept;
};
/** Forward-secure ChaCha20

View File

@@ -13,7 +13,7 @@
#include <assert.h>
#include <cstddef>
AEADChaCha20Poly1305::AEADChaCha20Poly1305(Span<const std::byte> key) noexcept : m_chacha20(UCharCast(key.data()))
AEADChaCha20Poly1305::AEADChaCha20Poly1305(Span<const std::byte> key) noexcept : m_chacha20(key)
{
assert(key.size() == KEYLEN);
}
@@ -21,7 +21,7 @@ AEADChaCha20Poly1305::AEADChaCha20Poly1305(Span<const std::byte> key) noexcept :
void AEADChaCha20Poly1305::SetKey(Span<const std::byte> key) noexcept
{
assert(key.size() == KEYLEN);
m_chacha20.SetKey32(UCharCast(key.data()));
m_chacha20.SetKey(key);
}
namespace {
@@ -46,8 +46,8 @@ void ComputeTag(ChaCha20& chacha20, Span<const std::byte> aad, Span<const std::b
static const std::byte PADDING[16] = {{}};
// Get block of keystream (use a full 64 byte buffer to avoid the need for chacha20's own buffering).
std::byte first_block[64];
chacha20.Keystream(UCharCast(first_block), sizeof(first_block));
std::byte first_block[ChaCha20Aligned::BLOCKLEN];
chacha20.Keystream(first_block);
// Use the first 32 bytes of the first keystream block as poly1305 key.
Poly1305 poly1305{Span{first_block}.first(Poly1305::KEYLEN)};
@@ -76,12 +76,12 @@ void AEADChaCha20Poly1305::Encrypt(Span<const std::byte> plain1, Span<const std:
assert(cipher.size() == plain1.size() + plain2.size() + EXPANSION);
// Encrypt using ChaCha20 (starting at block 1).
m_chacha20.Seek64(nonce, 1);
m_chacha20.Crypt(UCharCast(plain1.data()), UCharCast(cipher.data()), plain1.size());
m_chacha20.Crypt(UCharCast(plain2.data()), UCharCast(cipher.data() + plain1.size()), plain2.size());
m_chacha20.Seek(nonce, 1);
m_chacha20.Crypt(plain1, cipher.first(plain1.size()));
m_chacha20.Crypt(plain2, cipher.subspan(plain1.size()).first(plain2.size()));
// Seek to block 0, and compute tag using key drawn from there.
m_chacha20.Seek64(nonce, 0);
m_chacha20.Seek(nonce, 0);
ComputeTag(m_chacha20, aad, cipher.first(cipher.size() - EXPANSION), cipher.last(EXPANSION));
}
@@ -90,22 +90,22 @@ bool AEADChaCha20Poly1305::Decrypt(Span<const std::byte> cipher, Span<const std:
assert(cipher.size() == plain1.size() + plain2.size() + EXPANSION);
// Verify tag (using key drawn from block 0).
m_chacha20.Seek64(nonce, 0);
m_chacha20.Seek(nonce, 0);
std::byte expected_tag[EXPANSION];
ComputeTag(m_chacha20, aad, cipher.first(cipher.size() - EXPANSION), expected_tag);
if (timingsafe_bcmp(UCharCast(expected_tag), UCharCast(cipher.last(EXPANSION).data()), EXPANSION)) return false;
// Decrypt (starting at block 1).
m_chacha20.Crypt(UCharCast(cipher.data()), UCharCast(plain1.data()), plain1.size());
m_chacha20.Crypt(UCharCast(cipher.data() + plain1.size()), UCharCast(plain2.data()), plain2.size());
m_chacha20.Crypt(cipher.first(plain1.size()), plain1);
m_chacha20.Crypt(cipher.subspan(plain1.size()).first(plain2.size()), plain2);
return true;
}
void AEADChaCha20Poly1305::Keystream(Nonce96 nonce, Span<std::byte> keystream) noexcept
{
// Skip the first output block, as it's used for generating the poly1305 key.
m_chacha20.Seek64(nonce, 1);
m_chacha20.Keystream(UCharCast(keystream.data()), keystream.size());
m_chacha20.Seek(nonce, 1);
m_chacha20.Keystream(keystream);
}
void FSChaCha20Poly1305::NextPacket() noexcept
@@ -113,7 +113,7 @@ void FSChaCha20Poly1305::NextPacket() noexcept
if (++m_packet_counter == m_rekey_interval) {
// Generate a full block of keystream, to avoid needing the ChaCha20 buffer, even though
// we only need KEYLEN (32) bytes.
std::byte one_block[64];
std::byte one_block[ChaCha20Aligned::BLOCKLEN];
m_aead.Keystream({0xFFFFFFFF, m_rekey_counter}, one_block);
// Switch keys.
m_aead.SetKey(Span{one_block}.first(KEYLEN));

View File

@@ -299,7 +299,8 @@ Num3072 MuHash3072::ToNum3072(Span<const unsigned char> in) {
unsigned char tmp[Num3072::BYTE_SIZE];
uint256 hashed_in{(HashWriter{} << in).GetSHA256()};
ChaCha20Aligned(hashed_in.data()).Keystream64(tmp, Num3072::BYTE_SIZE / 64);
static_assert(sizeof(tmp) % ChaCha20Aligned::BLOCKLEN == 0);
ChaCha20Aligned{MakeByteSpan(hashed_in)}.Keystream(MakeWritableByteSpan(tmp));
Num3072 out{tmp};
return out;