Skip to content

Instantly share code, notes, and snippets.

@Sam-Belliveau
Last active September 26, 2021 20:22
Show Gist options
  • Save Sam-Belliveau/1ebc71da8c841e0ea0b9bdd308cf0fa1 to your computer and use it in GitHub Desktop.
Save Sam-Belliveau/1ebc71da8c841e0ea0b9bdd308cf0fa1 to your computer and use it in GitHub Desktop.
Fast, Secure, Robust, and Documented Implementation of ChaCha for C++. Compatible with the <random> header file and interfaces
/**
* MIT License
*
* Copyright (c) 2021, Samuel Robert Belliveau
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef SAM_BELLIVEAU_CHA_CHA_LIBRARY
#define SAM_BELLIVEAU_CHA_CHA_LIBRARY 1 // Just incase somebody wants to use it like a bool
#include <algorithm> // std::copy - copy arrays
#include <cstdint> // std::uint8_t, std::uint32_t, std::uint64_t, std::size_t
#include <cstdlib> // std::time(), std::rand()
#include <limits> // std::numeric_limits - min/max
#include <array> // std::array - used for passing and storing sequences
#include <random> // std::random_device - Used for collecting entropy
#include <chrono> // std::chrono::high_resolution_clock - Used for collecting entropy
namespace Cha
{
// Types used throughout code
namespace Type
{
// Base Types
using Byte = std::uint8_t;
using Word = std::uint32_t;
using DWord = std::uint64_t;
using Size = std::size_t;
// State Type
using State = std::array<Word, 16>;
// Input To State Types
using Key = std::array<Word, 8>;
using Nonce = DWord;
using Constant = std::array<Word, 4>;
using Position = DWord;
using Counter = Size;
// Check If The Input Types Add Up To The State Size
static_assert(sizeof(State) == sizeof(Key) + sizeof(Nonce) + sizeof(Constant) + sizeof(Position),
"Error With Size of Variables -> does not sum to Size of State");
}
// Default Values for the ChaCha engine
namespace Default {
// SHA256 Hash of string "ChaCha" as default key
static constexpr Type::Key Key = {
0xde68d987, 0x5e9fd141, 0x16a1a3bf, 0xedcbdcfe,
0x99f95736, 0xc7455d65, 0x485c2700, 0x48b98907
};
// Random nonce number
static constexpr Type::Nonce Nonce = 0xDEADBEEFDEADBEEF;
// "expand 32-byte k"
static constexpr Type::Constant Constant = {0x65787061, 0x6e642033, 0x322d6279, 0x7465206b};
// Starting position counter at 0
static constexpr Type::Position Position = 0;
// Starting counter at 0
static constexpr Type::Counter Counter = 0;
}
// Generate High Entropy values for Seeding
namespace Generate {
static constexpr Type::Size DefaultRounds = 64;
// Generate High Entropy Base Types
namespace Raw {
/**
* Slow, Secure way to generate a maximum entropy
* 64 bit integer. Used for seeding.
*/
static Type::DWord DWord(Type::Size rounds = DefaultRounds)
{
// We can use random_device as an entropy source. Even if it isn't
// cryptographically secure, the mixing process will make it secure
std::random_device entropy_device;
// Pool where all the entropy is stored
Type::DWord entropy_pool = static_cast<Type::DWord>(std::time(NULL));
// Rotate entropy pool around while xor-ing entropy into it
for(Type::Size i = 0; i < rounds; ++i) {
// Rotate entropy_pool left by 23 bits
entropy_pool = (entropy_pool << 23) | (entropy_pool >> 41);
entropy_pool ^= static_cast<Type::DWord>(entropy_device());
entropy_pool ^= static_cast<Type::DWord>(std::chrono::high_resolution_clock::now().time_since_epoch().count());
entropy_pool ^= static_cast<Type::DWord>(std::rand());
}
return entropy_pool;
}
/**
* Slow, Secure way to generate a maximum entropy
* 32 bit integer. Used for seeding.
*/
static Type::Word Word(Type::Size rounds = DefaultRounds)
{
Type::DWord result = DWord(rounds);
result ^= result >> 32;
return static_cast<Type::Word>(result & 0xffffffff);
}
}
/**
* Slow, Secure way to generate a maximum entropy
* Nonce value. Used for seeding.
*/
static Type::Nonce Nonce(Type::Size rounds = DefaultRounds)
{ return Raw::DWord(rounds); }
/**
* Slow, Secure way to generate a maximum entropy
* Key value. Used for seeding.
*/
static Type::Key Key(Type::Size rounds = DefaultRounds)
{
Type::Key output;
for(auto& i : output) i = Raw::Word(rounds);
return output;
}
}
// Class that does the randomness stuff
template<Type::Size Rounds>
class ChaCha
{
public:
/**
* Creates a high quality seeded engine using
* the functions found in generate
*/
static ChaCha RandomEngine(Type::Size rounds = Generate::DefaultRounds) noexcept
{ return ChaCha(Generate::Key(rounds), Generate::Nonce(rounds)); }
private: // State Variables
Type::State state_;
Type::Position pos_;
Type::Counter ctr_;
Type::Nonce nonce_;
public: // Constructors
/**
* Seeds the engine by setting up every
* part of the state. Refer to
* https://en.wikipedia.org/wiki/Salsa20#ChaCha_variant
* for more information
*/
inline constexpr ChaCha(
const Type::Key& key = Default::Key,
const Type::Nonce nonce = Default::Nonce) noexcept
{ seed(key, nonce); }
/**
* Seeds and Resets the engine with a 64bit
* unsigned integer by using it as the nonce value
*/
inline constexpr ChaCha(const Type::Nonce nonce) noexcept { seed(nonce); }
/**
* Default Copy Constructors
*/
inline constexpr ChaCha(const ChaCha& other) noexcept = default;
inline constexpr ChaCha& operator=(const ChaCha& other) = default;
public: // Comparison Operators
template<Type::Size R>
inline constexpr bool operator==(const ChaCha<R>& other) const noexcept
{
return (state_ == other.state_)
&& (pos_ == other.pos_)
&& (nonce_ == other.nonce_)
&& (Rounds == R);
}
template<Type::Size R>
inline constexpr bool operator!=(const ChaCha<R>& other) const noexcept
{ return !(operator==(other)); }
private: // Setting Up State (Private)
/**
* Move constant variable into
* the state and its positions
*/
inline constexpr void set_state_constant(const Type::Constant& constant)
{
// Copy Constant into slots 0 - 3
state_[0x0] = constant[0x0]; state_[0x1] = constant[0x1];
state_[0x2] = constant[0x2]; state_[0x3] = constant[0x3];
}
/**
* Move key variable into the
* state and its positions
*/
inline constexpr void set_state_key(const Type::Key& key)
{
// Copy Key into slots 4 - 11
state_[0x4] = key[0x0]; state_[0x5] = key[0x1];
state_[0x6] = key[0x2]; state_[0x7] = key[0x3];
state_[0x8] = key[0x4]; state_[0x9] = key[0x5];
state_[0xA] = key[0x6]; state_[0xB] = key[0x7];
}
/**
* Put a 64 bit word into two
* 32 bit array indexes
*/
template<Type::Size Position>
inline constexpr void set_state_dword(const Type::DWord word) noexcept
{
state_[Position + 0] = (word >> 32) & 0xffffffff;
state_[Position + 1] = (word >> 0) & 0xffffffff;
}
/**
* This function just has the position of
* the position variable built in
*/
inline constexpr void set_state_pos(const Type::Position pos) noexcept
{ set_state_dword<12>(pos); }
/**
* This function just has the position of
* the nonce variable built in
*/
inline constexpr void set_state_nonce(const Type::Nonce nonce) noexcept
{ set_state_dword<14>(nonce); }
public: // Setting Up State (Public)
/**
* Seeds and Resets the engine by setting
* up every part of the state. Refer to
* https://en.wikipedia.org/wiki/Salsa20#ChaCha_variant
* for more information
*/
inline constexpr void seed(
const Type::Key& key = Default::Key,
const Type::Nonce nonce = Default::Nonce
) noexcept {
// Set the Nonce variable and keep track of it
nonce_ = nonce;
// Set Position to default start
pos_ = Default::Position;
// Set Counter to default start
ctr_ = Default::Counter;
// Move Variables into state
set_state_constant(Default::Constant);
set_state_key(key);
set_state_nonce(nonce_);
set_state_pos(pos_);
}
/**
* Seeds and Resets the engine with a 64bit
* unsigned integer by using it as the nonce value.
*/
inline constexpr void seed(const Type::DWord nonce) noexcept
{ seed(Default::Key, nonce); }
private: // Round Helpers
/**
* Rotate 32 bit integer constexpr amount
* Helps with compiler optimization
*/
template<Type::Size Bits>
static inline constexpr Type::Word rotatel(const Type::Word i) noexcept
{
constexpr int Reverse = (sizeof(Type::Word) * 8) - Bits;
return (i << Bits) | (i >> Reverse);
}
/**
* Mix 4 words in quarter round
*/
static inline constexpr void quarter_round(
Type::Word& a, Type::Word& b, Type::Word& c, Type::Word& d) noexcept
{
a += b; d = rotatel<16>(d ^ a); c += d; b = rotatel<12>(b ^ c);
a += b; d = rotatel< 8>(d ^ a); c += d; b = rotatel< 7>(b ^ c);
}
/**
* Mix state by adding a mixed version
* of the state to the origional
*/
inline constexpr void mix_state() noexcept
{
// Copy state from before mixing
Type::State mix = state_;
// Mix state using odd/even rounds
for(Type::Size i = 0; i < Rounds; i += 2)
{
// Odd Round
quarter_round(mix[0], mix[4], mix[ 8], mix[12]);
quarter_round(mix[1], mix[5], mix[ 9], mix[13]);
quarter_round(mix[2], mix[6], mix[10], mix[14]);
quarter_round(mix[3], mix[7], mix[11], mix[15]);
// Even Round
quarter_round(mix[0], mix[5], mix[10], mix[15]);
quarter_round(mix[1], mix[6], mix[11], mix[12]);
quarter_round(mix[2], mix[7], mix[ 8], mix[13]);
quarter_round(mix[3], mix[4], mix[ 9], mix[14]);
}
// You have to add the mixed state to the original,
// or else it would be easy to reverse the mixing
for(Type::Size i = 0; i < mix.size(); ++i)
{ state_[i] += mix[i]; }
}
/**
* Iterate state of engine specified number of rounds
*/
inline constexpr void iterate(void) noexcept
{
// Put Position Data In State
set_state_pos(pos_++);
// Mix state
mix_state();
}
public: // Standard Random Interface (Information)
/**
* Type that random engine will return
*/
using result_type = Type::Word;
/**
* Returns the smallest value the engine will return.
*/
static constexpr result_type min(void)
{ return std::numeric_limits<result_type>::min(); }
/**
* Returns the largest value the engine will return.
*/
static constexpr result_type max(void)
{ return std::numeric_limits<result_type>::max(); }
public: // Standard Random Interface (Generation)
/**
* Gets the next result from the rng engine.
* Will iterate the state every 8 rounds.
* Does not return parts of state where key is given.
*/
inline constexpr result_type get(void) noexcept {
switch((ctr_++) & 0x7) {
case 0x0: iterate();
return state_[0x0]; break;
case 0x1: return state_[0x1]; break;
case 0x2: return state_[0x2]; break;
case 0x3: return state_[0x3]; break;
case 0x4: return state_[0xC]; break;
case 0x5: return state_[0xD]; break;
case 0x6: return state_[0xE]; break;
case 0x7: return state_[0xF]; break;
default: return -1; break;
}
}
/**
* Calls get() and returns value
*/
inline constexpr result_type operator()(void) noexcept
{ return get(); }
/**
* Skip ahead a specified amount ahead in the engine.
* Equivilant to calling operator(), but faster.
*/
inline constexpr void discard(Type::Size delta = 1) noexcept
{
do { get(); }
while(--delta);
}
public: // Get Information about RNG
/**
* Returns a constant referance to an std::array of
* 32 bit integers of size 16.
*/
inline constexpr const auto& state(void) const noexcept { return state_; }
/**
* Returns nonce used by engine
*
* This will never change. This lets
* you get random nonce value after
* initialization
*/
inline constexpr auto nonce(void) const noexcept { return nonce_; }
/**
* Returns the position of the engine.
*
* The position increments everytime
* the state iterates. Usually 1/8th
* as much as the counter variable.
*/
inline constexpr auto position(void) const noexcept { return pos_; }
/**
* Returns counter variable of state.
*
* The counter increments everytime
* get() or operator() is called.
*/
inline constexpr auto counter(void) const noexcept { return ctr_; }
};
// All of these settings pass the die hard test
using ChaCha4 = ChaCha<4>; // Recommended for general purpose RNG
using ChaCha12 = ChaCha<12>; // Recommended Setting for Cyptography
using ChaCha20 = ChaCha<20>; // Default Setting for ChaCha
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment