MD5-Ring その2

これは、ずっと前に書いた MD5-Ring という記事の続きです:

MD5-Ring - peanutsjamjamの日記
http://d.hatena.ne.jp/peanutsjamjam/20060218/p2


その3もあります:

MD5-Ring その3 - peanutsjamjamの日記
http://d.hatena.ne.jp/peanutsjamjam/20080902/p1

PerlMD5 を計算させてみる

Perl で、00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 という 16 バイトのデータの MD5 を表示するプログラムを書くとこうなる。(計算された md5 の値だけじゃなくて、どんな値を md5 関数に入力しているのかも一応表示。)

#! /usr/bin/perl
use Digest::MD5 qw ( md5 );

$data = pack("H*", "00" x 16);

print unpack("H*", $data), "\n";
$digest = md5($data);
print unpack("H*", $digest), "\n";


実行結果

00000000000000000000000000000000
4ae71336e44bf9bf79d2752e234818a5


別な 16 バイトのデータで試したい場合はこう書く。

#! /usr/bin/perl
use Digest::MD5 qw ( md5 );

$data = pack("H*", "4ae71336e44bf9bf79d2752e234818a5");

print unpack("H*", $data), "\n";
$digest = md5($data);
print unpack("H*", $digest), "\n";


実行結果

4ae71336e44bf9bf79d2752e234818a5
398d01fdf7934d1292c263d374778e1a


計算されたダイジェスト値をさらに md5 の入力として与えてやって、また計算させる・・・・・・ってのを 10 回繰り返すと、こんな感じ。

#! /usr/bin/perl

use Digest::MD5 qw ( md5 );

$data = pack("H*", "00" x 16);

print unpack("H*", $data), " <= start\n";

for (1 .. 10) {
    $data = md5($data);
    print unpack("H*", $data), "\n";
}

上のほうのプログラムで使っていた $digest っていう変数は消えて、$data っていう変数だけを使っている。


実行結果

00000000000000000000000000000000 <= start
4ae71336e44bf9bf79d2752e234818a5
398d01fdf7934d1292c263d374778e1a
7995886059df6dd8a1cef4338aa4118e
543a5cb14988ac60d7068649d34434f8
fbfdd8ae19dd5a531f729e8ec0ab66aa
d55f8f0ee335d18a55e983b568a9bb05
33105995009f4b777dbe84d4801e56ae
23841eed0995d10e3b9def2e79a6fcb8
73562a1e09d18bb2ff2b9445c70b4bb8
4892033e755650213415bbd6535a04cb

これをずーっと続けて行ったら


これをずーっと続けて行ったら、どこかで、既に一度出力した値をふたたび出力するだろう、ってことは自明だ。だって、

  1. どんな 16 バイトを与えても、md5 関数は何らかの 16 バイトを返す
  2. 16 バイトで表現できる値の種類は有限だ

という 2 つの命題が真だもの。*1


どこかで「既に一度出力した値」を md5 関数が吐いたら、そこから先は当然、以前と同じ道をたどることになる。そしてまた、先の「既に一度出力した値」を吐いて、以下ぐるぐる回る。これが「MD5 Ring」。

どうやって調べたらいいのか

どこで「既に一度出力した値」を吐くのかを調べるために、どういう方法を採ったらいいんだろう。


たとえば、上の例でいちばん最初に md5 関数が返してきた値 4ae71336e44bf9bf79d2752e234818a5 をハードコードで記録しておいて、md5 関数が値を返すたびに、それと比較するっていうのを考えてみる。

#! /usr/bin/perl
use strict;

use Digest::MD5 qw ( md5 );

my $search = pack("H*", "4ae71336e44bf9bf79d2752e234818a5");
my $data = pack("H*", "00" x 16);

print unpack("H*", $data), " <= start\n";

$data = md5($data);
print unpack("H*", $data), "\n"; # => 4ae71336e44bf9bf79d2752e234818a5
                                 #    だけど、最初の一回はノーチェック

while (1) {
    $data = md5($data);
    print unpack("H*", $data), "\n";
    die "Found!!" if ($data eq $search); # 見つけたら終了(死ぬことはないかw
}


これを実行しながら、「はやく Found!! って表示されないかなぁ」と、上へ上へと流れていく数多(あまた)の 16 進数をボーッと眺めていて思ったことは、「このプログラムは終了しないかもしれない」ということ。

どこでループするのか。どこにループするのか。

なぜかというと 4ae71336e44bf9bf79d2752e234818a5 という値が MD5 Ring の内部に存在しているという確証がないから。


たとえば、これは、私が勝手に捏造した実行結果。

00000000000000000000000000000000 <= start
4ae71336e44bf9bf79d2752e234818a5 <= search
398d01fdf7934d1292c263d374778e1a
7995886059df6dd8a1cef4338aa4118e
543a5cb14988ac60d7068649d34434f8
fbfdd8ae19dd5a531f729e8ec0ab66aa
d55f8f0ee335d18a55e983b568a9bb05
33105995009f4b777dbe84d4801e56ae
23841eed0995d10e3b9def2e79a6fcb8
73562a1e09d18bb2ff2b9445c70b4bb8
4892033e755650213415bbd6535a04cb
73562a1e09d18bb2ff2b9445c70b4bb8
4892033e755650213415bbd6535a04cb
73562a1e09d18bb2ff2b9445c70b4bb8
4892033e755650213415bbd6535a04cb
73562a1e09d18bb2ff2b9445c70b4bb8
4892033e755650213415bbd6535a04cb

これの下のほうを見てみると 73562a1e09d18bb2ff2b9445c70b4bb8 という値の md5 ダイジェストが 4892033e755650213415bbd6535a04cb で、4892033e755650213415bbd6535a04cb という値の md5 ダイジェストが 73562a1e09d18bb2ff2b9445c70b4bb8 となっている。たった 2 つの値の間を行ったり来たり、パタパタしているという状態。(=Ring の大きさが 2。)


これは捏造だけれど、実際のデータでもこんな風にならないとは限らない。もしこれが実際に起こっていたとしたら、"<= search" で示している値で待ち続けても、永遠にその値はやってこない。

輪とヒゲ

図にしてみるとこんな感じ。


*2


Ring は存在していても、その Ring の内部のどれかの値で待っていないと、永遠に Ring は見つけられない。Ring が見つかるまでは、その値が Ring の内部にあるのか否かもわからない。


Ring 外部の、ヒゲの部分の長さも分からないし、或る Ring に対してヒゲがいくつ存在するのかもわからない。



こういう場合、どうやって Ring を探せばいいのだろう。

*1:「真だ」と言い切ってしまったけど、「あるバイト列を与えると、md5 関数はバグってフリーズする」っていう可能性もあるか・・・・・・。「あるバイト列を与えると、md5 関数は計算不能エラー(divided by zero)に陥る。そもそも md5 関数の設計自体に不備があったのだ」とか。そういう可能性は無いとは思うけど、べつに確認はしていない。

*2:こういうちょっとした図を描くとき、haiku って便利だな。