【问题标题】:How to get "cryptographically strong" random bytes with Windows APIs?如何使用 Windows API 获取“加密强”随机字节?
【发布时间】:2014-01-29 01:14:36
【问题描述】:

我需要获得少量“加密良好”的随机字节。 (在我的情况下为 8 个字节。)是否有任何 Windows API 用于此?

PS。如果这些 API 能够向后兼容 Windows XP,那就太好了。但如果没有,它仍然可以工作。谢谢。

【问题讨论】:

  • CryptGenRandomRtlGenRandom,后者更易于使用。
  • @NiklasB.:谢谢。虽然我对这个说法并不怀疑:[The RtlGenRandom function ... may be altered or unavailable in subsequent versions. Instead, use the CryptGenRandom function.]
  • 在这种情况下,您可能想使用CryptGenRandom。不过,我不明白他们为什么会删除一个功能,因为它不会真正给他们买任何东西,但可能会让一些使用它来对抗警告的人感到不安:)
  • 更重要的是,RtlGenRandom 只生成一个伪随机数,虽然它是 Cryptography API 的一部分,但并没有真正喊出“密码学强大”。
  • @ahmd0:看看反汇编就知道了。或者相信下面的博文:blogs.msdn.com/b/michael_howard/archive/2005/01/14/353379.aspx 它只适用于 XP 和 vista 由于它的年代,可能

标签: c++ c windows winapi cryptography


【解决方案1】:

我知道我最初询问的是 Windows API,但自从我发表最初的帖子以来,我有一些时间进行研究。所以我想分享我的发现。

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

由于这是关于 Windows 实现的问题,并且大多数 Windows PC 在英特尔芯片组上运行,我决定编写一个小类(我不敢相信我这么说)似乎正在生成 真正的随机数Here's the description 是如何工作的,here's the analysis 是英特尔的 RNG。

我还假设此代码是为 32 位进程编译的(如果有人需要它用于 64 位实现,您必须调整 asm 部分。)说不应假定它将在任何英特尔硬件上运行。正如我上面所说,它需要相对较新的英特尔 Ivy Bridge 或更新的芯片组才能运行。 (我在后来的 Haswell 系统板上对其进行了测试。)好消息是几乎不用花时间就可以确定是否支持 RDRAND 指令,如果不支持,您最明显的方法应该是使用 OS provided APIs 中的任何一个,在其他帖子中描述。 (同时结合两种方法的结果也可以增加最终结果的熵。)

下面是我如何调用该方法来生成随机数:

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

所以,伙计们,我还没有对上述方法产生的数据进行任何广泛的密码分析......所以你将成为评委。欢迎任何更新!

【讨论】:

  • 啊,在 C/C++ 程序中看到汇编让我回味无穷。
  • 这是使用 RdRand 的正确方法。它从具有物理熵源的硬件中实现的符合 SP800-90 的 RNG 返回加密安全随机数。特别是它是正确的,因为代码正在使用 CPUID 检查 w 指令的可用性,然后使用该指令获取随机数。
【解决方案2】:

这里有一小段代码使用 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 Crytographic “提供者”,如果失败,它会尝试创建一个。如果一切顺利,generate 会用爱填满你的缓冲区。嗯...我的意思是随机字节。

这在 XP、Win7 和 Win8 上对我有用,虽然我实际上并没有将它用于加密,我只需要一个像样的随机字节序列。

【讨论】:

  • 谢谢。不过有两个问题。 1.我们不是应该每次都用CryptGenRandom调用CryptAcquireContext/CryptReleaseContext来产生更好的熵吗? 2. 我在 cmets 部分使用了本页底部的示例:msdn.microsoft.com/en-us/library/windows/desktop/… 那么您是否有 CryptAcquireContext 失败,这就是您检查 NTE_BAD_KEYSET 错误的原因?
  • @ahmd0:至少在 XP 上,CryptGenRandom 使用的“熵”与加密上下文无关。事实上,在那个操作系统上,我不相信它会真正提供加密安全的随机数,另请参阅 stackoverflow.com/a/3487338/916657 关于这方面
  • @NiklasB.:谢谢。好点子。我听说最新的 CPU 提供了从热噪声中的量子抖动中检索“实际”随机数的方法。我们可以期待微软在最新的操作系统中使用它吗?
  • @ahmd0:我不知道他们是否这样做,但我当然不会依赖它。
【解决方案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 

【讨论】:

  • 虽然此代码可能会回答问题,但提供有关此代码为何和/或如何回答问题的额外上下文可提高其长期价值。
【解决方案4】:

不确定它的便携性,可能只是 BSD/Mac;但这里是arc4random_buf

void arc4random_buf(void *buf, size_t nbytes);

MacOS man 页面说:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-03-20
    • 2014-07-13
    • 1970-01-01
    • 1970-01-01
    • 2020-12-03
    • 1970-01-01
    • 1970-01-01
    • 2012-07-04
    相关资源
    最近更新 更多