暗号学的に安全な乱数生成 -- 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 以降で導入された疑似乱数生成関数。