概要
前回、こういった乱数の生成記事を書きましたがめちゃくちゃ間違っていたので
ちゃんとしたものを書きました。恥ずかしい限りです。
目次
ソース
https://github.com/Lycheejam/PasswordCreater/tree/master/PasswordCreater 🔗
System.Randomクラスを利用する
上記のパスワード生成機ではSystem.Random.Next
を使用して
文字列の中からランダムに指定されたIndexの文字をパスワードの文字列に結合しています。
その際に、Random
にseed値を渡してやる必要があります。
このシード値を元に計算を開始します。そのため、このシード値が計算のたびに同じものだと同じ値が返されてしまいます。
public string CreatePasswd(int pwdLen, string pwdChar, int seed) {
var sr = new StringBuilder(pwdLen);
var rnd = new Random(seed);
for (int i = 0; i < pwdLen; i++) {
sr.Append(pwdChar[rnd.Next(pwdChar.Length)]);
}
return sr.ToString();
}
修正前(DateTime.Now.Ticksを使用)
冒頭でも紹介した乱数の生成記事で言っている方法です。
乱数と言うより、カウントアップし続けている時計ですね。
これを下記のように使用していました。
Random r = new Random((int)DateTime.Now.Ticks);
アホなので何も気が付かなかったのですがlong型
からint型
にキャストしたら普通に桁あふれしますよね。
何故か頭の中で上位ビットが切り捨てられて下位ビットのみ格納されてると思いこんでいました。
そしたら2の補数計算とかズレますよね。
DateTime.Now.Ticksを使用した正しいシード値の方法
上記の方法をとるなら下記のコードが正しい
Random r = new Random((int) DateTime.Now.Ticks & 0x0000FFFF);
ビット演算を行って上位ビットを切り捨てる必要がありました。
(まあこのビット演算公式ドキュメントに載っているのにまったく気が付かなかったんですけどね)
https://msdn.microsoft.com/ja-jp/library/ctssatww(v=vs.110).aspx 🔗
修正後(RNGCryptoServiceProviderを使用)
RNGCryptoServiceProvider.GetByte
を使用してバイト配列に乱数を取得します。
public int CreateRandomSeed() {
var bs = new byte[4];
//Int32と同じサイズのバイト配列にランダムな値を設定する
using (var rng = new RNGCryptoServiceProvider()) {
rng.GetBytes(bs);
}
//RNGCryptoServiceProviderで得たbit列をInt32型に変換してシード値とする。
return BitConverter.ToInt32(bs, 0);
}
ソースはdobon.netさんからコピペです。
RNGCryptoServiceProvider.GetByte
で各バイト配列に乱数を取得BitConverter.ToInt32
でバイト配列をInt32型に変換
こんな感じで乱数が出来上がりました。これをシード値に使ってやる。
乱数の生成アルゴリズムの過程で乱数に偏りが出るようですが
調べても難しい数式ばかりで諦めました。
参考サイト様
- https://dobon.net/vb/dotnet/programing/random.html 🔗
- http://www001.upp.so-net.ne.jp/isaku/rand.html 🔗
- https://msdn.microsoft.com/ja-jp/library/system.bitconverter.toint32(v=vs.110).aspx 🔗
- https://msdn.microsoft.com/ja-jp/library/wb9c8c67(v=vs.110).aspx 🔗
- https://msdn.microsoft.com/ja-jp/library/ctssatww(v=vs.110).aspx 🔗
雑感
このコードを書いたのが結構前で思い出すのに時間がかかりました。
なんかもっと調べてたはずなんですけど忘却の彼方です。
調べたらすぐ忘れないように記事にしておいたほうがいいですね。