MSC30-C. 疑似乱数の生成に rand() 関数を使用しない
疑似乱数生成器は数学的アルゴリズムを用いて数列を生成する。生成される数列は統計的には十分な乱数特性を持つが、真にランダムではない。
C の標準関数 rand()
には、生成する乱数列の特性についてなんの保証もない。rand()
の実装によっては、生成される数値列の周期が比較的短くなってしまい、どのような値が生成されるか予測される恐れがある。良い乱数特性を必要とするアプリケーションは、必要とする乱数特性を持つことが保証されている擬似乱数生成器を使わなければならない。
違反コード
以下のコード例は、rand()
関数を使って生成された数値部分を持つ識別子を生成する。生成された識別子は予測可能であり十分にランダムではない。
#include <stdio.h> #include <stdlib.h> enum { len = 12 }; void func(void) { /* * id は、文字列 "ID" ではじまり * ランダムな整数が続く識別子を格納する */ char id[len]; int r; int num; /* ... */ r = rand(); /* 乱数を生成する */ num = snprintf(id, len, "ID%-d", r); /* 識別子を生成する */ /* ... */ }
適合コード (POSIX)
次のコードでは、rand() 関数を POSIX 関数の
random() に置き換えている。
#include <stdio.h> #include <stdlib.h> #include <time.h> enum { len = 12 }; void func(void) { /* * id は、文字列 "ID" ではじまり * ランダムな整数が続く識別子を格納する */ char id[len]; int r; int num; /* ... */ struct timespec ts; if (timespec_get(&ts, TIME_UTC) == 0) { /* エラー処理 */ } srandom(ts.tv_nsec ^ ts.tv_sec); /* PRNG にシードを設定する */ /* ... */ r = random(); /* 乱数を生成する */ num = snprintf(id, len, "ID%-d", r); /* 識別子を生成する */ /* ... */ }
POSIX 関数の random()
は、より良い擬似乱数生成器である。rand()
の実装によっては、生成される数値の下位十数ビットに周期性がみられる場合があるが、random()
が生成する数値の場合はそのようなことはない。また、その他の選択肢として rand48
系の関数がある。
POSIX では規定されていないが、arc4random()
をサポートするシステムではこれも選択肢に入る。arc4random(3)
のマニュアルには次のように記述されている[OpenBSD]。
... は
rand(3)
,random(3)
,drand48(3)
で説明したものよりも高品質な乱数を提供する。
考えられる限り最良の乱数を得るには、処理系依存の関数を使わねばならない。強力な暗号鍵を生成する場合などのように、予測不可能であることが重要でスピードが問題にならない場合には、/dev/random
や乱数生成ハードウェアのような、(擬似乱数生成器でない)エントロピー源を使用すること。なお、適切なエントロピーを生成するのに十分なイベントが発生しない場合、/dev/random
は長時間にわたってブロックすることもあるので注意が必要である。
適合コード (Windows)
Windows プラットフォームでは、CryptGenRandom()
関数を用いて暗号学的に強力な乱数を生成することができる。ただしその実装の詳細は知られておらず、たとえばエントロピー源として何を使用しているかなどは不明であることに注意すること。Microsoft Developer Network [MSDN] の CryptGenRandom()
には次のように記載されている。
アプリケーションが良質の乱数源にアクセスできるならば、
CryptGenRandom()
呼び出し前に、バッファpbBuffer
にランダムデータを充填してもよい。CSP (Cryptographic Service Provider、暗号化サービスプロバイダ) はそのデータを用いて内部のシードを更にランダマイズする。バッファpbBuffer
を初期化するステップは省略してもよい。
#include <Windows.h> #include <wincrypt.h> #include <stdio.h> void func(void) { HCRYPTPROV prov; if (CryptAcquireContext(&prov, NULL, NULL, PROV_RSA_FULL, 0)) { long int li = 0; if (CryptGenRandom(prov, sizeof(li), (BYTE *)&li)) { printf("Random number: %ld\n", li); } else { /* エラー処理 */ } if (!CryptReleaseContext(prov, 0)) { /* エラー処理 */ } } else { /* エラー処理 */ } }
リスク評価
rand()
関数を使うと、予測可能な乱数が生成される可能性がある。
ルール |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
MSC30-C |
中 |
低 |
低 |
P6 |
L2 |
自動検出(最新の情報はこちら)
ツール |
バージョン |
チェッカー |
説明 |
---|---|---|---|
|
|
|
|
ECLAIR |
1.2 |
CC2.MSC30 |
実装済み |
Fortify SCA |
5.0 |
|
|
LDRA tool suite |
8.5.4 |
|
|
PRQA QA-C | 8.1 | Warncall -wc rand | 実装済み |
Related Guidelines
CERT C++ Secure Coding Standard | MSC30-CPP. Do not use the rand() function for generating pseudorandom numbers |
CERT Oracle Secure Coding Standard for Java | MSC02-J. 高品質の乱数を生成する |
MITRE CWE | CWE-327, Use of a Broken or Risky Cryptographic Algorithm CWE-330, Use of Insufficiently Random Values |
参考情報
[MSDN] | "CryptGenRandom Function" |
[OpenBSD] | arc4random() |
翻訳元
これは以下のページを翻訳したものです。
MSC30-C. Do not use the rand() function for generating pseudorandom numbers (revision 97)