Delphi の Random 関数
Delphi の Random 関数、デフォルトで提供されているアルゴリズムが線形合同法だけなんですよね。
まあ、多くの場合これで足りるんですけど、乱数の品質に関してはちょっと弱い。
でも Windows なら、Windows ならきっと何とかしてくれる!
CNG Random
CNG とは Crypto Next Generation の略で、その名の通り次世代の暗号化ライブラリ群です。
従来の CryptoAPI を置き換える物になっています。
CNG が提供している API に BCryptGenRandom という乱数を返す API があります。
この API が返す乱数は暗号に利用可能な強度を持った乱数です。
使い方は簡単で↓これだけで Result に乱数が返ります。
function CNGRandom: UInt32;
begin
if
BCryptGenRandom(
0, // アルゴリズム・プロバイダのハンドル
@Result, // 乱数を格納するバッファのアドレス
SizeOf(Result), // バッファのサイズ
BCRYPT_USE_SYSTEM_PREFERRED_RNG // デフォルト乱数アルゴリズムを使用
) <> ERROR_SUCCESS
then
Result := 0;
end;
BCRYPT_USE_SYSTEM_PREFERRED_RNG を使う場合はプロバイダのハンドルは 0 を指定します。
これで、つよつよ乱数が手に入ります。
でもこんなに引数指定するの面倒なので、簡単に呼べるようにしましょう。
Random32Proc 変数
System.pas に定義されている Random32Proc 変数の中身を入れ替えると、乱数プロバイダを変更できます。
これを先ほどの CNGRandom に変えます。
Random32Proc := CNGRandom;
var R := Random(10); // CNGRandom が呼ばれる!
Delphi デフォルトの Random 関数を呼ぶと CNGRandom が呼ばれるようになります。
簡単~
Unit 化
と、まあ BCryptGenRandom を簡単に呼べるようになったですが、実は CNG は Delphi では宣言されてないので定義なんかも書かないといけないんですよね。
なので、Unit 化しちゃいましょう
unit PK.Math.CNGRandom;
interface
implementation
uses
Winapi.Windows;
// CNG Random の定義
const
LIB_NAME = 'bcrypt.dll';
BCRYPT_USE_SYSTEM_PREFERRED_RNG = 2;
function BCryptGenRandom(
hAlgorithm: THandle;
pbBuffer: PByte;
cbBuffer: ULONG;
dwFlags: ULONG): HRESULT; stdcall; external LIB_NAME;
function CNGRandom: UInt32;
begin
if
BCryptGenRandom(
0,
@Result,
SizeOf(Result),
BCRYPT_USE_SYSTEM_PREFERRED_RNG
) <> ERROR_SUCCESS
then
Result := 0;
end;
initialization
// Random で呼ばれる乱数生成器を CNGRandom に置き換える
Random32Proc := CNGRandom;
end.
これを uses すると、Random を呼ぶと CNGRandom が呼び出されるようになります。
サンプル
ほとんど意味はありませんが、1万回呼んで 0~9 の値がどの程度出てくるかカウントしてみます(乱数の品質がわかるとかそういう意味は全くないサンプルです)。
program CNGRandomSample;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Winapi.Windows,
PK.Math.CNGRandom in 'PK.Math.CNGRandom.pas';
begin
var Rs: array [0.. 9] of Integer;
FillChar(Rs, SizeOf(Rs), 0);
for var i := 0 to 10000 do
begin
var R := Random(10); // CNGRandom を呼び出す
Inc(Rs[R]);
end;
for var i := 0 to High(Rs) do
Writeln(i, ' ', Rs[i]);
Readln;
end.
結果はこんな感じでした。
0 988
1 1015
2 973
3 1001
4 997
5 995
6 985
7 1066
8 979
9 1002
まとめ
セキュリティに重要な意味を持つ場合は BCryptGenRandom を使うといいですね。
そうでない場合はデフォルトの線形合同法でも一定の価値はあるでしょう。
個人的に良く使うのは XorShift ですが…