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

@@ -133,27 +133,27 @@ static void TestAES256CBC(const std::string &hexkey, const std::string &hexiv, b
static void TestChaCha20(const std::string &hex_message, const std::string &hexkey, ChaCha20::Nonce96 nonce, uint32_t seek, const std::string& hexout)
{
std::vector<unsigned char> key = ParseHex(hexkey);
auto key = ParseHex<std::byte>(hexkey);
assert(key.size() == 32);
std::vector<unsigned char> m = ParseHex(hex_message);
ChaCha20 rng(key.data());
rng.Seek64(nonce, seek);
std::vector<unsigned char> outres;
auto m = ParseHex<std::byte>(hex_message);
ChaCha20 rng{key};
rng.Seek(nonce, seek);
std::vector<std::byte> outres;
outres.resize(hexout.size() / 2);
assert(hex_message.empty() || m.size() * 2 == hexout.size());
// perform the ChaCha20 round(s), if message is provided it will output the encrypted ciphertext otherwise the keystream
if (!hex_message.empty()) {
rng.Crypt(m.data(), outres.data(), outres.size());
rng.Crypt(m, outres);
} else {
rng.Keystream(outres.data(), outres.size());
rng.Keystream(outres);
}
BOOST_CHECK_EQUAL(hexout, HexStr(outres));
if (!hex_message.empty()) {
// Manually XOR with the keystream and compare the output
rng.Seek64(nonce, seek);
std::vector<unsigned char> only_keystream(outres.size());
rng.Keystream(only_keystream.data(), only_keystream.size());
rng.Seek(nonce, seek);
std::vector<std::byte> only_keystream(outres.size());
rng.Keystream(only_keystream);
for (size_t i = 0; i != m.size(); i++) {
outres[i] = m[i] ^ only_keystream[i];
}
@@ -167,14 +167,14 @@ static void TestChaCha20(const std::string &hex_message, const std::string &hexk
lens[1] = InsecureRandRange(hexout.size() / 2U + 1U - lens[0]);
lens[2] = hexout.size() / 2U - lens[0] - lens[1];
rng.Seek64(nonce, seek);
outres.assign(hexout.size() / 2U, 0);
rng.Seek(nonce, seek);
outres.assign(hexout.size() / 2U, {});
size_t pos = 0;
for (int j = 0; j < 3; ++j) {
if (!hex_message.empty()) {
rng.Crypt(m.data() + pos, outres.data() + pos, lens[j]);
rng.Crypt(Span{m}.subspan(pos, lens[j]), Span{outres}.subspan(pos, lens[j]));
} else {
rng.Keystream(outres.data() + pos, lens[j]);
rng.Keystream(Span{outres}.subspan(pos, lens[j]));
}
pos += lens[j];
}
@@ -190,7 +190,7 @@ static void TestFSChaCha20(const std::string& hex_plaintext, const std::string&
auto plaintext = ParseHex<std::byte>(hex_plaintext);
auto fsc20 = FSChaCha20{key, rekey_interval};
auto c20 = ChaCha20{UCharCast(key.data())};
auto c20 = ChaCha20{key};
std::vector<std::byte> fsc20_output;
fsc20_output.resize(plaintext.size());
@@ -200,23 +200,23 @@ static void TestFSChaCha20(const std::string& hex_plaintext, const std::string&
for (size_t i = 0; i < rekey_interval; i++) {
fsc20.Crypt(plaintext, fsc20_output);
c20.Crypt(UCharCast(plaintext.data()), UCharCast(c20_output.data()), plaintext.size());
c20.Crypt(plaintext, c20_output);
BOOST_CHECK(c20_output == fsc20_output);
}
// At the rotation interval, the outputs will no longer match
fsc20.Crypt(plaintext, fsc20_output);
auto c20_copy = c20;
c20.Crypt(UCharCast(plaintext.data()), UCharCast(c20_output.data()), plaintext.size());
c20.Crypt(plaintext, c20_output);
BOOST_CHECK(c20_output != fsc20_output);
std::byte new_key[FSChaCha20::KEYLEN];
c20_copy.Keystream(UCharCast(new_key), sizeof(new_key));
c20.SetKey32(UCharCast(new_key));
c20.Seek64({0, 1}, 0);
c20_copy.Keystream(new_key);
c20.SetKey(new_key);
c20.Seek({0, 1}, 0);
// Outputs should match again after simulating key rotation
c20.Crypt(UCharCast(plaintext.data()), UCharCast(c20_output.data()), plaintext.size());
c20.Crypt(plaintext, c20_output);
BOOST_CHECK(c20_output == fsc20_output);
BOOST_CHECK_EQUAL(HexStr(fsc20_output), ciphertext_after_rotation);
@@ -823,20 +823,20 @@ BOOST_AUTO_TEST_CASE(chacha20_testvector)
BOOST_AUTO_TEST_CASE(chacha20_midblock)
{
auto key = ParseHex("0000000000000000000000000000000000000000000000000000000000000000");
ChaCha20 c20{key.data()};
auto key = ParseHex<std::byte>("0000000000000000000000000000000000000000000000000000000000000000");
ChaCha20 c20{key};
// get one block of keystream
unsigned char block[64];
c20.Keystream(block, sizeof(block));
unsigned char b1[5], b2[7], b3[52];
c20 = ChaCha20{key.data()};
c20.Keystream(b1, 5);
c20.Keystream(b2, 7);
c20.Keystream(b3, 52);
std::byte block[64];
c20.Keystream(block);
std::byte b1[5], b2[7], b3[52];
c20 = ChaCha20{key};
c20.Keystream(b1);
c20.Keystream(b2);
c20.Keystream(b3);
BOOST_CHECK_EQUAL(0, memcmp(b1, block, 5));
BOOST_CHECK_EQUAL(0, memcmp(b2, block + 5, 7));
BOOST_CHECK_EQUAL(0, memcmp(b3, block + 12, 52));
BOOST_CHECK(Span{block}.first(5) == Span{b1});
BOOST_CHECK(Span{block}.subspan(5, 7) == Span{b2});
BOOST_CHECK(Span{block}.last(52) == Span{b3});
}
BOOST_AUTO_TEST_CASE(poly1305_testvector)

View File

@@ -20,17 +20,17 @@ FUZZ_TARGET(crypto_chacha20)
ChaCha20 chacha20;
if (fuzzed_data_provider.ConsumeBool()) {
const std::vector<unsigned char> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32);
chacha20 = ChaCha20{key.data()};
chacha20 = ChaCha20{MakeByteSpan(key)};
}
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) {
CallOneOf(
fuzzed_data_provider,
[&] {
std::vector<unsigned char> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32);
chacha20.SetKey32(key.data());
chacha20.SetKey(MakeByteSpan(key));
},
[&] {
chacha20.Seek64(
chacha20.Seek(
{
fuzzed_data_provider.ConsumeIntegral<uint32_t>(),
fuzzed_data_provider.ConsumeIntegral<uint64_t>()
@@ -38,12 +38,12 @@ FUZZ_TARGET(crypto_chacha20)
},
[&] {
std::vector<uint8_t> output(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096));
chacha20.Keystream(output.data(), output.size());
chacha20.Keystream(MakeWritableByteSpan(output));
},
[&] {
std::vector<uint8_t> output(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096));
const std::vector<uint8_t> input = ConsumeFixedLengthByteVector(fuzzed_data_provider, output.size());
chacha20.Crypt(input.data(), output.data(), input.size());
chacha20.Crypt(MakeByteSpan(input), MakeWritableByteSpan(output));
});
}
}
@@ -62,9 +62,8 @@ template<bool UseCrypt>
void ChaCha20SplitFuzz(FuzzedDataProvider& provider)
{
// Determine key, iv, start position, length.
unsigned char key[32] = {0};
auto key_bytes = provider.ConsumeBytes<unsigned char>(32);
std::copy(key_bytes.begin(), key_bytes.end(), key);
auto key_bytes = provider.ConsumeBytes<std::byte>(ChaCha20::KEYLEN);
key_bytes.resize(ChaCha20::KEYLEN);
uint64_t iv = provider.ConsumeIntegral<uint64_t>();
uint32_t iv_prefix = provider.ConsumeIntegral<uint32_t>();
uint64_t total_bytes = provider.ConsumeIntegralInRange<uint64_t>(0, 1000000);
@@ -72,13 +71,13 @@ void ChaCha20SplitFuzz(FuzzedDataProvider& provider)
uint32_t seek = provider.ConsumeIntegralInRange<uint32_t>(0, ~(uint32_t)(total_bytes >> 6));
// Initialize two ChaCha20 ciphers, with the same key/iv/position.
ChaCha20 crypt1(key);
ChaCha20 crypt2(key);
crypt1.Seek64({iv_prefix, iv}, seek);
crypt2.Seek64({iv_prefix, iv}, seek);
ChaCha20 crypt1(key_bytes);
ChaCha20 crypt2(key_bytes);
crypt1.Seek({iv_prefix, iv}, seek);
crypt2.Seek({iv_prefix, iv}, seek);
// Construct vectors with data.
std::vector<unsigned char> data1, data2;
std::vector<std::byte> data1, data2;
data1.resize(total_bytes);
data2.resize(total_bytes);
@@ -90,14 +89,14 @@ void ChaCha20SplitFuzz(FuzzedDataProvider& provider)
uint64_t bytes = 0;
while (bytes < (total_bytes & ~uint64_t{7})) {
uint64_t val = rng();
WriteLE64(data1.data() + bytes, val);
WriteLE64(data2.data() + bytes, val);
WriteLE64(UCharCast(data1.data() + bytes), val);
WriteLE64(UCharCast(data2.data() + bytes), val);
bytes += 8;
}
if (bytes < total_bytes) {
unsigned char valbytes[8];
std::byte valbytes[8];
uint64_t val = rng();
WriteLE64(valbytes, val);
WriteLE64(UCharCast(valbytes), val);
std::copy(valbytes, valbytes + (total_bytes - bytes), data1.data() + bytes);
std::copy(valbytes, valbytes + (total_bytes - bytes), data2.data() + bytes);
}
@@ -108,9 +107,9 @@ void ChaCha20SplitFuzz(FuzzedDataProvider& provider)
// Encrypt data1, the whole array at once.
if constexpr (UseCrypt) {
crypt1.Crypt(data1.data(), data1.data(), total_bytes);
crypt1.Crypt(data1, data1);
} else {
crypt1.Keystream(data1.data(), total_bytes);
crypt1.Keystream(data1);
}
// Encrypt data2, in at most 256 chunks.
@@ -127,9 +126,9 @@ void ChaCha20SplitFuzz(FuzzedDataProvider& provider)
// This tests that Keystream() has the same behavior as Crypt() applied
// to 0x00 input bytes.
if (UseCrypt || provider.ConsumeBool()) {
crypt2.Crypt(data2.data() + bytes2, data2.data() + bytes2, now);
crypt2.Crypt(Span{data2}.subspan(bytes2, now), Span{data2}.subspan(bytes2, now));
} else {
crypt2.Keystream(data2.data() + bytes2, now);
crypt2.Keystream(Span{data2}.subspan(bytes2, now));
}
bytes2 += now;
if (is_last) break;

View File

@@ -275,14 +275,14 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20)
if (fuzzed_data_provider.ConsumeBool()) {
const std::vector<unsigned char> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32);
chacha20 = ChaCha20{key.data()};
chacha20 = ChaCha20{MakeByteSpan(key)};
ECRYPT_keysetup(&ctx, key.data(), key.size() * 8, 0);
} else {
// The default ChaCha20 constructor is equivalent to using the all-0 key.
ECRYPT_keysetup(&ctx, ZEROKEY, 256, 0);
}
// ECRYPT_keysetup() doesn't set the counter and nonce to 0 while SetKey32() does
// ECRYPT_keysetup() doesn't set the counter and nonce to 0 while SetKey() does
static const uint8_t iv[8] = {0, 0, 0, 0, 0, 0, 0, 0};
ChaCha20::Nonce96 nonce{0, 0};
uint32_t counter{0};
@@ -293,11 +293,11 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20)
fuzzed_data_provider,
[&] {
const std::vector<unsigned char> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32);
chacha20.SetKey32(key.data());
chacha20.SetKey(MakeByteSpan(key));
nonce = {0, 0};
counter = 0;
ECRYPT_keysetup(&ctx, key.data(), key.size() * 8, 0);
// ECRYPT_keysetup() doesn't set the counter and nonce to 0 while SetKey32() does
// ECRYPT_keysetup() doesn't set the counter and nonce to 0 while SetKey() does
uint8_t iv[8] = {0, 0, 0, 0, 0, 0, 0, 0};
ECRYPT_ivsetup(&ctx, iv);
},
@@ -306,7 +306,7 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20)
uint64_t iv = fuzzed_data_provider.ConsumeIntegral<uint64_t>();
nonce = {iv_prefix, iv};
counter = fuzzed_data_provider.ConsumeIntegral<uint32_t>();
chacha20.Seek64(nonce, counter);
chacha20.Seek(nonce, counter);
ctx.input[12] = counter;
ctx.input[13] = iv_prefix;
ctx.input[14] = iv;
@@ -315,7 +315,7 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20)
[&] {
uint32_t integralInRange = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096);
std::vector<uint8_t> output(integralInRange);
chacha20.Keystream(output.data(), output.size());
chacha20.Keystream(MakeWritableByteSpan(output));
std::vector<uint8_t> djb_output(integralInRange);
ECRYPT_keystream_bytes(&ctx, djb_output.data(), djb_output.size());
assert(output == djb_output);
@@ -324,7 +324,7 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20)
counter += (integralInRange + 63) >> 6;
if (counter < old_counter) ++nonce.first;
if (integralInRange & 63) {
chacha20.Seek64(nonce, counter);
chacha20.Seek(nonce, counter);
}
assert(counter == ctx.input[12]);
},
@@ -332,7 +332,7 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20)
uint32_t integralInRange = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096);
std::vector<uint8_t> output(integralInRange);
const std::vector<uint8_t> input = ConsumeFixedLengthByteVector(fuzzed_data_provider, output.size());
chacha20.Crypt(input.data(), output.data(), input.size());
chacha20.Crypt(MakeByteSpan(input), MakeWritableByteSpan(output));
std::vector<uint8_t> djb_output(integralInRange);
ECRYPT_encrypt_bytes(&ctx, input.data(), djb_output.data(), input.size());
assert(output == djb_output);
@@ -341,7 +341,7 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20)
counter += (integralInRange + 63) >> 6;
if (counter < old_counter) ++nonce.first;
if (integralInRange & 63) {
chacha20.Seek64(nonce, counter);
chacha20.Seek(nonce, counter);
}
assert(counter == ctx.input[12]);
});