RFC6238で遊ぼう!
RFC6238とは
なにそれ?って人が多いとは思いますが、「Google Authenticator」と言えば
分かる人が多いと思います。
仕組みをざっくり説明すると
- 予め秘密鍵Kをサーバと端末とで共有する
- 基準時刻(大抵UNIX TIME=0の時刻)から現在時刻までの秒数を、時間ステップ(30秒)で割った商Tを計算する
- HMACベースの関数、HOTP(K, T)を用意する
- R <- HMAC(K, T)を計算し、得られたRから6桁の数字を抽出すると、ワンタイムパスワードとなる
これにより、秘密鍵と時刻が同じならば、同じパスワードが得られるため、二要素認証として使えるわけです。
ところで、、、
ワンタイムパスワードなので、前の時間ステップの値から次の時間ステップの値を推測は出来ない => これ、時間で決定される疑似乱数として使えません?
どう計算するのか?
- 基準時刻と時間ステップは30秒ではなく、要件に合わせた数値に切り替える
- 最後のRの長さは使用するハッシュ関数の長さに依存する -> sha256であれば32byteのデータ、64bit整数4個分の値が得られる(Google Authenticatorはsha1で、ハッシュの一部を使って6桁の数値を出しています)
これにより、秘密鍵と現在時刻があれば、時間で変化する乱数を生成できます。
具体的に何に使えるのか?
例: ランダム名のチャットサービス
実際の実装に近い形ですが、以下の課題があるとします
- ランダムに名前が割り当てられるチャットサービス
- そのランダムの名前は一週間で変化します
- 名前の再抽選のタイミングは全ユーザ同じタイミングとします
この場合、各ユーザで一般的な乱数を使って計算して再抽選する場合、以下の問題が発生します
- 再抽選の結果をDBかどこかに保存する必要があります
- 再抽選のタイミングが同じなので、短時間に大量のデータを保存する必要が発生します
これを以下のように対処することで解決します。
- ユーザ作成時に秘密鍵Kを持たせます
- 時間ステップを1週間にしてTを計算し、HMAC(K, T)から得られた値を元に名前を決定するようにする
こうすることで、秘密鍵Kさえあれば、任意の時刻のユーザ名を簡単に計算で出すことができ、データベースに保存する必要が無くなります。
例: 時間で変化するステージギミック
例えば以下のようなゲームがあったとします。
- ステージ上でアイテムや経験値を集めてパワーアップしていくマルチプレイゲーム
- ステージ上には時間経過でランダムに変化する仕掛けが多数ある
- ゲームの時間が長く、仕掛けが変化する回数がとても多い
この場合、各仕掛けの変化の抽選結果を持つと、多数の乱数を生成&共有する必要があります。
これを、シード + 仕掛けID => Kという形にすると、シード一個決めるだけですべての仕掛けの選出が決定的になります。
他の疑似乱数との違い
「シードと時間によって決定される乱数」であることが、他の疑似乱数に無い特徴です。
代わりに内部状態を持たないので、時間とシードが同じである限り、何度繰り返しても同じ値を返します。
結論
RFC6238を元に時間によって決定される疑似乱数ができました。
ゲームやサービスの時間ギミックに使えると思います。