8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DelphiAdvent Calendar 2024

Day 9

[Delphi][小ネタ] CNG Random を使う

Last updated at Posted at 2024-12-08

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 化しちゃいましょう

PK.Math.CNGRandom.pas
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 ですが…

8
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?