#ifndef RANDOM_CHACHA20_2022_05_06_H #define RANDOM_CHACHA20_2022_05_06_H #include #include #include #include struct chacha20_block { // This is basically a random number generator seeded with key and nonce. // Generates 64 random bytes every time count is incremented. private: std::array state { }; public: static void unpack4(std::uint32_t src, std::uint8_t* dst) { // TBD we can lost data with the conversion // from std::uint32_t src to std::uint8_t dst dst[0U] = static_cast(src >> 0U); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) dst[1U] = static_cast(src >> 8U); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) dst[2U] = static_cast(src >> 16U); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) dst[3U] = static_cast(src >> 24U); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) } chacha20_block(const std::uint8_t* key, const std::uint8_t* nonce) // NOLINT(bugprone-easily-swappable-parameters) { constexpr std::array magic_constant = { 'e', 'x', 'p', 'a', 'n', 'd', ' ', '3', '2', '-', 'b', 'y', 't', 'e', ' ', 'k', '\0' }; state[ 0U] = al::crypto::math::utils::load_u32<4U>((magic_constant.data() + static_cast(0x00U * 0x04U))); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) state[ 1U] = al::crypto::math::utils::load_u32<4U>((magic_constant.data() + static_cast(0x01U * 0x04U))); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) state[ 2U] = al::crypto::math::utils::load_u32<4U>((magic_constant.data() + static_cast(0x02U * 0x04U))); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) state[ 3U] = al::crypto::math::utils::load_u32<4U>((magic_constant.data() + static_cast(0x03U * 0x04U))); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) state[ 4U] = al::crypto::math::utils::load_u32<4U>((key + static_cast(0x00U * 0x04U))); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) state[ 5U] = al::crypto::math::utils::load_u32<4U>((key + static_cast(0x01U * 0x04U))); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) state[ 6U] = al::crypto::math::utils::load_u32<4U>((key + static_cast(0x02U * 0x04U))); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) state[ 7U] = al::crypto::math::utils::load_u32<4U>((key + static_cast(0x03U * 0x04U))); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) state[ 8U] = al::crypto::math::utils::load_u32<4U>((key + static_cast(0x04U * 0x04U))); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) state[ 9U] = al::crypto::math::utils::load_u32<4U>((key + static_cast(0x05U * 0x04U))); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) state[10U] = al::crypto::math::utils::load_u32<4U>((key + static_cast(0x06U * 0x04U))); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) state[11U] = al::crypto::math::utils::load_u32<4U>((key + static_cast(0x07U * 0x04U))); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) // 64 bit counter initialized to zero by default. state[12U] = 0U; state[13U] = 0U; state[14U] = al::crypto::math::utils::load_u32<4U>((nonce + static_cast(0x00U * 0x04U))); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) state[15U] = al::crypto::math::utils::load_u32<4U>((nonce + static_cast(0x01U * 0x04U))); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) } void set_counter(std::uint64_t counter) { // Want to process many blocks in parallel? // No problem! Just set the counter to the block you want to process. state[12U] = std::uint32_t(counter); state[13U] = static_cast((counter >> 32U)); } template static inline void CHACHA20_QUARTERROUND( InputIteratorType x_dst, const int a, const int b, const int c, const int d) { using input_value_type = typename std::iterator_traits::value_type; static_assert(std::numeric_limits::digits == 32, "Error: Check input size on Quarter Round"); x_dst[a] = static_cast(x_dst[a] + x_dst[b]); x_dst[d] = al::crypto::math::utils::rotl(x_dst[d] ^ x_dst[a], 16); x_dst[c] = static_cast(x_dst[c] + x_dst[d]); x_dst[b] = al::crypto::math::utils::rotl(x_dst[b] ^ x_dst[c], 12); x_dst[a] = static_cast(x_dst[a] + x_dst[b]); x_dst[d] = al::crypto::math::utils::rotl(x_dst[d] ^ x_dst[a], 8); x_dst[c] = static_cast(x_dst[c] + x_dst[d]); x_dst[b] = al::crypto::math::utils::rotl(x_dst[b] ^ x_dst[c], 7); } void next(std::uint32_t* result) { // This is where the crazy voodoo magic happens. // Mix the bytes a lot and hope that nobody finds out how to undo it. for(std::size_t i = 0U; i < 16U; ++i) { result[i] = state[i]; // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index,cppcoreguidelines-pro-bounds-pointer-arithmetic) } for(auto i = 0U; i < 10U; ++i) { CHACHA20_QUARTERROUND(result, 0, 4, 8, 12); CHACHA20_QUARTERROUND(result, 1, 5, 9, 13); CHACHA20_QUARTERROUND(result, 2, 6, 10, 14); CHACHA20_QUARTERROUND(result, 3, 7, 11, 15); CHACHA20_QUARTERROUND(result, 0, 5, 10, 15); CHACHA20_QUARTERROUND(result, 1, 6, 11, 12); CHACHA20_QUARTERROUND(result, 2, 7, 8, 13); CHACHA20_QUARTERROUND(result, 3, 4, 9, 14); } for(std::size_t i = 0U; i < 16U; ++i) { result[i] += state[i]; // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index, cppcoreguidelines-pro-bounds-pointer-arithmetic) } std::uint32_t* counter = (state.data() + 12U); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) // increment counter ++counter[0U]; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) if(UINT32_C(0) == counter[0U]) // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) { // wrap around occured, increment higher 32 bits of counter ++counter[1U]; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) // Limited to 2^64 blocks of 64 bytes each. // If you want to process more than 1180591620717411303424 bytes // you have other problems. // We could keep counting with counter[2] and counter[3] (nonce), // but then we risk reusing the nonce which is very bad. // TBD: assert(0 != counter[1U]); } } void next(std::uint8_t* result8) { std::array temp32 { }; next(temp32.data()); for (std::size_t i = 0U; i < 16U; ++i) { unpack4(temp32[i], result8 + (i * 4)); // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index, cppcoreguidelines-pro-bounds-pointer-arithmetic) } } }; struct chacha20 { // XORs plaintext/encrypted bytes with whatever Chacha20Block generates. // Encryption and decryption are the same operation. // Chacha20Blocks can be skipped, so this can be done in parallel. // If keys are reused, messages can be decrypted. // Known encrypted text with known position can be tampered with. // See https://en.wikipedia.org/wiki/Stream_cipher_attack public: chacha20(const std::uint8_t* key, const std::uint8_t* nonce, std::uint64_t counter = UINT64_C(0)): block (key, nonce), keystream8 { 0U }, position (64U) { block.set_counter(counter); } auto crypt(std::uint8_t* bytes, std::size_t n_bytes) -> void { // initialize keystream8 with 0 keystream8.fill(0U); for (std::size_t i = 0U; i < n_bytes; ++i) { if (position >= 64U) { block.next(keystream8.data()); position = 0U; } const auto value_of_xor = static_cast(bytes[i] ^ keystream8[position]); // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index, cppcoreguidelines-pro-bounds-pointer-arithmetic) bytes[i] = value_of_xor; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) ++position; } } private: chacha20_block block; std::array keystream8; std::size_t position; }; namespace adler32 { inline auto adler32(const std::uint8_t* bytes, std::size_t n_bytes) -> std::uint32_t { std::uint32_t a = 0x01U; std::uint32_t b = 0x00U; for(auto i = static_cast(0U); i < n_bytes; ++i) { a = ((a + bytes[i]) % UINT16_C(0xFFF1U)); //NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) b = ((b + a) % UINT16_C(0xFFF1U)); } return ((b << UINT8_C(0x10U)) | a); } } // namespace adler32 #endif // RANDOM_CHACHA20_2022_05_06_H