Search code examples
c++randomcrypto++

How to regenerate values in AutoSeededX917RNG


I need to regenerate the pseuorandom values when I need them. My code is:

static const unsigned int BLOCKSIZE = 5; 
byte scratch[ BLOCKSIZE ];

CryptoPP::AutoSeededX917RNG<CryptoPP::AES> rng;
rng.GenerateBlock(scratch,BLOCKSIZE);

std::cout << "The generated random block is:" << std::endl;
for( unsigned int i = 0; i < BLOCKSIZE; i++ )
{
    std::cout << std::setw(2) << std::setfill('0');
    std::cout << static_cast<unsigned int>( scratch[ i ] );
}
std::cout << std::endl;

Solution

  • How to regenerate values in AutoSeededX917RNG

    You can't because you are using an AutoSeeded* generator.

    -----

    I need to regenerate the pseuorandom values when I need them...

    In that case, I think you only have two choices - an LC_RNG (which is insecure) or an RandomPool (more secure, but it has gaps). Both would require you to operate the generator with the same seed to produce the same bit streams.

    RandomPool is a PGP style generator and its underlying algorithm is MDC<SHA>. It will produce the same stream of bits given the same seed. It uses time, so it produces a different stream for each run (even with the same seeds).

    Do not use an AutoSeeded* generator, like AutoSeededRandomPool or AutoSeededX917RNG. The AutoSeeded* read from the OS's entropy pool and then seed the generators with the bits it read.

    Also see RandomNumberGenerator on the Crypto++ wiki.

    -----

    UPDATE: RandomPool uses time (sorry, I should have checked before I recommended it).

    You can use OFB_Mode<T>::Encryption to generate reproducible random streams. The Crypto++ test program uses it (see test.cpp - its the generator returned from GlobalRNG()). An example is shown below.

    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;
    }
    

    OFB_mode<T>::Encryption can be used as a generator because OFB mode uses AdditiveCipherTemplate<T>, which derives from RandomNumberGenerator.

    Running it produces 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
    

    -----

    You can also use the code below in AES_RNG.h for a deterministic generator based on AES-256. It will produce the same bit stream if presented with the same seed. Its a little more flexible than OFB_Mode<T>::Encryption because it can take a arbitrarily-sized seed.

    The code to test it is as follows:

    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 << endl;
    }
    

    Its output will look similar to below. Each run of the program will be different because each run uses a different seed (by way of OS_GenerateRandomBlock):

    $ ./cryptopp-test.exe 
    Random: D9B48CB7D37C88BDF2A0B0022AB1A812
    Random: D9B48CB7D37C88BDF2A0B0022AB1A812
    Random: D9B48CB7D37C88BDF2A0B0022AB1A812
    Random: D9B48CB7D37C88BDF2A0B0022AB1A812
    Random: D9B48CB7D37C88BDF2A0B0022AB1A812
    Random: D9B48CB7D37C88BDF2A0B0022AB1A812
    Random: D9B48CB7D37C88BDF2A0B0022AB1A812
    Random: D9B48CB7D37C88BDF2A0B0022AB1A812
    Random: D9B48CB7D37C88BDF2A0B0022AB1A812
    Random: D9B48CB7D37C88BDF2A0B0022AB1A812
    

    AES_RNG.h:

    #include <cryptopp/cryptlib.h>
    using CryptoPP::NotCopyable;
    using CryptoPP::BufferedTransformation;
    using CryptoPP::BlockCipher;
    
    #include <cryptopp/secblock.h>
    using CryptoPP::AlignedSecByteBlock;
    using CryptoPP::FixedSizeSecBlock;
    
    #include <cryptopp/smartptr.h>
    using CryptoPP::member_ptr;
    
    #include <cryptopp/osrng.h>
    using CryptoPP::OS_GenerateRandomBlock;
    using CryptoPP::RandomNumberGenerator;
    
    #include <cryptopp/aes.h>
    using CryptoPP::AES;
    
    #include <cryptopp/sha.h>
    using CryptoPP::SHA512;
    
    class AES_RNG : public RandomNumberGenerator, public NotCopyable
    {
    public:
        explicit AES_RNG(const byte *seed = NULL, size_t length = 0)
        : m_pCipher(new AES::Encryption), m_keyed(SeedHelper(seed, length))
        {
        }
    
        bool CanIncorporateEntropy() const
        {
            return true;
        }
    
        void IncorporateEntropy(const byte *input, size_t length)
        {
            m_keyed = SeedHelper(input, length, false);
        }
    
        void GenerateIntoBufferedTransformation(BufferedTransformation &target, const std::string &channel, lword size)
        {
            if (!m_keyed) {
                m_pCipher->SetKey(m_key, m_key.size());
                m_keyed = true;
            }
    
            while (size > 0)
            {
                m_pCipher->ProcessBlock(m_seed);
                size_t len = std::min((size_t)16, (size_t)size);
                target.ChannelPut(channel, m_seed, len);
                size -= len;
            }
        }
    
    protected:
        // Sets up to use the cipher. Its a helper to allow a throw
        //   in the contructor during initialization.  Returns true
        //   if the cipher was keyed, and false if it was not.
        bool SeedHelper(const byte* input, size_t length, bool ctor = true)
        {
            // 32-byte key, 16-byte seed
            AlignedSecByteBlock seed(32 + 16);
            SHA512 hash;
    
            if(ctor)
            {
                memset(m_key, 0x00, m_key.size());
                memset(m_seed, 0x00, m_seed.size());
            }
    
            if(input && length)
            {
                // Use the user supplied seed.
                hash.Update(input, length);
            }
            else
            {
                // No seed or size. Use the OS to gather entropy.
                OS_GenerateRandomBlock(false, seed, seed.size());
                hash.Update(seed, seed.size());
            }
    
            hash.Update(m_key.data(), m_key.size());
            hash.TruncatedFinal(seed.data(), seed.size());
    
            memcpy(m_key.data(), seed.data() + 0, 32);
            memcpy(m_seed.data(), seed.data() + 32, 16);
    
            // Return false. This allows the constructor to complete
            //   before the pointer m_pCipher is used.
            return false;
        }
    
    private: 
        FixedSizeSecBlock<byte, 32> m_key;
        FixedSizeSecBlock<byte, 16> m_seed;    
        member_ptr<BlockCipher> m_pCipher;
        bool m_keyed;
    };