如何使用Windows API获取“密码学强度”的随机字节?

6

我需要获取一小段“密码学强度”的随机字节。(我需要8个字节。)是否有任何Windows API可以实现这个功能?

PS. 如果这些API与Windows XP向后兼容,那就太好了。但如果不是,也没关系,谢谢。


2
CryptGenRandomRtlGenRandom,后者使用起来更简单。 - Niklas B.
@NiklasB.:同意,尽管谁会想到他们会删除开始按钮...所以你永远不知道。 - ahmd0
1
@ahmd0:只需查看反汇编结果。或者相信以下博客文章:http://blogs.msdn.com/b/michael_howard/archive/2005/01/14/353379.aspx 由于其年龄,它仅适用于XP和Vista。 - Niklas B.
@NiklasB.:嗯,有趣。是的,反汇编是实现的方法。尽管我认为接受更详细参数的API(即CryptAcquireContextCryptReleaseContextCryptGenRandom)将嵌套在参数较少的API之内,例如RtlGenRandom - ahmd0
我个人认为,它只是调用了 RtlGenRandom,而整个加密上下文对于这个特定的函数来说完全无关紧要。 - Niklas B.
显示剩余6条评论
4个回答

14

我知道我最初询问的是关于Windows API的问题,但自从我的原始帖子以来,我有时间进行了一些研究。因此,我想分享一下我的发现。

事实证明,自从他们的Ivy Bridge芯片组以来,英特尔就包含了一个非常酷的硬件随机数生成器,可通过RDRAND CPU指令使用。

由于这是关于Windows实现的问题,并且大多数Windows PC都运行在英特尔芯片组上,所以我决定编写一个小类(我简直不敢相信我在说什么),似乎生成了真正的随机数这里描述了它的工作原理,这里分析了英特尔的RNG。

我还假设这个代码是编译为32位进程的(如果有人需要64位实现,您需要调整asm部分)。还值得注意的是,一个人不应该假设它将在任何英特尔硬件上运行。正如我上面所说,它需要比较新的英特尔Ivy Bridge或更高版本的芯片组才能运行。(我在后来的Haswell系统板上测试了它。)好消息是,几乎不需要时间就可以找出是否支持RDRAND指令,如果不支持,则您最明显的路线应该是使用其他帖子中描述的任何操作系统提供的API。(同时将两种方法的结果结合起来也可以增加最终结果的熵。)

因此,这里是我调用生成随机数的方法的方式:

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

实现文件如下:

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

我不知道,各位,对于上述方法生成的数据,我还没有进行深入的加密分析……所以要由你来判断。欢迎提供任何更新!


2
啊,看到C/C++程序中的汇编代码让我回想起了过去。 - icabod
这是使用RdRand的正确方式。它从硬件实现的符合SP800-90标准的RNG中返回加密安全的随机数,该RNG具有物理熵源。具体而言,它是正确的,因为代码正在使用CPUID检查指令的可用性,然后使用该指令获取随机数。 - David Johnston

3

这里有一小段代码,使用Microsoft Cryptography API生成一串“加密强度”的字节...我自己也用过它,因为除了其他方面,它是一个很好的方法来得到一个不错的随机数字序列...我并没有将其用于加密:

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

这是一个简单的小类,它尝试获取RSA加密“提供程序”,如果失败,则尝试创建一个。然后,如果一切顺利,“generate”将用爱填充您的缓冲区。嗯……我的意思是随机字节。

这对我在XP、Win7和Win8上都有效,但我实际上没有将其用于密码学,我只需要一个不错的随机字节序列。


1
@ahmd0:至少在XP上,CryptGenRandom使用的“熵”与加密上下文无关。实际上,在该操作系统上,我不会相信它能够提供具有密码学安全性的随机数。另请参见https://dev59.com/JE_Ta4cB1Zd3GeqPD8kx#3487338,了解该方面的信息。 - Niklas B.
@NiklasB.:谢谢。说得好。我听说最新的CPU提供了从热噪声中获取“实际”随机数的方法。我们可以期待微软在最新的操作系统中利用这一点吗? - ahmd0
@NiklasB.: 为什么?http://en.wikipedia.org/wiki/Hardware_random_number_generator 和 http://electronicdesign.com/learning-resources/understanding-intels-ivy-bridge-random-number-generator 看起来都很正确... - ahmd0
@ahmd0:我不会依赖它,因为我不确定。这并不意味着我认为它不可能。 - Niklas B.
@NiklasB:显然有一种新的CPU机器指令RdRand可以在低级别上完成这个任务,无需涉及操作系统:http://en.wikipedia.org/wiki/RdRand - ahmd0
显示剩余4条评论

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 

1
虽然这段代码可以回答问题,但是提供有关为什么和/或如何回答问题的额外上下文可以提高其长期价值。 - Donald Duck

0

不确定这个有多可移植,可能只局限于BSD/Mac;但是这里有arc4random_buf

void arc4random_buf(void *buf, size_t nbytes);

MacOS中的man页面说:

这些函数使用加密伪随机数生成器快速生成高质量的随机字节。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接