CakePHP 3.x の PHP 5.6向けUUID実装では衝突がおきるかもねというお話

標準

この記事は、 CakePHP Advent Calendar 2019 2日目の記事です。

1日目の記事は、hgsgtkさんの「来たるCakePHP 4.0 を知ろう – Qiita」でした。


TL;DR: PHP 5.6 を使っている人は paragonie/random_compat を入れましょう。


先月に、UUIDの衝突に関する話題が盛り上がりました。

10秒で衝突するUUIDの作り方 – Speaker Deck

この話のキモは、UUIDの生成に擬似乱数関数の mt_rand を使用しているとシードにより乱数が固定化するので、同じUUIDが生成されてしまうということです。

特にプログラムコード中で不用意に mt_srand を呼び出していると、衝突しやすくなります。


CakePHP にも Cake\Utility\Text::uuid というメソッドがあり、UUIDv4が生成できるようになっています。データベーステーブルでidフィールドをchar(36)で定義すると、レコード追加時にこのメソッドが呼ばれてUUIDがセットされるようになっています。

Cake\Utility\Text::uuid は、

src/Utility/Text.php L61-83

と実装されており、random_int関数のない環境(< PHP 7.1)では、mt_rand関数が使用されます。

というわけで、Cake\Utility\Text::uuid() を使用した 衝突のサンプルコード を書いてみました。

サンプルコード抜粋

サンプルコードでは不用意な mt_srand の使用例として、ありがちなマイクロ秒をシードとして与えています。
なお、シードとしてマイクロ秒を与えなくても mt_srand が受け取るのは32bit整数の範囲なので、「10秒で衝突するUUIDの作り方」にあるように mt_srand を呼び出している時点で65,536回試行すればだいたい衝突します。

この問題は、CakePHP 3.x を PHP5.6環境で利用している時に発生します。PHP 7.1以降では、random_int 関数が使用されるため mt_rand に起因する問題は起こりません。
(CakePHP 4では、PHP 7.2以降のためこの問題は発生しません。)

Cake\Utility\Text::uuid() の呼び出し前に、 mt_srand が存在しない場合は衝突しにくくなりますが、分散環境でPID、時間が一致する場合は同じ乱数シードとなるので同じUUIDが生成されることがあります。
(参考: https://twitter.com/zeriyoshi/status/1199639847457046529


というわけで、UUIDv4の生成にはmt_randを使わないようにする必要があります。そのためには、PHP 5.6でも random_int 等の関数を使うことができるようになる paragonie/random_compat パッケージを入れましょう。

以下のように、

としてrandom_compatをインストールすれば、PHP7以降の環境では、何もしないバージョンである v9.99.99 が入りますので、将来的にPHPをバージョンアップする場合でも安心です。

補足

いちおう、この問題については Issue を上げていますので、将来のバージョン(3.9以降)では何らかの対策がされると思います。

Collision uuid in PHP 5.6 · Issue #13944 · cakephp/cakephp
Deprecation of mt_rand in uuid generation by nojimage · Pull Request #13958 · cakephp/cakephp

追記

3.9以降、paragonie/random_compatが必須となり、uuid生成でmt_randは使用されなくなります。

Deprecation of mt_rand in uuid generation by nojimage · Pull Request #13958 · cakephp/cakephp

コメントを残す

Page optimized by WP Minify WordPress Plugin