6

I need to get a small number of "cryptographically good" random bytes. (8 bytes in my case.) Are there any Windows APIs for that?

PS. It'd be nice if those APIs were backward compatible with Windows XP. But if not, it'd still work. Thanks.

ahmd0
  • 16,633
  • 33
  • 137
  • 233
  • 2
    [`CryptGenRandom`](http://msdn.microsoft.com/en-us/library/windows/desktop/aa379942(v=vs.85).aspx) or [`RtlGenRandom`](http://msdn.microsoft.com/en-us/library/windows/desktop/aa387694(v=vs.85).aspx), where the latter is much simpler to use. – Niklas B. Jan 29 '14 at 01:15
  • @NiklasB.: Thanks. Although I'm not kinda leery about this statement: `[The RtlGenRandom function ... may be altered or unavailable in subsequent versions. Instead, use the CryptGenRandom function.]` – ahmd0 Jan 29 '14 at 01:39
  • In that case you probably want to use `CryptGenRandom`. I don't see why they would ever remove a function, though, since it wouldn't really buy them anything but might upset some people that use it against the warnings :) – Niklas B. Jan 29 '14 at 01:41
  • More importantely, `RtlGenRandom` generates just a pseudo-random number, which although part of the Cryptography API, doesn't really shout "cryptographically strong". – icabod Jan 29 '14 at 11:41
  • @NiklasB.: Agreed, although who would've thought that they'd remove the Start button... So you never know. – ahmd0 Jan 29 '14 at 18:55
  • @icabod: `CryptGenRandom` actually uses `RtlGenRandom` internally... But yeah, that's just the way it is, it's not guaranteed to be this way in future versions. – Niklas B. Jan 29 '14 at 18:57
  • @NiklasB.: How do you know that `CryptGenRandom` uses `RtlGenRandom` internally? – ahmd0 Jan 29 '14 at 19:01
  • 1
    @ahmd0: Just look at the disassembly. Or believe the following blog post: http://blogs.msdn.com/b/michael_howard/archive/2005/01/14/353379.aspx It only applies to XP and vista due to its age, probably – Niklas B. Jan 29 '14 at 19:02
  • @NiklasB.: Hmm. Interesting. Yes, disassembly would be the way to do it. Although I'd think that the APIs that take more detailed parameters (i.e. `CryptAcquireContext`, `CryptReleaseContext`, `CryptGenRandom`) would be nested within APIs with less parameters, i.e. `RtlGenRandom`. – ahmd0 Jan 29 '14 at 19:08
  • Personally I think that it just calls out to `RtlGenRandom` and the whole crypto context is totally irrelevant for this particular function. – Niklas B. Jan 29 '14 at 19:09
  • @NiklasB.: Very interesting, thanks for the link to Michael Howards blog. I was going just on the description rather than internals, which as Raymond Chen always likes to point out, is liable to change. – icabod Jan 30 '14 at 10:05

4 Answers4

14

I know that I originally asked about the Windows API, but since my original post I had some time to do the research. So I want to share my findings.

It turns out that since their Ivy Bridge chipset, Intel included a pretty cool hardware random number generator available via the RDRAND CPU instruction.

Since this is the question about Windows implementation and most of the Windows PCs run on the Intel chipsets, I decided to code a small class that (I can't believe that I'm saying it) seems to be generating true random numbers. Here's the description of how it works, and here's the analysis of the Intel's RNG.

I'm also assuming that this code is compiled for a 32-bit process (in case someone needs it for a 64-bit implementation, you'll have to adjust the asm parts.) It is also prudent to say that one should not assume that it will run on any Intel hardware. As I said above, it requires a relatively recent Intel's Ivy Bridge, or later chipset to run. (I tested it on the later Haswell system board.) The good news is that it takes almost no time to find out if the RDRAND instruction is supported, and if not, your most obvious route should be to use any of the OS provided APIs, described in other posts. (Also combining the results from both methods could also increase the entropy of your final result.)

So here's how I call the method to generate random numbers:

CHardwareRandomNumberGenerator h;
BYTE arr[4096] = {0};
UINT ncbSz = sizeof(arr);
int r = h.GetHardwareRandomBytes(arr, &ncbSz);
if(ncbSz != sizeof(arr))   //We'll need only the full array
{
    //Use an alternate RNG method:
    //- RtlGenRandom()
    //or
    //- CryptGenRandom()
}

_tprintf(L"RdRand result is %d\n", r);
if(ncbSz > 0)
{
    _tprintf(L"Random Bytes (%d): ", ncbSz);

    for(UINT i = 0; i < ncbSz; i++)
    {
        _tprintf(L"%02x", arr[i]);
    }

    _tprintf(L"\n");
}

This is the header file:

//This class uses the Intel RdRand CPU instruction for 
//the random number generator that is compliant with security 
//and cryptographic standards:
//
//  http://en.wikipedia.org/wiki/RdRand
//
#pragma once

class CHardwareRandomNumberGenerator
{
public:
    CHardwareRandomNumberGenerator(void);
    ~CHardwareRandomNumberGenerator(void);
    int GetHardwareRandomBytes(BYTE* pOutRndVals = NULL, UINT* pncbInOutSzRndVals = NULL, DWORD dwmsMaxWait = 5 * 1000);
private:
    BOOL bRdRandSupported;
    static BOOL __is_cpuid_supported(void);
    static BOOL __cpuid(int data[4], int nID);
    int __fillHardwareRandomBytes(BYTE* pOutRndVals, UINT* pncbInOutSzRndVals, UINT& ncbOutSzWritten, DWORD dwmsMaxWait);
};

And the implementation file:

//This class uses the Intel RdRand CPU instruction for 
//the random number generator that is compliant with security 
//and cryptographic standards:
//
//  http://en.wikipedia.org/wiki/RdRand
//
//[32-bit Intel-only implementation]
//
#include "HardwareRandomNumberGenerator.h"

CHardwareRandomNumberGenerator::CHardwareRandomNumberGenerator(void) :
bRdRandSupported(FALSE)
{
    //Check that RdRand instruction is supported
    if(__is_cpuid_supported())
    {
        //It must be Intel CPU
        int name[4] = {0};
        if(__cpuid(name, 0))
        {
            if(name[1] == 0x756e6547 &&         //uneG
                name[2] == 0x6c65746e &&        //letn
                name[3] == 0x49656e69)          //Ieni
            {
                //Get flag itself
                int data[4] = {0};
                if(__cpuid(data, 1))
                {
                    //Check bit 30 on the 2nd index (ECX register)
                    if(data[2] & (0x1 << 30))
                    {
                        //Supported!
                        bRdRandSupported = TRUE;
                    }
                }
            }
        }
    }
}

CHardwareRandomNumberGenerator::~CHardwareRandomNumberGenerator(void)
{
}


int CHardwareRandomNumberGenerator::GetHardwareRandomBytes(BYTE* pOutRndVals, UINT* pncbInOutSzRndVals, DWORD dwmsMaxWait)
{
    //Generate random numbers into the 'pOutRndVals' buffer
    //INFO: This function uses CPU/hardware to generate a set of
    //      random numbers that are cryptographically strong.
    //INFO: For more details refer to:
    //       http://electronicdesign.com/learning-resources/understanding-intels-ivy-bridge-random-number-generator
    //INFO: To review the "ANALYSIS OF INTEL’S IVY BRIDGE DIGITAL RANDOM NUMBER GENERATOR" check:
    //       http://www.cryptography.com/public/pdf/Intel_TRNG_Report_20120312.pdf
    //'pOutRndVals' = if not NULL, points to the buffer that receives random bytes
    //'pncbInOutSzRndVals' = if not NULL, on the input must contain the number of BYTEs to write into the 'pOutRndVals' buffer
    //                                    on the output will contain the number of BYTEs actually written into the 'pOutRndVals' buffer
    //'dwmsMaxWait' = timeout for this method, expressed in milliseconds
    //RETURN:
    //      = 1 if hardware random number generator is supported & the buffer in 'pOutRndVals' was successfully filled out with random numbers
    //      = 0 if hardware random number generator is supported, but timed out while filling out the buffer in 'pOutRndVals'
    //          INFO: Check 'pncbInOutSzRndVals', it will contain the number of BYTEs actually written into the 'pOutRndVals' array
    //      = -1 if general error
    //      = -2 if hardware random number generator is not supported on this hardware
    //          INFO: Requires Intel Ivy Bridge, or later chipset.

    UINT ncbSzWritten = 0;
    int nRes = __fillHardwareRandomBytes(pOutRndVals, pncbInOutSzRndVals, ncbSzWritten, dwmsMaxWait);

    if(pncbInOutSzRndVals)
        *pncbInOutSzRndVals = ncbSzWritten;

    return nRes;
}

int CHardwareRandomNumberGenerator::__fillHardwareRandomBytes(BYTE* pOutRndVals, UINT* pncbInOutSzRndVals, UINT& ncbOutSzWritten, DWORD dwmsMaxWait)
{
    //INTERNAL METHOD

    ncbOutSzWritten = 0;

    //Check support
    if(!bRdRandSupported)
        return -2;

    __try
    {
        //We must have a buffer to fill out
        if(pOutRndVals &&
            pncbInOutSzRndVals &&
            (int*)*pncbInOutSzRndVals > 0)
        {
            //Begin timing ticks in ms
            DWORD dwmsIniTicks = ::GetTickCount();

            UINT ncbSzRndVals = *pncbInOutSzRndVals;

            //Fill in data array
            for(UINT i = 0; i < ncbSzRndVals; i += sizeof(DWORD))
            {
                DWORD random_value;
                int got_value;

                int nFailureCount = 0;

                //Since RdRand instruction may not have enough random numbers
                //in its buffer, we may need to "loop" while waiting for it to
                //generate more results...
                //For the first 10 failures we'll simply loop around, after which we
                //will wait for 1 ms per each failed iteration to save on the overall
                //CPU cycles that this method may consume.
                for(;; nFailureCount++ < 10 ? 1 : ::Sleep(1))
                {
                    __asm
                    {
                        push eax
                        push edx
                        xor eax, eax

                        ;RDRAND instruction = Set random value into EAX. Will set overflow [C] flag if success
                        _emit 0x0F
                        _emit 0xC7
                        _emit 0xF0

                        mov edx, 1

                        ;Check if the value was available in the RNG buffer
                        jc lbl_set_it

                        ;It wasn't available
                        xor edx, edx
                        xor eax, eax
lbl_set_it:
                        mov dword ptr [got_value], edx
                        mov dword ptr [random_value], eax

                        pop edx
                        pop eax
                    }

                    if(got_value)
                    {
                        //Got random value OK
                        break;
                    }

                    //Otherwise RdRand instruction failed to produce a random value

                    //See if we timed out?
                    if(::GetTickCount() - dwmsIniTicks > dwmsMaxWait)
                    {
                        //Timed out
                        return 0;
                    }

                    //Try again
                }

                //We now have a 4-byte, or DWORD, random value
                //So let's put it into our array
                if(i + sizeof(DWORD) <= ncbSzRndVals)
                {
                    *(DWORD*)(pOutRndVals + i) = random_value;
                    ncbOutSzWritten += sizeof(DWORD);
                }
                else if(i + sizeof(WORD) + sizeof(BYTE) <= ncbSzRndVals)
                {
                    *(WORD*)(pOutRndVals + i) = (WORD)random_value;
                    *(BYTE*)(pOutRndVals + i + sizeof(WORD)) = (BYTE)(random_value >> 16);
                    ncbOutSzWritten += sizeof(WORD) + sizeof(BYTE);
                }
                else if(i + sizeof(WORD) <= ncbSzRndVals)
                {
                    *(WORD*)(pOutRndVals + i) = (WORD)random_value;
                    ncbOutSzWritten += sizeof(WORD);
                }
                else if(i + sizeof(BYTE) <= ncbSzRndVals)
                {
                    *(BYTE*)(pOutRndVals + i) = (BYTE)random_value;
                    ncbOutSzWritten += sizeof(BYTE);
                }
                else
                {
                    //Shouldn't even be here
                    ASSERT(NULL);
                    return -1;
                }
            }
        }
    }
    __except(1)
    {
        //A generic catch-all just to be sure...
        return -1;
    }

    return 1;
}


BOOL CHardwareRandomNumberGenerator::__is_cpuid_supported(void)
{
    //See if CPUID command is supported
    //INFO: Some really old CPUs may not support it!
    //RETURN: = TRUE if yes, and __cpuid() can be called
    BOOL bSupported;
    DWORD nEFlags = 0;

    __try
    {
        #define FLAG_VALUE (0x1 << 21)

        _asm
        {
            //remember EFLAGS & EAX
            pushfd
            push eax

            //Set bit 21 in EFLAGS
            pushfd
            pop eax
            or eax, FLAG_VALUE
            push eax
            popfd

            //Check if bit 21 in EFLAGS was set
            pushfd
            pop eax
            mov nEFlags, eax

            //Restore EFLAGS & EAX
            pop eax
            popfd
        }

        bSupported = (nEFlags & FLAG_VALUE) ? TRUE : FALSE;
    }
    __except(1)
    {
        //A generic catch-all just to be sure...
        bSupported = FALSE;
    }

    return bSupported;
}

BOOL CHardwareRandomNumberGenerator::__cpuid(int data[4], int nID)
{
    //INFO: Call __is_cpuid_supported() first to see if this function is supported
    //RETURN:
    //      = TRUE if success, check 'data' for results
    BOOL bRes = TRUE;

    __try
    {
        _asm
        {
            push eax
            push ebx
            push ecx
            push edx
            push esi

            //Call CPUID
            mov eax, nID
            _emit 0x0f      ;CPUID
            _emit 0xa2

            //Save 4 registers
            mov esi, data
            mov dword ptr [esi], eax
            mov dword ptr [esi + 4], ebx
            mov dword ptr [esi + 8], ecx
            mov dword ptr [esi + 12], edx

            pop esi
            pop edx
            pop ecx
            pop ebx
            pop eax
        }

    }
    __except(1)
    {
        //A generic catch-all just to be sure...
        bRes = FALSE;
    }

    return bRes;
}

So idk, guys, I haven't done any extensive cryptographic analysis of the data produced by the method above ... so you'll be the judge. Any updates are welcome!

ahmd0
  • 16,633
  • 33
  • 137
  • 233
  • 2
    Aah, seeing assembly in a C/C++ program takes me back. – icabod Jan 30 '14 at 10:25
  • This is a correct way to use RdRand. It returns cryptographically secure random number from an SP800-90 compliant RNG implemented in hardware with a physical entropy source. specifically it is correct because the code is checking for the availability of thew instruction with CPUID, then getting the random number using the instruction. – David Johnston Feb 24 '17 at 20:58
3

Here's a little bit of code that produces a sequence of "cryptographically strong" bytes, using the Microsoft Cryptography API... I've used this myself, as aside from anything else it's a nice way to just get a decent random sequence of numbers... I wasn't using it for cryptography:

#include <wincrypt.h>

class RandomSequence
{
  HCRYPTPROV hProvider;
public:
  RandomSequence(void) : hProvider(NULL) {
    if (FALSE == CryptAcquireContext(&hProvider, NULL, NULL, PROV_RSA_FULL, 0)) {
      // failed, should we try to create a default provider?
      if (NTE_BAD_KEYSET == GetLastError()) {
        if (FALSE == CryptAcquireContext(&hProvider, NULL, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET)) {
          // ensure the provider is NULL so we could use a backup plan
          hProvider = NULL;
        }
      }
    }
  }

  ~RandomSequence(void) {
    if (NULL != hProvider) {
      CryptReleaseContext(hProvider, 0U);
    }
  }

  BOOL generate(BYTE* buf, DWORD len) {
    if (NULL != hProvider) {
      return CryptGenRandom(hProvider, len, buf);
    }
    return FALSE;
  }
};

It's a simple little class that tries to get an RSA Crytographic "provider", and if that fails it tries to create one. Then if all is well, generate will fill your buffer with love. Uhm... I mean random bytes.

This has worked for me on XP, Win7 and Win8, tho' I've not actually used it for cryptography, I just needed a decent sequence of random-ish bytes.

icabod
  • 6,992
  • 25
  • 41
  • Thanks. Two questions though. 1. Aren't we supposed to call `CryptAcquireContext`/`CryptReleaseContext` each time with `CryptGenRandom` to produce better entropy? And 2. I used the example at the bottom of this page in the comments section: http://msdn.microsoft.com/en-us/library/windows/desktop/aa379942(v=vs.85).aspx So did you have `CryptAcquireContext` fail on you and that's why you check for `NTE_BAD_KEYSET` error? – ahmd0 Jan 29 '14 at 18:53
  • 1
    @ahmd0: At least on XP the "entropy" used by `CryptGenRandom` is independent of the the crypto context. In fact on that OS, I wouldn't trust it to actually give cryptographically secure random numbers, also see http://stackoverflow.com/a/3487338/916657 regarding that aspect – Niklas B. Jan 29 '14 at 19:07
  • @NiklasB.: Thanks. Good point. I heard that the latest CPUs provide means to retrieve the "actual" random number from quantum jitters in the thermal noise. Can we expect Microsoft to utilize this in the latest OS's? – ahmd0 Jan 29 '14 at 19:19
  • @ahmd0: I have no idea whether they do, but I certainly wouldn't rely on it. – Niklas B. Jan 29 '14 at 19:36
  • @NiklasB.: Why? http://en.wikipedia.org/wiki/Hardware_random_number_generator and this http://electronicdesign.com/learning-resources/understanding-intels-ivy-bridge-random-number-generator seem quite right... – ahmd0 Jan 29 '14 at 19:54
  • @ahmd0: I wouldn't rely on it, because I don't know for sure. That does not mean I consider it unlikely – Niklas B. Jan 29 '14 at 19:54
  • @NiklasB.: There's evidently a new CPU machine instruction RdRand that does this on the low level, no OS involved: http://en.wikipedia.org/wiki/RdRand – ahmd0 Jan 29 '14 at 19:57
  • @ahmd0: You need to *use* that instruction as well if you want to profit from it. No idea whether MS does that but it might be possible to find out – Niklas B. Jan 29 '14 at 20:02
  • @ahmd0: 1. for my purpose, it wasn't necessary, but I just wanted a nice random sequence. 2. The code is based on the example from [CryptAcquireContext](http://msdn.microsoft.com/en-us/library/windows/desktop/aa379886.aspx), where `NTE_BAD_KEYSET` _can_ mean that the key container doesn't exist, so we then try to create it. I confess to not _fully_ understanding all this - but as mentioned, it provided me with what I wanted. – icabod Jan 30 '14 at 10:12
3
#include <stdexcept>
#include <string>
#include <sstream>

#ifndef __linux__
// For Windows
// Also Works with: MinGW Compiler
#include <windows.h>
#include <wincrypt.h> /* CryptAcquireContext, CryptGenRandom */

int RandBytes(void* const byte_buf, const size_t byte_len) {
  HCRYPTPROV p;
  ULONG     i;

  if (CryptAcquireContext(&p, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) == FALSE) {
    throw runtime_error{"RandBtyes(): CryptAcquireContext failed."};
  }

  if (CryptGenRandom(p, byte_len, (BYTE*)byte_buf) == FALSE) {
    throw runtime_error{"RandBytes(): CryptGenRandom failed."};
  }

  CryptReleaseContext(p, 0);
  return 0;
}
#endif // Not Linux

#if __linux__
#include <fctl.h>

int RandBytes(void* const byte_buf, const size_t byte_len) {
  // NOTE: /dev/random is supposately cryptographically safe
  int fd = open("/dev/urandom", O_RDONLY);
  if (fd < 0) {
    throw runtime_error{"RandBytes(): failed to open"};
  }

  int rd_len = 0;
  while(rd_len < byte_len) {
    int n = read(fd, byte_buf, byte_len);
    if (n < 0){
      stringstream ss;
      ss << "RandBytes(): failed (n=" << n << ") " << "(rd_len=" << rd_len << ")";
      throw runtime_error{ss.str()};
    }
    rd_len += n;
  }

  close(fd);
  return 0;
}
#endif 
Bimo
  • 5,987
  • 2
  • 39
  • 61
  • 1
    While this code may answer the question, providing additional context regarding why and/or how this code answers the question improves its long-term value. – Donald Duck Jan 13 '17 at 17:13
0

Not sure how portable this is, probably just BSD/Mac; but here's arc4random_buf:

void arc4random_buf(void *buf, size_t nbytes);

MacOS man page says:

These functions use a cryptographic pseudo-random number generator to generate high quality random bytes very quickly.

Victor Sergienko
  • 13,115
  • 3
  • 57
  • 91