暗号学的に安全な乱数生成 -- Windows 2000 用

ついカッとなって書いた。検証はしていない。

#include <Wincrypt.h>
#include <Tchar.h>
#pragma comment(lib, "advapi32") 

/* RtlGenRandom による乱数生成に成功したら 0、
   そうでなければ 1 を返す */
static DWORD secure_rand_xp(INT *value)
{
  HMODULE module = LoadLibrary(TEXT("Advapi32.dll"));
  /* RtlGenRandom function */
  FARPROC proc = GetProcAddress(module, "SystemFunction036");
  BOOLEAN succeeded = (*(BOOLEAN (*)(PVOID, ULONG))(proc))
    (value, sizeof(*value));
  if (module) FreeLibrary(module);
  return (module && succeeded) ? 0 : 1;
}

/* CryptGenRandom による乱数生成を試みる */
static DWORD secure_rand_2k(INT *value)
{
  DWORD retry = 5, err = ERROR_SUCCESS;
  HANDLE provider;
  if (!CryptAcquireContext(&provider, NULL, NULL
    , PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
    return GetLastError();
  if (!CryptGenRandom(provider, sizeof(*value), (BYTE*)value))
    err = GetLastError();
  while (retry-- && !CryptReleaseContext(provider, 0)
    && ERROR_BUSY == GetLastError())
    Sleep(200);
  return err;
}

DOWRD secure_rand(INT *value)
{
  return 0 == secure_rand_xp(value)
    ? ERROR_SUCCESS : secure_rand_2k(value);
}

※これは INT サイズの乱数を取得するコード例です。呼び出し側は INT value; DWORD err = secure_rand(&value); if (0 != err) { // エラー処理 } のようなものを考えていました。任意長の乱数を得たい場合(それが自然ですよね…)、 secure_rand の引数を PVOID と UINT として、ポインターとデータ長を受け取るようにし、先の SystemFunction036 や CryptGenRandom の呼び出しに渡せるようにします。

たとえば暗号プロバイダーを都度生成しては解放するという無駄をしているだとか、解放に失敗したときに最後まで面倒をみないだとか、エラーコードの解説をまったくしていないだとか、いろいろ言い訳したい個所がある。

コンテキストを保存せずに一関数で解決しようとすると、こんな感じにならざるをえないとはおもうものの。

RtlGenRandom は XP 以降で導入された疑似乱数生成関数。