JPCERT コーディネーションセンター

MSC32-C. 乱数生成器には適切なシード値を与える

疑似乱数生成器 (PRNG) は、乱数に近い性質の数列を生成できる決定性アルゴリズムである。各数列は、PRNG の初期状態と状態を変化させるアルゴリズムによって決定される。大半の PRNG は初期状態 (シード状態 とも呼ばれる) を設定可能である。初期状態を設定することを「PRNG にシードを設定する」と表現する。

明示的にシードを設定しない、あるいは同じシード値を設定するなど、同一の初期状態で PRNG を呼び出している場合には、プログラムを実行し直しても同一の数列が生成される。シードを設定して PRNG 関数を10 回連続で呼び出し、10 個の乱数からなる数列 S = {r1, r2, r3, r4, r5, r6, r7, r8, r9, r10} が生成されたとする。この後、同一のシード値を設定して再度このコードを実行すると、まったく同じ数列 S が生成される。

したがって、一度 PRNG が実行されれば、攻撃者は次に生成される乱数列を予測することができる。PRNG に不適切なシード値を設定したり、そもそもシードを設定しなかったりすると、脆弱性につながる可能性がある。特にセキュリティプロトコルでは重大な問題になる。

解決策は、常に PRNG に適切なシード値を設定することである。PRNG に適切なシード値を設定することで、異なる乱数列が生成される。

すべての乱数生成器でシードを設定できるわけではない。ハードウェアで予測不可能な結果を生成する真の乱数生成器にはシードを設定する必要はないし、設定できるようなつくりにはなっていない。一部の UNIX システムで実装されている /dev/random デバイスなどのような高品質な PRNG もシードの設定はできない。このルールは、シードを設定することが可能なアルゴリズム的疑似乱数生成器にのみ適用されるものとする。

違反コード (POSIX)

以下のコード例では random() 関数を使用して10個の擬似乱数列を生成している。random() はシードが設定されていない場合、rand() 関数と同じように振舞う。そのため、このコードは何度呼び出されても同じ数列を生成する。

#include <stdio.h>
#include <stdlib.h>

void func(void) {
  for (unsigned int i = 0; i < 10; ++i) {
    /* 常に同一の数列を生成する */
    printf("%ld, ", random());
  }
}

出力は次のようになる。

1st run: 1804289383, 846930886, 1681692777, 1714636915, 1957747793, 424238335, 719885386, 1649760492, 596516649,
         1189641421,
2nd run: 1804289383, 846930886, 1681692777, 1714636915, 1957747793, 424238335, 719885386, 1649760492, 596516649,
         1189641421,
...
nth run: 1804289383, 846930886, 1681692777, 1714636915, 1957747793, 424238335, 719885386, 1649760492, 596516649,
         1189641421,
適合コード (POSIX)

random() 関数を呼び出す前に srandom() 関数を呼び出してシード値を設定すること。以下の適合コードでは、実行するたびに異なる乱数列を生成する。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void func(void) {
  struct timespec ts;
  if (timespec_get(&ts, TIME_UTC) == 0) {
    /* Handle error */
  }
  else {
    srandom(ts.tv_nsec ^ ts.tv_sec);
    for (unsigned int i = 0; i < 10; ++i) {
      /* Generates different sequences at different runs */
      printf("%ld, ", random());
    }
  }
}

出力は次のようになる。

1st run: 198682410, 2076262355, 910374899, 428635843, 2084827500, 1558698420, 4459146, 733695321, 2044378618, 1649046624,
2nd run: 1127071427, 252907983, 1358798372, 2101446505, 1514711759, 229790273, 954268511, 1116446419, 368192457,
         1297948050,
3rd run: 2052868434, 1645663878, 731874735, 1624006793, 938447420, 1046134947, 1901136083, 418123888, 836428296,
         2017467418,

これは、複数のスレッドが並行実行されている状況では適切な乱数列を得るには不十分かもしれない。各スレッドで生成される乱数列の間に相関ができるなどの可能性がある。また、unsigned int 型が16ビット幅の組み込みシステムにおいても、乱数生成コードとしては不十分だろう。(POSIX 標準では、unsigned int のビット幅は32ビット以上と規定している。しかし2001年より前の POSIX.1 では 16ビット幅の unsigned int が許されていた)。アプリケーションや要求されるセキュリティレベルによって、PRNG にシードを設定する方法は変わってくるだろう。一般に、乱数生成にはソフトウェアよりもハードウェアのほうが適している(例えば、ダイオードの熱雑音をサンプリングするなど)。

適合コード (Windows)

CryptGenRandom() 関数は、引数がシードとして使われるため、適切にシードが設定されないというリスクは発生しない。

#include <Windows.h>
#include <wincrypt.h>
#include <stdio.h>

void func(void) {
  HCRYPTPROV hCryptProv;
  long rand_buf;
  /* CSP をインスタンス化する例 */
  if (CryptAcquireContext(&hCryptProv, NULL, NULL,
                          PROV_RSA_FULL, 0)) {
    printf("CryptAcquireContext succeeded.\n");
  } else {
    printf("Error during CryptAcquireContext!\n");
  }

  for (unsigned int i = 0; i < 10; ++i) {
    if (!CryptGenRandom(hCryptProv, sizeof(rand_buf),
                        (BYTE *)&rand_buf)) {
      printf("Error\n");
    } else {
      printf("%ld, ", rand_buf);
    }
  }
}

出力は次のようになる。

1st run: -1597837311, 906130682, -1308031886, 1048837407, -931041900, -658114613, -1709220953, -1019697289, 1802206541,
         406505841,
2nd run: 885904119, -687379556, -1782296854, 1443701916, -624291047, 2049692692, -990451563, -142307804, 1257079211,
         897185104,
3rd run: 190598304, -1537409464, 1594174739, -424401916, -1975153474, 826912927, 1705549595, -1515331215, 474951399,
         1982500583,
リスク評価

ルール

深刻度

可能性

修正コスト

優先度

レベル

MSC32-C

P18

L1

関連するガイドライン
CERT C Secure Coding Standard MSC30-C. 疑似乱数の生成に rand() 関数を使用しない
CERT C++ Secure Coding Standard MSC32-CPP. Ensure your random number generator is properly seeded
MITRE CWE CWE-327, Use of a Broken or Risky Cryptographic Algorithm
CWE-330, Use of Insufficiently Random Values
参考資料
[MSDN] "CryptGenRandom Function"
翻訳元

これは以下のページを翻訳したものです。

MSC32-C. Properly seed pseudorandom number generators (revision 76)

Top へ

Topへ
最新情報(RSSメーリングリストTwitter