web-dev-qa-db-ja.com

ランダムなバイトから0〜9の数値を取得する正しい方法は?

一度に1バイトのデータを取得できる優れた乱数ジェネレーターがあり、そのバイトストリームから0から9のランダムな10進数を抽出したい場合、正しい方法は何ですか?

最初は単純な(randomByte mod 10)計算で十分だと単純に想定していましたが、256は10で割り切れるわけではないため、「ランダム」桁に明確なバイアスが生じます。

0: 101323 #################################
1: 101261 #################################
2: 101473 #################################
3: 101389 #################################
4: 101551 #################################
5: 101587 #################################
6: 97831  ###############################
7: 97893  ###############################
8: 97843  ###############################
9: 97849  ###############################
(histogram from 1 million 'random' digits)

機能しているように見える1つの方法は、249を超える値を破棄し、25で除算することです。これは暗号的に正しいですか?ランダム性の(潜在的に高価な)バイトの破棄を含まないより良い方法はありますか?

(この質問は CryptoCatの脆弱性 について読むことで促されます。発見された欠陥の1つは、249の代わりに250を超えるランダムな値を破棄し、わずかに彼らの「ランダムな」数に偏りがあるので...それを行うための「正しい」方法が何であるか私は知りました)

16
Johnny

「十分に偏りのない」ランダムな数字を生成するには、2つの一般的な方法があります。

最初の方法は、バイトが正しい範囲内になかった場合にループすることです。つまり:

  • 次のランダムバイトbを取得します。
  • bが0..249の範囲内にある場合、bmod 10を返します。
  • ループ。

このメソッドは無制限のランダムバイト数を消費する可能性がありますが、完全に不偏であり、何度もループする必要はほとんどありません。これがJavaの Random.nextInt(int) 標準メソッドが適用するものです(ただし、バイトの代わりに32ビットのワードを使用します)。

2番目の方法は、1つのbyteではなく、十分に大きなWordをソース値として使用することです。たとえば、20バイトを使用し、これらを0..2の整数xとして解釈します160-1の範囲で、xmod 10を返します。このようにして、バイアスはまだ存在しますが、問題がなくなるまで任意に小さくすることができます。これは(最初の方法よりも)計算コストが高くなりますが、常に同じ数の入力バイトを必要とするという利点があり、特定の状況(サイドチャネルリークなど)では便利です。

16
Tom Leek

複数の数値をレンダリングするためのバイトの分割

ランダムジェネレーターは何か高価かもしれないので、より効率的な方法は、各バイト(0-255)を2つの部分(0-15)にカットしてから範囲外の値:

bash を使用した小さなサンプルがあります:

unset random cnt
while [ ! "$random" ] ;do
    ((cnt++))
    i=$(dd if=/dev/random bs=1 count=1 2>/dev/null | od -A n -t u1)
    for val in $((i&15)) $((i>>4)) ;do
        [ $val -lt 10 ] && [ ! "$random" ] && random=$val
    done
done
printf "%d bytes read, random value: %d\n" $cnt $random

種類の比較:

@AndreasKreyのコメントへの回答として、0から9までの10個の数値を取得しようとする小さなデモがあります。

両方の方法で同じpot(乱数の)を使用します。

  • バイトを2つの部分に分割し、9より大きい数をフィルタリングする
  • 249より大きい数値を削除し、modを使用:

#!/bin/bash

myRandom() {
    printf ${1+-v} $1 "%s" $(
        head -c1 /dev/urandom | od -A n -t u1
    )
}

num=${1:-10} byteMod=0 byteSplit=0 potMod=() potSplit=() wholePot=()

while [ ${#potMod[@]} -lt $num ] || [ ${#potSplit[@]} -lt $num ];do
    myRandom rndVal
    wholePot+=($rndVal)

    [ ${#potMod[@]}   -lt $num ] && ((byteMod+=1)) &&
        [ $rndVal -lt 250 ] && potMod+=($[rndVal%10])

    [ ${#potSplit[@]} -lt $num ] && ((byteSplit+=1))

    for val in $[rndVal&15] $[rndVal>>4] ;do
        [ $val -lt 10 ] && [ ${#potSplit[@]} -lt $num ] && potSplit+=($val)
      done

  done

printf "%2d bytes was read for rendering %2d values by split * max 10: %s\n" \
    $byteSplit ${#potSplit[@]} "${potSplit[*]}"
printf "%2d bytes was read for rendering %2d values by max 250 && mod: %s\n" \
    $byteMod ${#potMod[@]} "${potMod[*]}"

echo Whole pot: ${wholePot[@]}

これは数回実行できます:

./randGen10.sh
 6 bytes was read for rendering 10 values by split * max 10: 8 3 9 7 9 3 1 1 3 4
10 bytes was read for rendering 10 values by max 250 && mod: 6 1 7 0 9 0 3 1 3 9
Whole pot: 56 121 57 30 49 20 183 161 123 239

./randGen10.sh
 7 bytes was read for rendering 10 values by split * max 10: 7 1 5 0 7 4 6 9 4 4
10 bytes was read for rendering 10 values by max 250 && mod: 3 3 6 1 8 3 4 0 4 9
Whole pot: 23 213 176 71 198 73 244 220 154 139

./randGen10.sh
10 bytes was read for rendering 10 values by split * max 10: 0 8 3 9 6 6 8 9 2 3
11 bytes was read for rendering 10 values by max 250 && mod: 1 8 2 5 4 8 9 9 0 7
Whole pot: 221 128 254 62 105 214 168 249 189 50 77

./randGen10.sh
 7 bytes was read for rendering 10 values by split * max 10: 3 1 5 9 5 8 6 9 7 7
10 bytes was read for rendering 10 values by max 250 && mod: 9 1 9 8 8 1 7 4 7 6
Whole pot: 19 181 89 168 198 121 247 54 117 226

./randGen10.sh
 9 bytes was read for rendering 10 values by split * max 10: 5 8 6 3 6 8 5 4 0 1
10 bytes was read for rendering 10 values by max 250 && mod: 4 0 0 9 3 4 8 6 6 9
Whole pot: 234 90 200 109 243 214 88 196 16 199

./randGen10.sh
11 bytes was read for rendering 10 values by split * max 10: 3 1 9 5 0 0 6 9 5 5
10 bytes was read for rendering 10 values by max 250 && mod: 5 9 7 0 5 0 1 7 8 9
Whole pot: 175 19 157 90 235 0 191 107 238 89 117

もちろん、splited max 10メソッドがmod max 250...より多くのバイトを使用する場合もあります。

説明:

驚いたことに、ドロップする必要がある6/256 values -> 2.34%は、ドロップする必要があるよりもはるかに小さいようです6/16 values -> 37.5%ですが、各数値について2回目のチャンスを得ることができました。

  • 分割することにより、2 x 62.5%=> 125%の確率で正しい数を得る
  • mod(または25で除算)を使用すると、97.65%の確率しかありません...

したがって、100,000の値をレンダリングする場合:

./randGen10.sh 100000 | sed 's/^\(.\{74\}\).*$/\1.../'
 80086 bytes was read for rendering 100000 values by split * max 10: 9 9 2...
102397 bytes was read for rendering 100000 values by max 250 && mod: 3 7 6...
Whole pot: 233 217 46 36 193 182 9 44 187 48 100 172 127 230 157 194 197 1...

これは正しいようです:100'000/97.65% = 102'407および100'000/1.25=80'000

3
F. Hauri

これを行うには2つの方法があります。

1つ目は、バイトを2つの4ビット数に分割することです。 4ビットの数値が9より大きい場合は、それを破棄します。このようにして、バイトあたり最大2桁を取得できます。

これを行うもう1つの方法は、200未満の場合は下2桁を取ることです。次に、2つのランダムな数字があります。 250未満の場合は、最後の桁を使用します。 250を超える場合は破棄します。このようにして、あなたの番号のうち最もランダムな数字を得ることができます。

1
Spl1ce