Random Number Generators in Crypto++
Random number generators, or RNGs, are a fundamental part of many cryptographic algorithms, so it's no surprise that Crypto++ provides several RNG implementations. The index of the documentation lists the following algorithms:
- NullRNG
- LC_RNG
- RandomPool
- BlockingRng
- NonblockingRng
- AutoSeededRandomPool
- AutoSeededX917RNG
- 
NIST Hash_DRBGandHMAC_DRBG
- 
MersenneTwister(MT19937 and MT19937-AR)
- DARN
- RDRAND
- RDSEED
They all implement the RandomNumberGenerator class. Similar to hash implementations RNGs can be
used on their own or as part of a pipeline.
Crypto++ provides various RNGs, including both software and hardware-based implementations. Before
using an RNG, ensure it is compatible with your system. For instance, consider PadlockRNG, which
is almost guaranteed to be unsupported on any modern machine. This RNG relies on the VIA PadLock
instruction set, available only on certain old VIA CPUs (more details on
Wikipedia). Running the following code on a modern
system will result in an exception:
#include <cryptopp/cryptlib.h> #include <cryptopp/padlkrng.h> int main() { auto rng = CryptoPP::PadlockRNG(); return 0; }
Resulting exception:
terminate called after throwing an instance of 'CryptoPP::PadlockRNG_Err' what(): PadlockRNG: PadlockRNG generator not available Aborted (core dumped)
This is expected since hardware RNGs require compatible hardware.
Another hardware RNG, RDRAND, generates random data using the CPU's onboard hardware RNG. This
instruction is available on modern Intel processors (after 2012) and AMD processors (after 2015)
(details on Wikipedia). When using such RNGs, consider
portability. Programs relying on RDRAND will work on modern x86 machines but throw exceptions on
unsupported architectures.
Generating random values
Let's explore a portable RNG, AutoSeededRandomPool which uses a random generator from the
operating system (e.g. /dev/urandom on Linux):
#include <iostream> #include <cryptopp/cryptlib.h> #include <cryptopp/osrng.h> int main() { auto rng = CryptoPP::AutoSeededRandomPool(); uint8_t random_byte = rng.GenerateByte(); std::cout << "Random byte: " << int{random_byte} << std::endl; uint32_t random_word = rng.GenerateWord32(); std::cout << "Random word: " << random_word << std::endl; uint32_t random_bounded = rng.GenerateWord32(0, 100); std::cout << "Random number between 0 and 100: " << random_bounded << std::endl; uint64_t random_bigger_int = 0; rng.GenerateBlock(reinterpret_cast<uint8_t *>(&random_bigger_int), sizeof(random_bigger_int)); std::cout << "Random 64 bit int: " << random_bigger_int << std::endl; return 0; }
We initialise the RNG and call GenerateByte to get a random one byte value. Note that we convert
the result to int in order to have something meaningful printed on the screen. However the value
is not a real int because the RNG generates only one byte.
To generate a proper integer GenerateWord32 can be used. It returns a 32 bit value which can be
used go generate an uint32_t. The function also accepts bound in case you want to have the values
in a specific range.
If you need to generate a bigger value you can use GenerateBlock. Its input is pointer to a buffer
and its size and the RNG fills the buffer with random data. We can use this to generate a uint64_t
value for example.
Reshuffling STL containers
Another useful application of RNGs is shuffling STL containers. Here's an example:
#include <iostream> #include <vector> #include <cryptopp/cryptlib.h> #include <cryptopp/osrng.h> int main() { auto rng = CryptoPP::AutoSeededRandomPool(); std::vector<uint32_t> data = {0, 1, 2, 3, 4, 5}; rng.Shuffle(data.begin(), data.end()); for (uint32_t &d : data) { std::cout << d << " "; } std::cout << std::endl; return 0; }
We create an RNG instance and a vector holding the numbers from 0 to 5. We can randomise the vector
with Shuffle. Its input parameters are iterators so it can work with any STL container. After
running the sample you should see that the vector is randomised.
Using RNG with a Crypto++ pipeline
RNGs can work with a Crypto++ pipeline in two ways:
Using GenerateIntoBufferedTransformation
The GenerateIntoBufferedTransformation method generates random data and sends it to a filter. The
filter is passed by reference, so the RNG does not take ownership. Here is an example:
#include <cryptopp/hex.h> #include <iostream> #include <cryptopp/cryptlib.h> #include <cryptopp/osrng.h> int main() { auto rng = CryptoPP::AutoSeededRandomPool(); std::string encoded; auto hex_filter = CryptoPP::HexEncoder(new CryptoPP::StringSink(encoded), true /*upper_case*/, 2 /* group_size */); rng.GenerateIntoBufferedTransformation(hex_filter, CryptoPP::DEFAULT_CHANNEL, 128); std::cout << encoded << std::endl; return 0; }
We have and AutoSeededRandomPool and HexEncoder instances. Then we call
GenerateIntoBufferedTransformation and pass a reference to the HexEncoder. Finally we print the
output on the screen.
Using RandomNumberSource
RandomNumberSource integrates RNGs into a pipeline as a typical source.
#include <cryptopp/filters.h> #include <cryptopp/hex.h> #include <iostream> #include <cryptopp/cryptlib.h> #include <cryptopp/osrng.h> int main() { auto rng = CryptoPP::AutoSeededRandomPool(); std::string encoded; (void)CryptoPP::RandomNumberSource( rng, 128 /* length */, true /* pumpAll */, new CryptoPP::HexEncoder(new CryptoPP::StringSink(encoded), true /* upper_case */, 2 /* group_size */)); std::cout << encoded << std::endl; return 0; }
RandomNumberSource gets a reference to RandomNumberGenerator, so we are initialising the RNG in
advance. The second parameter is an int indicating how much data to generate. pumpAll is a
typical parameter for a Source. If you don't know what it does you can refer to chapter 3 which
discuses it in detail. The final parameter is a pointer to a BufferedTransformation which is the
next element from the pipeline. In our case this is a HexEncoder, saving the result to a
StringSink.
Conclusion
Choosing the right RNG is an important aspect for any cryptographic application. Crypto++ provides many RNG options, which can make the decision challenging. Since I am not a cryptographer I can't give an actionable advise but at least consider the following when selecting an RNG:
- Application Requirements: Your application's purpose will determine the RNG choice. For example, simulating a dice throw in a betting app is different from picking a car color in a game.
- Security: Depending on the application, ensure the RNG implementation is secure enough. Use reputable sources for research and avoid taking advice from unreliable sources.
- Portability: Not all RNGs work everywhere. If you control the target platform, you can choose any suitable and secure RNG. Otherwise, ensure the RNG is portable across all target environments.
The book
 
    This post is an excerpt from my book "Crypto++ in Practice: A Concise Guide". It is a guide explaining key Crypto++ concepts and demonstrating how to use the algorithms in the library.
Interested? You can buy it here.
Comments
Comments powered by Disqus