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_DRBG and HMAC_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:

  1. 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.
  2. Security: Depending on the application, ensure the RNG implementation is secure enough. Use reputable sources for research and avoid taking advice from unreliable sources.
  3. 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