[Python] UUIDを生成するuuid.uuid4()はどうやってUUIDを生成しているのか?

95件のシェア(ちょっぴり話題の記事)

※2019/2/17:OS固有の乱数の発生方法について追記しました。

どうも!大阪オフィスの西村祐二です。

何かにIDを設定するときにUUIDをよく使うかと思います。

Pythonではuuidモジュールというのが提供されており下記のように簡単に生成することができます。

import uuid

hoge_id = str(uuid.uuid4())

どうやってuuidを生成しているの気になったのでドキュメントをみたところ、uuid1,3,5はわかったのですが、uuid4はどうやっているのかわからなかったので調べてみました。

https://docs.python.org/ja/3/library/uuid.html

環境

OS: macOS 10.14.3 python3.7.2

ソースコードをみて確認する

やはり、ここが一番の近道です。ソースを覗いてみるとos.urandom(16)を使っていることがわかります。

os.urandom()って何

ドキュメントによると

この関数は OS 固有の乱数発生源からランダムなバイト列を生成して返します。この関数の返すデータは暗号を用いたアプリケーションで十分利用できる程度に予測不能ですが、実際のクオリティは OS の実装によって異なります。

https://docs.python.jp/3/library/os.html#os.urandom

とのことで、アプリケーションで十分利用できると明記されているので安心ですね。

( OS固有の乱数発生源ってなんだと思いましたが、沼にはまりそうなので、今日はここまでにしておきます。)

=>OSによって乱数の発生方法が異なるということで、少し調査し追記しました。

- Linux

Linuxでは、getrandomシステムコールが使用可能な場合は、ブロッキングモードでこのシステムコールが使用されます。カーネルによって128ビットのエントロピーがエントロピープールに収集されるまでブロックされます。

エントロピープールは乱数を発生させるために、デバイスドライバや、その他の情報源から集めた環境ノイズ情報を貯める場所のようです。

getrandomは
/dev/randomまたは/dev/urandomからデータを読み出すことと同じ機能を提供するLinuxカーネルのシステムコールです。 getrandomはデフォルトで/dev/urandomを利用し、引数のフラグで動作が変更できるようです。

/dev/randomを利用する場合は引数のフラグにGRND_RANDOMを指定する必要があるのですが、 Pythonの実装をみるとフラグには0もしくはGRND_NONBLOCKしか渡さないようなロジックなので、/dev/urandomが利用されることになるかと思います。

/dev/randomは
擬似デバイスの一種で、エントロピープール内のノイズビット数から乱数のバイト列のみを返す機能をもっています。エントロピープールが空の場合、/dev/random から読み出そうとするとブロックされ、環境ノイズの収集がなされるまで待たされます。

つまり、LinuxOS上のPythonではgetrandomシステムコールが使用可能な場合、ブロッキングモードで/dev/urandomから乱数データを読み出すのかなと思われます。

詳細は下記をご参照ください。

https://www.python.org/dev/peps/pep-0524/

- Windows

Windowsでは、CryptGenRandom()が使用されています。

乱数の生成方法としては、SHA-1とFIPS 186-2 appendix 3.1で標準化された方法とエントロピーから乱数を生成しているようです。

ただ、こちらによると現在非推奨となっているAPIみたいです。

調べているとBCryptGenRandomという次世代のAPIが提供されているの今後のバージョンでは使われるAPIが変更されるかもしれませんね。

https://docs.microsoft.com/en-us/windows/desktop/api/bcrypt/nf-bcrypt-bcryptgenrandom

- UnixライクなOS

UnixライクなOSは/dev/urandomデバイスからランダムなバイト列が返されます。/dev/urandomデバイスが利用できない、または読めない場合は、NotImplementedErrorの例外を発生させます。

/dev/urandomは
過去に使ったエントロピーを再利用することで擬似乱数的な乱数ビット列を生成します。すなわち、/dev/randomのようにエントロピーが足りないと蓄積されるまでブロックされるということはありません。が、/dev/randomに比べると、真の乱数ではなく擬似的に乱数が生成されています。

/dev/randomと/dev/urandomの違いは下記サイトがわかりやすくまとめられています。

https://oplern.hatenablog.com/entry/2017/05/29/221313

====

つまり、os.urandom(16)

16バイト=128bitの乱数を生成してくれるということになります。

まとめ

uuid.uuid4()

裏でos.urandom(16)を使って、128ビットの乱数を生成し、それを利用してUUIDを生成しています。ただし、OSに依存します。

さいごに

いかがだったでしょうか。

uuid.uuid4()はどうやってUUIDを生成しているのか調べてみました。

誰かの参考になれば幸いです。

参考サイト

https://ja.wikipedia.org/wiki//dev/random

https://en.wikipedia.org/wiki/CryptGenRandom

https://docs.python.jp/3/library/os.html#os.urandom

https://oplern.hatenablog.com/entry/2017/05/29/221313