Skip to content

Commit 4a16ada

Browse files
committed
Add AES256-GCM.
1 parent 31dfe95 commit 4a16ada

File tree

2 files changed

+268
-1
lines changed

2 files changed

+268
-1
lines changed

include/bitcoin/system/crypto/aes256.hpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,26 @@ typedef data_array<block_size> block;
3838
constexpr size_t secret_size = bytes<256>;
3939
typedef data_array<secret_size> secret;
4040

41-
/// Perform aes256 encryption/decryption on a data block.
41+
constexpr size_t tag_size = bytes<128>;
42+
typedef data_array<tag_size> tag;
43+
44+
constexpr size_t nonce_size = bytes<96>;
45+
typedef data_array<nonce_size> nonce;
46+
47+
/// Perform AES256 encryption/decryption on a data block.
4248
void encrypt(block& bytes, const secret& key) NOEXCEPT;
4349
void decrypt(block& bytes, const secret& key) NOEXCEPT;
4450

51+
/// Perform AES256-GCM encryption/decryption on a data block.
52+
/// Returns false if authentication fails on decrypt.
53+
/// Tag is always populated on encrypt and verified on decrypt.
54+
/// Nonce must be unique for each encryption with the same key.
55+
/// AAD is optional authenticated additional data (not encrypted).
56+
bool gcm_encrypt(data_chunk& out, tag& out_tag, const data_slice& in,
57+
const secret& key, const nonce& nonce, const data_slice& aad) NOEXCEPT;
58+
bool gcm_decrypt(data_chunk& out, const tag& tag, const data_slice& in,
59+
const secret& key, const nonce& nonce, const data_slice& aad) NOEXCEPT;
60+
4561
} // namespace aes256
4662
} // namespace system
4763
} // namespace libbitcoin

src/crypto/aes256.cpp

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
#include <bitcoin/system/define.hpp>
2323
#include <bitcoin/system/data/data.hpp>
24+
#include <bitcoin/system/endian/endian.hpp>
2425
#include <bitcoin/system/math/math.hpp>
2526

2627
namespace libbitcoin {
@@ -409,6 +410,256 @@ void decrypt(block& bytes, const secret& key) NOEXCEPT
409410
////zeroize(context);
410411
}
411412

413+
// GCM implementation
414+
// ----------------------------------------------------------------------------
415+
416+
template <typename Value, if_integer<Value> = true>
417+
constexpr void bit_xor_into(Value& out, Value other) NOEXCEPT
418+
{
419+
out = bit_xor(out, other);
420+
}
421+
422+
template <size_t Size>
423+
constexpr void array_xor_into(data_array<Size>& out,
424+
const data_array<Size>& other) NOEXCEPT
425+
{
426+
for (size_t index{}; index < Size; ++index)
427+
bit_xor_into(out[index], other[index]);
428+
}
429+
430+
template <size_t Size>
431+
constexpr block array_xor(const data_array<Size>& left,
432+
const data_array<Size>& right) NOEXCEPT
433+
{
434+
block out{ left };
435+
array_xor_into(out, right);
436+
return out;
437+
}
438+
439+
template <size_t Size>
440+
constexpr void array_shift_right_into(data_array<Size>& value) NOEXCEPT
441+
{
442+
bool carry{};
443+
for (auto index = Size; is_nonzero(index); --index)
444+
{
445+
const auto offset = sub1(index);
446+
const bool next = get_right(value[offset]);
447+
shift_right_into(value[offset]);
448+
if (carry) set_left_into(value[offset]);
449+
carry = next;
450+
}
451+
}
452+
453+
// GHASH multiplication in GF(2^128) with reduction polynomial 0xe1 << 120.
454+
static block ghash_multiply(const block& left, block right) NOEXCEPT
455+
{
456+
constexpr auto mask = 0b1110'0001_u8;
457+
458+
block out{};
459+
for (size_t byte{}; byte < block_size; ++byte)
460+
{
461+
for (auto bit = bits<uint8_t>; is_nonzero(bit); --bit)
462+
{
463+
if (get_right(left[byte], sub1(bit)))
464+
{
465+
array_xor_into(out, right);
466+
}
467+
468+
const bool carry = get_right(right[sub1(block_size)]);
469+
array_shift_right_into(right);
470+
471+
if (carry)
472+
{
473+
bit_xor_into(right.front(), mask);
474+
}
475+
}
476+
}
477+
478+
return out;
479+
}
480+
481+
static void ghash_update(block& hash, const data_slice& data,
482+
const block& h_key) NOEXCEPT
483+
{
484+
block block{};
485+
const auto size = data.size();
486+
const auto blocks = floored_divide(size, block_size);
487+
488+
for (size_t index{}; index < blocks; ++index)
489+
{
490+
const auto begin = std::next(data.begin(), index * block_size);
491+
std::copy(begin, std::next(begin, block_size), block.begin());
492+
array_xor_into(hash, block);
493+
hash = ghash_multiply(hash, h_key);
494+
}
495+
496+
if (is_nonzero(floored_modulo(size, block_size)))
497+
{
498+
block = {};
499+
const auto begin = std::next(data.begin(), blocks * block_size);
500+
std::copy(begin, data.end(), block.begin());
501+
array_xor_into(hash, block);
502+
hash = ghash_multiply(hash, h_key);
503+
}
504+
}
505+
506+
static void increment_counter(block& counter) NOEXCEPT
507+
{
508+
constexpr auto size = sizeof(uint32_t);
509+
constexpr auto offest = block_size - size;
510+
auto& slice = array_cast<uint8_t, size, offest>(counter);
511+
slice = to_big_endian(add1(from_big_endian(slice)));
512+
}
513+
514+
static void ctr_encrypt(aes256::context& context, data_chunk& out,
515+
const data_slice& in, block counter) NOEXCEPT
516+
{
517+
block stream{};
518+
const auto size = in.size();
519+
const auto blocks = floored_divide(size, block_size);
520+
out.resize(size);
521+
522+
for (size_t block{}; block < blocks; ++block)
523+
{
524+
stream = counter;
525+
encrypt_block(context, stream);
526+
increment_counter(counter);
527+
528+
for (size_t index{}; index < block_size; ++index)
529+
{
530+
const auto at = block * block_size + index;
531+
out[at] = bit_xor(in[at], stream[index]);
532+
}
533+
}
534+
535+
if (const auto remain = floored_modulo(size, block_size);
536+
is_nonzero(remain))
537+
{
538+
stream = counter;
539+
encrypt_block(context, stream);
540+
541+
for (size_t index{}; index < remain; ++index)
542+
{
543+
const auto at = blocks * block_size + index;
544+
out[at] = bit_xor(in[at], stream[index]);
545+
}
546+
}
547+
}
548+
549+
constexpr size_t pad_size(size_t value_size) NOEXCEPT
550+
{
551+
return (block_size - (value_size % block_size)) % block_size;
552+
}
553+
554+
// GCM encrypt/decrypt
555+
// ----------------------------------------------------------------------------
556+
557+
constexpr auto one32 = to_big_endian<uint32_t>(1);
558+
559+
bool gcm_encrypt(data_chunk& out, tag& out_tag, const data_slice& in,
560+
const secret& key, const nonce& nonce, const data_slice& aad) NOEXCEPT
561+
{
562+
if (is_multiply_overflow<uint64_t>(aad.size(), byte_bits) ||
563+
is_multiply_overflow<uint64_t>(in.size(), byte_bits))
564+
return false;
565+
566+
aes256::context context{};
567+
initialize(context, key);
568+
569+
// Compute H = AES(0).
570+
block h_key{};
571+
encrypt_block(context, h_key);
572+
573+
// Initialize counter: nonce || 0x00000001 (BE).
574+
block counter{};
575+
array_cast<uint8_t, 12, 0>(counter) = nonce;
576+
array_cast<uint8_t, 4, 12>(counter) = one32;
577+
578+
// Encrypt plaintext to ciphertext (CTR mode).
579+
ctr_encrypt(context, out, in, counter);
580+
581+
// Compute GHASH over aad || pad || out || pad || len(aad) || len(out).
582+
data_chunk pad{};
583+
block hash{};
584+
585+
ghash_update(hash, aad, h_key);
586+
pad.resize(pad_size(aad.size()));
587+
if (!pad.empty())
588+
ghash_update(hash, pad, h_key);
589+
590+
ghash_update(hash, out, h_key);
591+
pad.resize(pad_size(out.size()));
592+
if (!pad.empty())
593+
ghash_update(hash, pad, h_key);
594+
595+
// Append lengths (out.size() is same as in.size() - overflow guarded).
596+
block lengths{};
597+
const auto aad_length = aad.size() * byte_bits;
598+
const auto out_length = out.size() * byte_bits;
599+
array_cast<uint8_t, 8, 0>(lengths) = to_big_endian<uint64_t>(aad_length);
600+
array_cast<uint8_t, 8, 8>(lengths) = to_big_endian<uint64_t>(out_length);
601+
ghash_update(hash, lengths, h_key);
602+
603+
// Initialize counter: nonce || 0x00000001 (BE) (and encrypt).
604+
array_cast<uint8_t, 12, 0>(counter) = nonce;
605+
array_cast<uint8_t, 4, 12>(counter) = one32;
606+
encrypt_block(context, counter);
607+
608+
// Tag = GHASH ^ E(counter0).
609+
out_tag = array_xor(hash, counter);
610+
return true;
611+
}
612+
613+
bool gcm_decrypt(data_chunk& out, const tag& tag, const data_slice& in,
614+
const secret& key, const nonce& nonce, const data_slice& aad) NOEXCEPT
615+
{
616+
if (is_multiply_overflow<uint64_t>(aad.size(), byte_bits) ||
617+
is_multiply_overflow<uint64_t>(in.size(), byte_bits))
618+
return false;
619+
620+
aes256::context context{};
621+
initialize(context, key);
622+
623+
// Compute H = AES(0).
624+
block h_key{};
625+
encrypt_block(context, h_key);
626+
627+
// Compute GHASH over aad || pad || in || pad || len(aad) || len(in).
628+
data_chunk pad{};
629+
block hash{};
630+
631+
ghash_update(hash, aad, h_key);
632+
pad.resize(pad_size(aad.size()));
633+
if (!pad.empty())
634+
ghash_update(hash, pad, h_key);
635+
636+
ghash_update(hash, in, h_key);
637+
pad.resize(pad_size(in.size()));
638+
if (!pad.empty())
639+
ghash_update(hash, pad, h_key);
640+
641+
block lengths{};
642+
const auto aad_length = aad.size() * byte_bits;
643+
const auto in_length = in.size() * byte_bits;
644+
array_cast<uint8_t, 8, 0>(lengths) = to_big_endian<uint64_t>(aad_length);
645+
array_cast<uint8_t, 8, 8>(lengths) = to_big_endian<uint64_t>(in_length);
646+
ghash_update(hash, lengths, h_key);
647+
648+
// Initialize counter: nonce || 0x00000001 (BE) (and encrypt).
649+
block counter{};
650+
array_cast<uint8_t, 12, 0>(counter) = nonce;
651+
array_cast<uint8_t, 4, 12>(counter) = one32;
652+
encrypt_block(context, counter);
653+
654+
// Verify tag = GHASH ^ E(counter0)
655+
if (array_xor(hash, counter) != tag)
656+
return false;
657+
658+
// Decrypt ciphertext to plaintext (CTR mode, same as encrypt).
659+
ctr_encrypt(context, out, in, counter);
660+
return true;
661+
}
662+
412663
} // namespace aes256
413664
} // namespace system
414665
} // namespace libbitcoin

0 commit comments

Comments
 (0)