RandomNumberGenerator
Documentation |
#include <cryptopp/cryptlib.h>
|
Random numbers are a primitive for cryptographic operations. They are used frequently, from generating asymmetric and symmetric keys, to initialization vectors, salts and nonces. The library abstracts them with the RandomNumberGenerator
base class and its derivatives. Some of the generators are cryptographically secure, while others are not.
RandomNumberGenerator
is intended to set up the interface, and you should not instantiate one. Trying to generate random numbers with RandomNumberGenerator
will result in infinite stack recursion. It is OK to use a RandomNumberGenerator
pointer or reference since polymorphism will ensure the derived object's implementation is used.
In general, use an auto-seeded generator like AutoSeededRandomPool
. AutoSeeded*
generators automatically seed the generator using the underlying OS's entropy pools. Entropy is retrieved using Crypto++'s OS_GenerateRandomBlock
. On Linux, OS_GenerateRandomBlock
uses /dev/random
(blocking=true
) or /dev/urandom
(blocking=false
); on Windows, it uses CryptGenRandom
, and on the BSDs, it uses /dev/srandom
(blocking=true
) or /dev/urandom
(blocking=false
).
In addition to automatice seeds, you should seed the generator with any entropy you can get your hands on, even less than perfect ones. Entropy can include anything specific to the use, including any entropy a peer offers like a nonce used during key exchange. Using the peer's entropy before extracting your random bits will help mitigate some classes of attacks, like Virtual Machine playback attacks.
If you are using a generator in a multithreaded program, then use a single generator per thread or provide an external lock for a single generator. Wei Dai recommends using a generator on a per thread basis. Additionally, see WORKAROUND_MS_BUG_Q258000.
You should reseed the generator after a fork()
to avoid multiple generators with the same internal state.
DefaultAutoSeededRNG
The library provides a typedef for DefaultAutoSeededRNG
. In the non-FIPS DLL builds DefaultAutoSeededRNG
is AutoSeededRandomPool
. In the former FIPS DLL builds the library used AutoSeededX917RNG
as the typedef. Both generators use OS_GenerateRandomBlock to gather seed material, so neither generator suffered the DUHK attacks.
OS Entropy
You can use OS_GenerateRandomBlock
to gather entropy using whatever the underlying operating system provides. OS_GenerateRandomBlock
is a global function, and not tied to any class.
On Linux, OS_GenerateRandomBlock
uses /dev/random
(blocking=true
) or /dev/urandom
(blocking=false
); on Windows, it uses CryptGenRandom
; and on the BSDs, it uses /dev/srandom
(blocking=true
) or /dev/urandom
(blocking=false
).
According to Theodore Ts'o on the Linux Kernel Crypto mailing list, Linux's /dev/random
has been deprecated for a decade. From RFC PATCH v12 3/4: Linux Random Number Generator:
Practically no one uses /dev/random. It's essentially a deprecated interface; the primary interfaces that have been recommended for well over a decade is /dev/urandom, and now, getrandom(2).
OS_GenerateRandomBlock
Documentation |
#include <cryptopp/osrng.h>
|
OS_GenerateRandomBlock
is used to gather entropy using the OS and its signature is shown below:
void OS_GenerateRandomBlock(bool blocking, byte *output, size_t size)
Once you gather entropy with OS_GenerateRandomBlock
, you can use it directly or use it to seed a generator. Below, the entropy is used directly for a key and initialization vector. The key draws from /dev/random
, while the iv draws from /dev/urandom
on Linux.
SecByteBlock key(AES::DEFAULT_KEYLENGTH), iv(AES::BLOCKSIZE); string k,v; OS_GenerateRandomBlock(true, key, key.size()); OS_GenerateRandomBlock(false, iv, iv.size()); HexEncoder hex(new StringSink(k)); hex.Put(key, key.size()); hex.MessageEnd(); hex.Detach(new StringSink(v)); hex.Put(iv, iv.size()); hex.MessageEnd(); cout << "Key: " << k << endl; cout << "IV: " << v << endl;
The program will produce an output similar to below.
$ ./cryptopp-test.exe Key: 774353D982E1A1FD4334C5D5D3AF9314 IV: 503E428E63E999B3D7E6CCDE0143197A
Seeding
Nearly all generators should be seeded before use. To test if a generator can incorporate a seed, call CanIncorporateEntropy
. CanIncorporateEntropy
will return true
if the generator can incorporate a seed. Some generators, like Intel's deterministic random-bit generator (accessed via RDRAND
) cannot accept entropy.
To seed or reseed a generator that accepts a seed, call IncorporateEntropy
to add the entropy to the generator.
If you are using an AutoSeeded*
generator, then the library will attempt to seed the generator for you using the underlying OS's entropy pool by way of OS_GenerateRandomBlock
. You can call still seed an auto-seeded generator and add more entropy if you have it.
You should reseed the generator after a fork()
to avoid multiple generators with the same internal state.
IncorporateEntropy
To seed one of the Crypto++ random number generators, call the IncorporateEntropy
function. It takes a pointer to a byte block and a length:
void IncorporateEntropy (const byte *input, size_t length)
A sample using IncorporateEntropy
is shown below.
RandomPool prng; SecByteBlock seed(32); OS_GenerateRandomBlock(false, seed, seed.size()); prng.IncorporateEntropy(seed, seed.size()); string s; HexEncoder hex(new StringSink(s)); hex.Put(seed, seed.size()); hex.MessageEnd(); cout << "Seed: " << s << endl;
The program will produce an output similar to below.
$ ./cryptopp-test.exe Seed: 6BA0DC9C85A7133287A70A4C14BCA3B150025B3F621C8930B08A91F304245067
RandomNumberSink
Documentation |
#include <cryptopp/filters.h>
|
A RandomNumberSink
allows you to add entropy to a generator. Internally, it calls IncorporateEntropy
for you. Note: this is one of the times pumpAll = false
is used for a Source
.
const unsigned int BLOCKSIZE = 16 * 8; SecByteBlock scratch( BLOCKSIZE ); RandomPool prng; FileSource entropy("/dev/urandom", false, new RandomNumberSink(prng)); // Add 16 bytes of entropy before generating a block of random bits entropy.Pump(16); prng.GenerateBlock( scratch, scratch.size() ); // Add 16 bytes of entropy before generating a block of random bits entropy.Pump(16); prng.GenerateBlock( scratch, scratch.size() ); ...
Generation
This section details how to generate random numbers using the different generators. In general, you should seed a generator immediately before using it to generate bits. You should do so before each call, and not just once. Doing so helps avoid virtual machine playback attacks.
There are generally two ways to get a random number from a generator. First is with GenerateBlock
, and second is with GenerateIntoBufferedTransformation
. GenerateBlock
takes a pointer to a buffer and a length. Internally, GenerateBlock
wraps the buffer in an ArraySource
and then calls GenerateIntoBufferedTransformation
. The second method is GenerateIntoBufferedTransformation
and its where the real work is performed. When generating into the BufferedTransformation
, the generator produces the stream and places it in the specified channel.
LC_RNG
Documentation |
#include <cryptopp/rng.h>
|
LC_RNG is a Linear Congruential Generator. Though this generator has no cryptographic value, it does allow one to reproduce results when debugging a program. Additionally, it is generally faster at generating a byte block (or stream). If one seeds the LCG with 0x00, a steady stream of 0x80 is the result. Other seeds perform as expected.
If you want to use the original constants as specified in S.K. Park and K.W. Miller's CACM paper, then you should #define LCRNG_ORIGINAL_NUMBERS
before compiling the Crypto++ library. The define is available in config.h
.
Current RandomPool
Documentation |
#include <cryptopp/randpool.h>
|
RandomPool
is a PGP style random pool. Crypto++ 5.5 and later versions of RandomPool use AES and are hardened against VM rollback attacks. Crypto++ 5.4 and early followed PGP 2.6.x and used MDC<SHA>
via typedef MDC<SHA> RandomPoolCipher
. From the current randpoool.cpp
:
RandomPool::RandomPool() : m_pCipher(new AES::Encryption), m_keySet(false) { memset(m_key, 0, m_key.SizeInBytes()); memset(m_seed, 0, m_seed.SizeInBytes()); }
RandomPool
uses time, so each run of the generator will produce different results. But the difference between runs is weak (it only differs by the time of the call), so be sure to seed the generator with unpredictable data.
Using the generator is similar to the following:
// Must be at least 16 for RandomPool const unsigned int SEEDSIZE = 16; SecByteBlock seed( SEEDSIZE ); // Scratch Area const unsigned int BLOCKSIZE = 16 * 8; SecByteBlock scratch( BLOCKSIZE ); ... // Random Pool Initalization CryptoPP::RandomPool rng( SEEDSIZE ); rng.IncorporateEntropy( seed, seed.size() ); rng.GenerateBlock( scratch, scratch.size() );
Old RandomPool
If you need the old RandomPool
generator which uses MDC<SHA>
then you can find it at OldRandomPool
. The OldRandomPool
class was added at Crypto++ 6.0 to help provide an upgrade path. For Crypto++ 5.6.5 and earlier, you must apply the 6.0 change yourself. The check-ins of interest are Commit 02e3a794443a, Add OldRandomPool class (Issue 452) and Commit 5fbbc5311cea, Add self tests for OldRandomPool (Issue 452). The issue was tracked at Issue 452, Add OldRandomPool for pre-Crypto++ 5.5 compatibility.
There's now a wiki page about it at Old RandomPool.
AutoSeededRandomPool
Documentation |
#include <cryptopp/osrng.h>
|
Unlike LC_RNG and RandomPool, AutoSeeded generators do not require a seed. An auto seeded random pool was suggested by Leonard Janke, which Wei later incorporated into Crypto++ with version [?].
// Scratch Area const unsigned int BLOCKSIZE = 16 * 8; SecByteBlock scratch( BLOCKSIZE ); // Construction CryptoPP::AutoSeededRandomPool rng; // Random Block rng.GenerateBlock( scratch, scratch.size() );
AutoSeededX917RNG
Documentation |
#include <cryptopp/osrng.h>
|
When using an X9.17 generator, you must specify an approved Block Cipher as a template parameter. If you use TripleDES (DES_EDE3
), then its an X9.17 generator. If you use AES (AES
), then its an X9.31 generator (the underlying algorithm did not change).
// Scratch Area const unsigned int BLOCKSIZE = 16 * 8; SecByteBlock scratch( BLOCKSIZE ); // Construction CryptoPP::AutoSeededX917RNG<CryptoPP::DES_EDE3> rng; // Random Block rng.GenerateBlock( scratch, scratch.size() );
RDRAND
Documentation |
#include <cryptopp/rdrand.h>
|
The library provides the RDRAND
generator. The following demonstrates using the generator.
SecByteBlock key(AES::DEFAULT_KEYLENGTH), iv(AES::BLOCKSIZE); string k,v; RDRAND prng; if (prng.Available()) { prng.GenerateBlock(key, key.size()); prng.GenerateBlock(iv, iv.size()); HexEncoder hex(new StringSink(k)); hex.Put(key, key.size()); hex.MessageEnd(); hex.Detach(new StringSink(v)); hex.Put(iv, iv.size()); hex.MessageEnd(); cout << "Key: " << k << endl; cout << "IV: " << v << endl; } else { cout << "Failed to generate Key and IV" << endl; }
If you call GenerateBlock
on a machine without RDRAND
circuit, then a RDRAND_Err
exception will be thrown.
NIST DRBG
Documentation |
#include <cryptopp/drbg.h>
|
The library provides two NIST's Deterministic Random Bit Generators (DRBGs). They are Hash_DRBG
, and HMAC_DRBG
. They are discussed at NIST DRBGs wiki page.
The generators have their own page because they are trickier to use due to randomness requirements during instantiation. In addition, they accept at least three other types of randomness distinct from the entropy required during instantiation.
RandomNumberSource
Documentation |
#include <cryptopp/filters.h>
|
A RandomNumberSource
allows you to use a generator in a pipeline.
const unsigned int BLOCKSIZE = 16 * 8; SecByteBlock scratch( BLOCKSIZE ); AutoSeededRandomPool prng; // Extract BLOCKSIZE bytes of random bits RandomNumberSource(prng, scratch.size(), true, new ArraySink( scratch, scratch.size() ));
Creating a Generator
If you would like to create generator, then derive a class from RandomNumberGenerator
and provide the implementation. You must provide an override for GenerateBlock
. The library's default implementation for GenerateIntoBufferedTransformation
should be sufficient.
By default, the library returns false
for CanIncorporateEntropy
, so be sure to override it as required.
Example Generator
You can find an example of creating a generator at Mersenne Twister. The generator is somewhat tricky to implement because it is word oriented, and not byte oriented.
The Mersenne Twister provides overrides for GenerateBlock
, GenerateWord32
and Discard
. Because the generator is word oriented, there are two implications for an implementation. First, the result of GenerateWord32
must be consistent with the result of calling GenerateBlock
with 1, 2, 3 and 4 byte arrays. For example, if GenerateWord32
returns 0xD091BB5C, then GenerateBlock
must return 0xD0 0x91 0xBB 0x5C
for 1, 2, 3 and 4 byte arrays. Second, Discard
rounds up to a multiple of a word size, and then discards the required number of words (and not bytes).
Reproducibility
If you need a generator to reproduce results between runs, then you have three choices. First is to use LC_RNG
, second is to use OFB_Mode<T>::Encryption
or CTR_Mode<T>::Encryption
, and third is to use AES_RNG
. AES_RNG
is not part of the Crypto++ library, but you can download it below.
OFB_Mode<T>::Encryption
OFB_Mode<T>::Encryption
is used by the Crypto++ library in test.cpp
to generate random numbers (where T
is a block cipher like AES
). The encryptor subscribes to the RandomNumberGenerator
interface by way of AdditiveCipherTemplate<T>
, so it can be used anywhere a Crypto++ generator is required.
Seeding occurs by keying the cipher. Keying the cipher with the same key and IV will produce the same bit stream. In the case of test.cpp
, time is used, so the results can be reproduced using the same time string (the time used is printed to the console during a run of cryptest.exe v
).
Note: other modes, like CBC and CFB, do not inherit from AdditiveCipherTemplate<T>
, so they cannot be used as a random number generator.
An example of using OFB_Mode<T>::Encryption
is shown below. Notice a random seed is fetched from the OS using OS_GenerateRandomBlock
, and then same seed is used to key the cipher in the loop.
SecByteBlock seed(32 + 16); OS_GenerateRandomBlock(false, seed, seed.size()); for(unsigned int i = 0; i < 10; i++) { OFB_Mode<AES>::Encryption prng; prng.SetKeyWithIV(seed, 32, seed + 32, 16); SecByteBlock t(16); prng.GenerateBlock(t, t.size()); string s; HexEncoder hex(new StringSink(s)); hex.Put(t, t.size()); hex.MessageEnd(); cout << "Random: " << s << endl; }
Running the program produces results similar to below.
$ ./cryptopp-test.exe Random: DF3D3F8E8A21C39C0871B375013AA2CD Random: DF3D3F8E8A21C39C0871B375013AA2CD Random: DF3D3F8E8A21C39C0871B375013AA2CD Random: DF3D3F8E8A21C39C0871B375013AA2CD Random: DF3D3F8E8A21C39C0871B375013AA2CD Random: DF3D3F8E8A21C39C0871B375013AA2CD Random: DF3D3F8E8A21C39C0871B375013AA2CD Random: DF3D3F8E8A21C39C0871B375013AA2CD Random: DF3D3F8E8A21C39C0871B375013AA2CD Random: DF3D3F8E8A21C39C0871B375013AA2CD
CTR_Mode<T>::Encryption
In addition to OFB_Mode<T>::Encryption
, CTR_Mode<T>::Encryption
(where T
is a block cipher like AES
) allows you to use the block cipher as a random number generators because CTR mode inherits from AdditiveCipherTemplate<T>
. As with OFB mode, CTR mode seeding occurs by keying the cipher. Keying the cipher with the same key and IV will produce the same bit stream.
The sample code is left as an exercise to the reader, but it does not differ much from the example for OFB_Mode<T>::Encryption
. Just copy and paste and it should work.
AES_RNG
The AES_RNG
generator uses AES-256, and it will be strong enough to meet most needs as long as its used correctly. It also allows you to use an arbitrarily sized seed because it relies upon SHA-512 to expand then extract entropy that is used to key the underlying cipher.
If you supply a seed, then the generator will always produce the same sequence because it forgoes calls to time
when generating a sequence. Repeating a sequence would usually be considered "using the generator incorrectly". If you don't provide a seed to the constructor, then the generator will use OS_GenerateRandomBlock
and each run will produce different results. This is usually considered "using the generator correctly".
An example of using AES_RNG
is shown below. Notice a random seed is fetched from the OS using OS_GenerateRandomBlock
, and then same seed is used in the AES_RNG
constructor within the loop.
// Example of using a seed for reproducible results. SecByteBlock seed(32); OS_GenerateRandomBlock(false, seed, seed.size()); for(unsigned int i = 0; i < 10; i++) { AES_RNG prng(seed, seed.size()); SecByteBlock t(16); prng.GenerateBlock(t, t.size()); string s; HexEncoder hex(new StringSink(s)); hex.Put(t, t.size()); hex.MessageEnd(); cout << "Random: " << s << endl; }
Running the program produces results similar to below.
$ ./cryptopp-test.exe Random: B40DEFDE59036914A513711803B52F3C Random: B40DEFDE59036914A513711803B52F3C Random: B40DEFDE59036914A513711803B52F3C Random: B40DEFDE59036914A513711803B52F3C Random: B40DEFDE59036914A513711803B52F3C Random: B40DEFDE59036914A513711803B52F3C Random: B40DEFDE59036914A513711803B52F3C Random: B40DEFDE59036914A513711803B52F3C Random: B40DEFDE59036914A513711803B52F3C Random: B40DEFDE59036914A513711803B52F3C
Test Suite and GlobalRNG
The test and validation suites use GlobalRNG
declared in validate.h
and defined in test.cpp
. GlobalRNG
is simply a function:
RandomNumberGenerator & GlobalRNG() { static OFB_Mode<AES>::Encryption s_globalRNG; return dynamic_cast<RandomNumberGenerator&>(s_globalRNG); }
Before using GlobalRNG
, the test suite seeds the generator like so:
// Pad the seed with space to make it easier to reproduce results std::string seed = IntToString(time(NULL)); seed.resize(16, ' '); OFB_Mode<AES>::Encryption& prng = dynamic_cast<OFB_Mode<AES>::Encryption&>(GlobalRNG()); prng.SetKeyWithIV((byte *)seed.data(), 16, (byte *)seed.data());
You should not use the test suite's GlobalRNG
because you will have undefined symbol errors during link since your project does not include the test.cpp
source file from the test suite.
Alternate Generators
If you need a generator similar in form and function to GlobalRNG
, then use an AutoSeededRandomPool
. Its one of the easiest generators to use safely.
You can also copy/paste the code above into your project. Be aware of the pitfalls in making s_globalRNG
static, especially if its being used in other compilation units. If you want to avoid the C++ static initialization problems, then don't use the generator across translation units. Instead, create a local RNG in a function when its needed. Also see Static Initialization Order Fiasco on the wiki.
Windows Phone 8 and Windows Store 8
Crypto++ is multi-platform, and the platforms include traditional Windows desktops and servers. Crypto++ 5.6.4 increased support for Windows Phone, Windows Store and Universal Windows Platform (UWP). Improved support includes better platform integration and specialized ARM implementations. Also see Issue 143: Support for Universal Windows Platform (UWP) and Issue 164: Need NonblockingRng based on BCryptGenRandom for Windows on the GitHub bug tracker.
Random numbers can be a problem on Windows Phone 8 and Windows Store 8 because Microsoft does not provide a way for the library to obtain random numbers for its AutoSeeded generators. The coverage of the WinCrypt API and CryptoNG API simply has a big hole at Windows Phone 8 and Windows Store 8.
When compiling osrng.cpp
you may see the following warning:
cl.exe /nologo /W4 /D_MBCS /Zi /TP /EHsc /MD /FI sdkddkver.h /FI winapifamily.h /DWINAPI_FAMILY=WINAPI_FAMILY_PHONE_APP /c osrng.cpp osrng.cpp WARNING: Compiling for Windows but an OS RNG is not available. This is likely a Windows Phone 8 or Windows Store 8 app.
Remediations
There are a few ways to approach the Windows Phone 8 and Windows Store 8 gaps. First, you can abandon the platform. This appears to be the strategy used by Microsoft.
Second, you can call the managed CryptographicBuffer.GenerateRandom
method for random numbers. You can also instantiate a non-AutoSeeded generator and seed it from CryptographicBuffer.GenerateRandom
.
Third, you can set WINVER
or _WIN32_WINNT
to 0x0A00
. 0x0A00
is Windows 10, and it signals Windows Phone 10, Windows Store 10 and Windows Universal Platform. Microsoft provides Bcrypt for this platform, so the library can obtain random numbers without the need for the managed CryptographicBuffer.GenerateRandom
.
Fourth, you can sample sensor data and use the sampled data as the seed to a non-AutoSeeded generator. If you select this option, then be sure to extract entropy with a function like HKDF. Also be aware that sensors vary among devices - some devices are sensor rich, and other devices are sensor anemic. Anemic devices usually have one sensor and its an accelerometer for gaming. Examples of using this technique are available for Android and iOS, but not Windows Phone and Windows Store. Also see Android Activity on the Crypto++ wiki.
Sample Programs
LCG.zip - Demonstrates using the Linear Congruential PRNG to generate pseudo random bytes
RandomPool.zip - Demonstrates using a RandomPool to generate pseudo random bytes
AutoSeededX917.zip - Demonstrates using a AutoSeededX917RNG to generate pseudo random bytes
ASRP.zip - Demonstrates using an AutoSeededRandomPool to generate pseudo random bytes
AES_RNG.zip - AES-256 based random number generator that produces the same bit stream given the same seed is used in the constructor.
mersenne.zip - patch to provide Mersenne Twister implementation for Crypto++.