2023年3月23日(木)〜25日(土)に技術カンファレンス「PHPerKaigi 2023」が練馬区立区民・産業プラザ Coconeriホールおよびオンラインにて開催されました。GMOインターネットグループはダイヤモンドスポンサーとして「成瀬の挑戦状」という企画を実施。セッションでは、GMOインターネットグループメンバーよる挑戦の様子を出題者である成瀬允宣が実況・解説しました。 改めてこの記事では解説をお送りします。
概要
『成瀬の挑戦状』の概要に関しては、次の企画ページを参照ください。
https://developers.gmo.jp/event/phperkaigi2023
第一関門の解説
『成瀬の挑戦状』は次の地図から始まります。
この地図を解読するところがまず一つ目の関門です。
地図では nrslib/naruse-no-chosenjo-2023 という地名(?)にマーキングされています。徳川埋蔵金よろしく、どうやらこの地にお宝が眠っていると予測できます。
次に地図上の島の形に目を向けます。くじらのようなこの形は Docker のロゴマークを模しています。docker といえば Docker Hub に思い至る方は多いでしょう。Docker Hub で nrslib/naruse-no-chosenjo-2023 にアクセスするとコンテナイメージが公開されています。
https://hub.docker.com/repository/docker/nrslib/naruse-no-chosenjo-2023/general
nrslib/naruse-no-chosenjo-2023 起動してみましょう(M1 Mac などの場合は platform を指定する必要があります)。
docker container run -it --rm -p 80:80 --name naruse-no-chosenjo-2023 nrslib/naruse-no-chosenjo-2023
Docker コンテナを立ち上げると PHP が立ち上がるので、localhost にアクセスしてみると挑戦ページにたどり着きます。
挑戦サイト(glhf は Good luck have fun のスラング)
Challenge にリンクがあるのでクリックするとひとつ目の問題にたどり着きます。
ひとつ目の問題
第1の問題
ひとつ目の問題は「MXQJYIJXUDQCUEVJXYISYFXUH」を解読するというものです。
明らかに暗号文ではありますが、この暗号がどんな暗号なのかがわかりません。そこでもう一度地図に目を向けます。
地図の地名に目を向けると、「Caesal」「Gaius」「Iulius」の地名があります。これらの地名を入れ替えると、Gaius Iulius Caesar にでき、ガイウス・ユリウス・カエサルという人物を示唆しています。
カエサルは「賽は投げられた」や「ブルータスお前もか」といった引用文で有名な人物です。彼は暗号を使っていたと伝えられています。その暗号がシーザー暗号(カエサル暗号)と言われています。したがって、第一問目の暗号はカエサル暗号によるものと分かります。
シーザー暗号は換字式暗号です。換字式暗号とはある文字をまた別の文字などに変換する暗号を指します。
『2001年宇宙の旅』では HAL という AI が登場します。HAL は IBM の換字をした名称です。IBM の I をひとつ手前のアルファベットにして H、B をひとつ手前のアルファベットにして A、M をひとつ手前のアルファベットにすると L。IBM の先を行くという意味の込められたネーミングです。
シーザー暗号はまさに HAL と同じ方式の換字による暗号で、たとえば NRSLIB を3文字分ずらすと QUVOLE になります。復号する場合には QUVOLE を3文字分手前にずらします。
カエサル暗号について理解したところで、問題文の「MXQJYIJXUDQCUEVJXYISYFXUH」に取り組みましょう。
カエサル暗号であることは分かったものの、何文字ずらせばよいかわかりません。とはいえアルファベットは26文字しかないので、25回試行すれば結果にたどり着けると予測できます。そこでひとつの選択肢としてプログラムを作成してみましょう。
<?php
const ALPHABETS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for($i = 1; $i < count($argv); $i++) {
$cipher = strtoupper($argv[$i]);
echo "====================" . PHP_EOL;
echo "TARGET:" . $cipher . PHP_EOL;
echo "--------------------" . PHP_EOL;
enumerate_candidates($cipher);
echo "====================" . PHP_EOL;
echo PHP_EOL;
}
function shift(string $cipher, int $diff): string {
$cipher_length = strlen($cipher);
$plain = "";
for ($i = 0; $i < $cipher_length; $i++) {
$next = strpos(ALPHABETS, $cipher[$i]) + $diff;
if ($next >= 26) {
$next -= 26;
}
$plain .= ALPHABETS[$next];
}
return $plain;
}
function enumerate_candidates(string $cipher) {
for ($i = 1; $i < 26; $i++) {
$plain = shift($cipher, $i);
echo str_pad($i, 2, 0, STR_PAD_LEFT) . "|" . $plain . PHP_EOL;
}
}
このプログラムは25パターンのシフトを網羅して表示するプログラムです。実行すると次の結果を得られます。
====================
TARGET:MXQJYIJXUDQCUEVJXYISYFXUH
--------------------
01|NYRKZJKYVERDVFWKYZJTZGYVI
02|OZSLAKLZWFSEWGXLZAKUAHZWJ
03|PATMBLMAXGTFXHYMABLVBIAXK
04|QBUNCMNBYHUGYIZNBCMWCJBYL
05|RCVODNOCZIVHZJAOCDNXDKCZM
06|SDWPEOPDAJWIAKBPDEOYELDAN
07|TEXQFPQEBKXJBLCQEFPZFMEBO
08|UFYRGQRFCLYKCMDRFGQAGNFCP
09|VGZSHRSGDMZLDNESGHRBHOGDQ
10|WHATISTHENAMEOFTHISCIPHER
11|XIBUJTUIFOBNFPGUIJTDJQIFS
12|YJCVKUVJGPCOGQHVJKUEKRJGT
13|ZKDWLVWKHQDPHRIWKLVFLSKHU
14|ALEXMWXLIREQISJXLMWGMTLIV
15|BMFYNXYMJSFRJTKYMNXHNUMJW
16|CNGZOYZNKTGSKULZNOYIOVNKX
17|DOHAPZAOLUHTLVMAOPZJPWOLY
18|EPIBQABPMVIUMWNBPQAKQXPMZ
19|FQJCRBCQNWJVNXOCQRBLRYQNA
20|GRKDSCDROXKWOYPDRSCMSZROB
21|HSLETDESPYLXPZQESTDNTASPC
22|ITMFUEFTQZMYQARFTUEOUBTQD
23|JUNGVFGURANZRBSGUVFPVCURE
24|KVOHWGHVSBOASCTHVWGQWDVSF
25|LWPIXHIWTCPBTDUIWXHRXEWTG
====================
結果の中で10文字ずらした結果の最初の文字を読み取ると「WHAT」になっていて、文章になっていそうです。空白を単語の間に入れると「WHAT IS THE NAME OF THIS CIPHER」となり、和訳すると「この暗号の名前は何ですか」となり、ひとつ目の問題の回答は「シーザー暗号」または「カエサル暗号」になります。
第2の問題
ひとつ目の問題に回答し終えたら、ふたつ目の問題に取り組みたいところですが、次の問題へのリンクがありません。
そこでひとつ目の問題が first.php であることからアタリをつけて、second.php にアクセスすると該当ページは存在しません。
second.php は存在しない
そこで今回の挑戦状サイトにある URL を網羅してみます。もっとも手軽な方法は、コンテナでファイルを一覧化することでしょう。
$ docker container exec -it naruse-no-chosenjo-2023 bash
# ls
BHJDIO.php FXFUHSXQBBUDWUYDVE.php KZXUK.php V20xc2RWbFhkejA9.php YKIUTJ.php
DHIBYR.php JABZNSLACRQR.php MYCOZHKDBKF.php VTDIABLQFKZQK.php first.php
EVNNZ.php JBJYL.php OIWRXPRAU.php VTDKD.php fourth.php
FRPBAQUVAG.php JMTNFN.php TMFAESTAQNAGKZXUFVODR.php VTDKDYGUUO.php index.php
ファイルの中でかろうじて読み取れるのは first.php と fourth.php、index.php で、そのほかのファイル名は暗号化されているようにみえます。試しに第1の問題で使った復号方法で、暗号化されたファイル名を復号してみると、いくつかのファイル名は復号ができます。
====================
TARGET:BHJDIO
--------------------
01|CIKEJP
02|DJLFKQ
03|EKMGLR
04|FLNHMS
05|GMOINT
06|HNPJOU
07|IOQKPV
08|JPRLQW
09|KQSMRX
10|LRTNSY
11|MSUOTZ
12|NTVPUA
13|OUWQVB
14|PVXRWC
15|QWYSXD
16|RXZTYE
17|SYAUZF
18|TZBVAG
19|UACWBH
20|VBDXCI
21|WCEYDJ
22|XDFZEK
23|YEGAFL
24|ZFHBGM
25|AGICHN
====================
この結果は最初の BHJDIO.php を復号した結果です。5文字シフトした結果を見ると「GMOINT」とそれらしい単語が得られていることがわかります。
おそらく、次の問題は SECOND.php であることから、6文字の暗号化されたファイルにアタリをつけてカエサル暗号で復号すると、YKIUTJ.php が SECOND.php の換字であると分かります。したがって、第2問は YKIUTJ.php の内容です。
YKIUTJ.php
問題は「CTOZUGZTSTMAKATZTWYOVGXZKZUK」を解読することです。暗号文の見た目から換字式暗号であるように見えますが、鍵というキーワードがあるあたり、カエサル暗号ではないようです。
そこでその他のファイルにカエサル暗号の復号をしてみると、FRPBAQUVAG.php が SECONDHINT.php になります。ふたつ目の問題のヒントになってそうです。FRPBAQUVAG.php を見てみましょう。
なんのことかわからないのでカエサル暗号で復号すると、次の文章になります。
Cipher conceived in the late fifteenth and late sixteenth centuries.
The second cipher is a relative of the first.
この文章は「15世紀後半から16世紀後半に考案された暗号で、第1の暗号の親戚である」という意味になります。
換字式暗号のうち、第1の暗号であるカエサル暗号と関係しているものを調べると、ヴィジュネル暗号というものが見つかります。
ヴィジュネル暗号は次のようなヴィジュネル方陣と呼ばれる表を利用します。
|ABCDEFGHIJKLMNOPQRSTUVWXYZ
-+--------------------------
A|ABCDEFGHIJKLMNOPQRSTUVWXYZ
B|BCDEFGHIJKLMNOPQRSTUVWXYZA
C|CDEFGHIJKLMNOPQRSTUVWXYZAB
D|DEFGHIJKLMNOPQRSTUVWXYZABC
E|EFGHIJKLMNOPQRSTUVWXYZABCD
F|FGHIJKLMNOPQRSTUVWXYZABCDE
G|GHIJKLMNOPQRSTUVWXYZABCDEF
H|HIJKLMNOPQRSTUVWXYZABCDEFG
I|IJKLMNOPQRSTUVWXYZABCDEFGH
J|JKLMNOPQRSTUVWXYZABCDEFGHI
K|KLMNOPQRSTUVWXYZABCDEFGHIJ
L|LMNOPQRSTUVWXYZABCDEFGHIJK
M|MNOPQRSTUVWXYZABCDEFGHIJKL
N|NOPQRSTUVWXYZABCDEFGHIJKLM
O|OPQRSTUVWXYZABCDEFGHIJKLMN
P|PQRSTUVWXYZABCDEFGHIJKLMNO
Q|QRSTUVWXYZABCDEFGHIJKLMNOP
R|RSTUVWXYZABCDEFGHIJKLMNOPQ
S|STUVWXYZABCDEFGHIJKLMNOPQR
T|TUVWXYZABCDEFGHIJKLMNOPQRS
U|UVWXYZABCDEFGHIJKLMNOPQRST
V|VWXYZABCDEFGHIJKLMNOPQRSTU
W|WXYZABCDEFGHIJKLMNOPQRSTUV
X|XYZABCDEFGHIJKLMNOPQRSTUVW
Y|YZABCDEFGHIJKLMNOPQRSTUVWX
Z|ZABCDEFGHIJKLMNOPQRSTUVWXY
ヴィジュネル方陣はアルファベットを並べたものです。1行目はAから26文字、2行目はBから26文字と1文字ずつずらしたアルファベットの並びです。
ヴィジュネル暗号はヴィジュネル方陣と暗号文以外に鍵を使います。たとえば「IWILLBLOG」という文章を「NRSLIB」の鍵で暗号化する手順は次の図のとおりです。
ヴィジュネル暗号の暗号化手順
暗号化したい平文と鍵と方陣を①のように並べ、②のように平文に鍵を重ねます。もし平文より鍵が短かった場合は、文字列長が同じになるまで鍵を繰り返します。
次に平文の文字に着目して、ヴィジュネル方陣の行を特定します。この図の場合は暗号化対象の平文の1文字目 I なので I の行です。
行を特定したら、次は列です。平文に並んだ鍵の文字を使って、列を特定します。④がそれを実行していて、I に対する鍵は N と分かります。
その結果、平文「I」に対し、鍵の「N」を合わせて、暗号の「V」を得ます。あとはその繰り返しをしていって、⑤のように「IWILLBLOG」を鍵「NRSLIB」で暗号化すると「VNAWTCYFY」の暗号文が得られます。
暗号文を復号したいときは、この手順を逆に進めていきます。それでは問題に立ち返りましょう。
ヴィジュネル暗号を解読するためには鍵を特定する必要があります。幸い問題文に鍵が提示されています。今回の鍵は3文字ということで「GMO」になります。
ヴィジュネル方陣にしたがって、「CTOZUGZTSTMAKATZTWYOVGXZKZUK」を解いていきましょう。鍵は「GMO」ですので、まずは一文字目は G 列を見ます。G 列にある C を見つけたら、その行が何かを見てみると、W 行であるとわかります。したがって、平文の最初の文字は W です。
ヴィジュネル方陣を使って、暗号文の1文字目を解く
この手法で復号していくのですが、1文字ずつ復号するのは手間なのでプログラムを作りましょう。
<?php
const ALPHABETS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$token = strtoupper($argv[1]);
$key = strtoupper($argv[2]);
show_matrix();
echo "===========" . PHP_EOL;
$plain = decrypt($token, $key);
echo "Cipher: $token Plain: $plain";
function show_matrix()
{
echo " |" . ALPHABETS . PHP_EOL;
echo "-+--------------------------" . PHP_EOL;
for ($i = 0; $i < 26; $i++) {
$row = caesar(ALPHABETS, $i);
echo ALPHABETS[$i] . "|" . $row . PHP_EOL;
}
}
function caesar(string $cipher, int $diff): string {
$cipher_length = strlen($cipher);
$plain = "";
for ($i = 0; $i < $cipher_length; $i++) {
$next = strpos(ALPHABETS, $cipher[$i]) + $diff;
if ($next >= 26) {
$next -= 26;
}
$plain .= ALPHABETS[$next];
}
return $plain;
}
function decrypt(string $cipher, string $key): string {
$plain = "";
for($i = 0; $i < strlen($cipher); $i++) {
$key_pos = $i % strlen($key);
$key_char = $key[$key_pos];
$column_nr = strpos(ALPHABETS, $key_char);
$column = caesar(ALPHABETS, $column_nr);
$cipher_char = $cipher[$i];
$row_nr = strpos($column, $cipher_char);
$plain .= ALPHABETS[$row_nr];
}
return $plain;
}
急ごしらえのプログラムですが、このプログラムは第一引数に暗号文、第二引数に鍵を渡すと、ヴィジュネル暗号の復号化を行います(ついでにヴィジュネル方陣も出力します)。decrypt 関数が復号化本体で、caesar 関数はカエサル暗号のように文字をシフトする関数です。
プログラムを使って「CTOZUGZTSTMAKATZTWYOVGXZKZUK」を復号すると「WHATISTHENAMEOFTHISCHALLENGE」の文字列が得られます。読みやすいように空白を入れると「WHAT IS THE NAME OF THIS CHALLENGE」となり、第2の問題の回答は「成瀬の挑戦状」であるとわかります。
第3の問題
第3問を得るには、第2問を得るときと同じように直前の暗号と同じ方式で復号化します。
first.php, second.php ときているので、次はおそらく third.php とアタリを付けてヴィジュネル暗号で復号化を試みると、対象となるファイルは5文字であると予想できますが、それでは第3問が見つかりません。
ひとつずつファイル名を復号していくと、TMFAESTAQNAGKZXUFVODR.php が NARUSENOCHOSENJOTHIRD.php であると分かり、第3問にたどり着きます。5文字を頼りに見つけられないようにされていたわけです。
第3問
第3問は「VFhwVmRVNXFWVEpPUkVFMVRFUkZlazlUTkRKUFZHdDZUbFJSUFE9PQ==」を解読します。
ヒントはありませんが、末尾に=が付与されていることから base64 であると予想する開発者は多いでしょう。試しに base64 で復号すると「TXpVdU5qVTJOREE1TERFek9TNDJPVGt6TlRRPQ==」という文字列が得られます。満足した結果は得られませんでしたが、またも末尾が=です。
暗号文を base64 で復号すると base64 の文字列が得られることから、どうやら暗号文はストレッチングされているようです。ストレッチングとはパスワードのハッシュ値を推測されないようにするために計算を何度もすることです。
今回のケースの場合、base64 でストレッチングされていることは予測できるものの、肝心のストレッチングの回数が分かりません。手動で復号を繰り返してもよいのですが、面倒です。base64 による復号を繰り返すプログラムで平文を手に入れるよう試みてみましょう。
<?php
$data = $argv[1];
$decoded = $data;
$nr = 10;
for($i = 0; $i < $nr; $i++) {
$decoded = base64_decode($decoded);
echo $decoded . PHP_EOL;
}
プログラムはとりあえず10回、base64 による復号を実施します。「VFhwVmRVNXFWVEpPUkVFMVRFUkZlazlUTkRKUFZHdDZUbFJSUFE9PQ==」をプログラムにかけてみると、次の結果が得られます。
TXpVdU5qVTJOREE1TERFek9TNDJPVGt6TlRRPQ==
MzUuNjU2NDA5LDEzOS42OTkzNTQ=
35.656409,139.699354
ߞ��=�z���
3回復号したときにそれらしい文字列が手に入ります。数字をカンマでつないだものであり、日本人からすると見たことありそうな35と130台の数字であることから、どうやら緯度と経度であると推測できます。
「35.656409,139.699354」を検索すると渋谷区のセルリアンタワーを指していると分かり、第3問の答えは「GMOインターネットグループ」ないしは「セルリアンタワー」等、それに準ずるものとなります。
最終問題
いよいよ最後の問題です。これまでと同じように最終問題を直前の復号方法で探してみましょう。
base64 の文字列に見えるものは「V20xc2RWbFhkejA9.php」です。base64 で3回復号してみましょう。
Wm1sdVlXdz0=
ZmluYWw=
final
~)�
final という文字列が得られることから、最終問題であることは間違いなさそうです。
最終問題
最終問題には2つの暗号文があります。後者はハッシュ値のようですが、現段階だと解読する手立てがありません。前者の「VVZWR1dGZFZPVWRTVld4RQ==」に取り組みましょう。
末尾が=ということから base64 で「VVZWR1dGZFZPVWRTVld4RQ==」を復号すると次の文字列が得られます。
UVVGWFdVOUdSVWxE
QUFXWU9GRUlD
AAWYOFEIC
�8
�
これまでの経緯から3つ目の文字列が平文であることが予測できますが、意味不明な文字列です。そこで、これまでの復号を順番にかけていきます。
まずは「GMO」を鍵にしてヴィジュネル暗号の復号化をします。
Cipher: AAWYOFEIC Plain: UOISCRYWO
平文は「UOISCRYWO」のようです。最後のしあげにカエサル暗号の復号化を実施してみます。
====================
TARGET:UOISCRYWO
--------------------
01|VPJTDSZXP
02|WQKUETAYQ
03|XRLVFUBZR
04|YSMWGVCAS
05|ZTNXHWDBT
06|AUOYIXECU
07|BVPZJYFDV
08|CWQAKZGEW
09|DXRBLAHFX
10|EYSCMBIGY
11|FZTDNCJHZ
12|GAUEODKIA
13|HBVFPELJB
14|ICWGQFMKC
15|JDXHRGNLD
16|KEYISHOME
17|LFZJTIPNF
18|MGAKUJQOG
19|NHBLVKRPH
20|OICMWLSQI
21|PJDNXMTRJ
22|QKEOYNUSK
23|RLFPZOVTL
24|SMGQAPWUM
25|TNHRBQXVN
====================
それらしい文字列としては、16番目の結果が「KEYISHOME」となっていて、空白をいれると「KEY IS HOME」になりそうです。
鍵はホームにあるとのことでコンテナイメージのホームディレクトリを参照してみます。
$ docker container exec -it naruse-no-chosenjo-2023 bash
:/var/www/html# cd ~
:~# ls
SKSHUFKDOOHQJH XMWTNCCFOHXS
なにやらファイルが見つかりました。どうやら暗号化されているようなので「XMWTNCCFOHXS」をヴィジュネル暗号(鍵:GMO)で復号してみます。
Cipher: XMWTNCCFOHXS Plain: RAINBOWTABLE
このファイルはレインボーテーブルであると分かります。レインボーテーブルとは、あらかじめ平文候補からハッシュ値を計算しておき、テーブルとして保持することで、総当たり攻撃を効率化するためのデータです。
最終問題のハッシュ値でレインボーテーブルを検索してみましょう。
:~# cat XMWTNCCFOHXS | grep be49dff024b9745dfcff21a11ac419be8e3ff906c403b0bd9156281119cfc4d9
CTOZ,be49dff024b9745dfcff21a11ac419be8e3ff906c403b0bd9156281119cfc4d9
どうやらひとつ目のハッシュ値は「CTOZ」であると分かります。どうやら今回のハッシュ値はこのレインボーテーブルから検索することで、元の文章が得られるようです。
すべてのハッシュ値をレインボーテーブルから検索してみます。
:~# array=(be49dff024b9745dfcff21a11ac419be8e3ff906c403b0bd9156281119cfc4d9 7c82b7123b94faf8741492a0a4b84c6fefe2ba9e564175fe349f0a65bc9d1235 978f5b19f6d215c7c7e9ec7712893f74cf692dc340f171a2c493f3e5cd3ced28 5f9e267134b8a2d5f87789e897319ea537064464a0d8f853c36b50249e19ab47 9a8bf3ebbe6f826e39772c4763bea0722b18c0e4b5b7ba2fe6813f02f7f0a06a 2fbde56b8d02e6ca6c55832c683d05b90129d75c6f1d8c7a575f8a7fc98a5afb 978f5b19f6d215c7c7e9ec7712893f74cf692dc340f171a2c493f3e5cd3ced28 8d27e107ab90615cb8613543cd957ad32bc2752d9aa8beb0421e41385376a858 f89e51947e77e8446ad87f4d0eb3c4fa344dda814a1153442a763e40810ea573 810137bcb1c8d2d8656bc3f554b7f8e88b0753918d975497e2b2c19e3dbfc06c)
:~# for elem in ${array[@]}; do grep "$elem" XMWTNCCFOHXS; done
CTOZ,be49dff024b9745dfcff21a11ac419be8e3ff906c403b0bd9156281119cfc4d9
OE,7c82b7123b94faf8741492a0a4b84c6fefe2ba9e564175fe349f0a65bc9d1235
ZTS,978f5b19f6d215c7c7e9ec7712893f74cf692dc340f171a2c493f3e5cd3ced28
GZGCQF,5f9e267134b8a2d5f87789e897319ea537064464a0d8f853c36b50249e19ab47
ZA,9a8bf3ebbe6f826e39772c4763bea0722b18c0e4b5b7ba2fe6813f02f7f0a06a
RUTK,2fbde56b8d02e6ca6c55832c683d05b90129d75c6f1d8c7a575f8a7fc98a5afb
ZTS,978f5b19f6d215c7c7e9ec7712893f74cf692dc340f171a2c493f3e5cd3ced28
AZWBQFYQ,8d27e107ab90615cb8613543cd957ad32bc2752d9aa8beb0421e41385376a858
GZR,f89e51947e77e8446ad87f4d0eb3c4fa344dda814a1153442a763e40810ea573
KHSXKHNUBM,810137bcb1c8d2d8656bc3f554b7f8e88b0753918d975497e2b2c19e3dbfc06c
結果をみると「CTOZ OE ZTS GZGCQF ZA RUTK ZTS AZWBQFYQ GZR KHSXKHNUBM」であるとわかります。どうやら暗号化されてるようで、復号を試みてみましょう。
base64 ではなさそうなので、ヴィジュネル暗号(鍵:GMO)で1単語ずつ復号してみると次のようになります。
what is the answer to life the universe and everything
日本語訳をすると「生命、宇宙、そして万物についての究極の疑問の答えは何か」となります。まったく答えが分からない方が大多数でしょう。
問題文を Google 検索などでそのまま検索すると「42」という結果が得られます。
生命、宇宙、そして万物についての究極の疑問の答え
「生命、宇宙、そして万物についての究極の疑問の答え」というのは SF 小説の『銀河ヒッチハイクガイド』に出てくるスーパーコンピュータに同様の問いを計算させた結果、750万年かけて42という答えを出した、というのが元ネタになっています。
最終問題の答えは「42」でした。
まとめ
常日頃、カンファレンスでリアル脱出ゲームをやってみたいなと考えていたのが、今回 PHPerKaigi 2023 で機会をもらえたので実現できました。
だれも全問正解者は出ないと考えて作った問題でしたが、思ったよりも正解者は多かったようです。
次回があれば、もう少しレベルをあげて実施してみたいと思います。
そのほか小ネタや秘話
「成瀬の挑戦状」にはいくつか小ネタが織り交ぜられています。また企画開発時にいくつか秘話めいたものもありました。ここで少し紹介します。
第1の問題のヒントがなさすぎた
暗号といえばまずカエサル暗号、と思っていたのですが、案外そうでもなかったようです。
今回の企画にはグループメンバーに「成瀬の挑戦状」に挑戦してもらい、それを成瀬が実況するというスポンサーセッションも含まれていました。そのとき、メンバーに提示した挑戦状は次の図でした。
挑戦状プロトタイプ
本番の挑戦状との違いは地名がないことです。したがって、メンバーは第一の暗号がカエサル暗号であることを示唆されずに取り組むことになったのです。
結果として、第一の暗号のとっかかりにかなり時間を使ってしまう現象が発生したため、本番では地名に「カエサル」を示す名称を入れ込むことになりました。
第2問のフェイク
第2問は SECOND.php を暗号化した YKIUTJ.php ですが、それをひとつだけ用意したのでは復号せずとも第2問にたどり着いてしまいます。
そこで、それらしいフェイクを用意することにしました。ここでそれらを紹介していきます。
BHJDIO.php
BHJDIO はカエサル暗号で GMOINT になります。
本文を解読すると「do you want to be a hacker」という質問文になるので、解答するとすれば「yes」または「no」などになるでしょう。
DHIBYR.php
DHIBYR は NRSLIB になります。
本文にある暗号文を解読すると「What programming language do you use」となり、好きなプログラミング言語を答えてもらうことになります。
JBJYL.php
JBJYL は PHPER です。
暗号文は「what is the name of this sponsor」となり、想定解答は「GMOインターネットグループ」などです。
JMTNFN.php
JMTNFN は LOVPHP です。SECOND と誤解するように6文字にしています。
暗号文は「what is the name of this cipher」となり、「ヴィジュネル暗号」が答えになります。
第3問のフェイク
第2問と同じく第3問にもフェイクを用意しました。その紹介をします。
VTDKDYGUUO.php
VTDKDYGUUO はヴィジュネル暗号で復号すると PHPerKaigi になります。
問題文の暗号を解くと得られる緯経度は PHPerKaigi の会場のココネリホールを指しています。
KZXUK.php
KZXUK は enjoy に復号できます。
問題文の暗号は PHPerKaigi のタイムテーブルの URL です(https://fortee.jp/phperkaigi-2023/timetable)。
VTDIABLQFKZQK.php
VTDIABLQFKZQK は PHPConference に復号されます。
本文にある暗号を復号すると PHP Conference の会場として利用されている大田区産業プラザ Pio の緯経度が得られます。
MYCOZHKDBKF.php
MYCOZHKDBKF は GMOInternet に復号できます。
暗号文は GMO インターネットグループ第2本社のフクラスを指す緯経度です。
OIWRXPRAU.php
OIWRXPRAU は iwillblog です。
これを復号した方はブログを書くことになります。本文を復号すると PHPerKaigi のブログ投稿 URL に誘導されます(https://fortee.jp/phperkaigi-2023/blog-link)。
VTDKD.php
VTDKD は PHPer に復号できる暗号です。
本文から得られる緯経度はカナダを指しています。PHP の開発者であるラスマス・ラードフ氏がカナダ出身であることに由来します。
JABZNSLACRQR.php
JABZNSLACRQR は dont be fooled に復号でき、「騙されないで」という意味になります。
本文を解読すると「fourchphpislie」という警告が手に入ります。「fourth.php is lie」ということで、fourth.php が最終問題でないことが示唆されています。
Portal というゲームにでてくる「the cake is a lie」のオマージュです。
第4問へのこだわり
すでに分かるとおり、第4問はフェイクです。
挑戦者は回答フォームの回答欄が4つであることから問題が4問あることがわかります。したがって fourth.php が正しい4問目のようにみえますが、実はフォームに仕掛けがあります。
回答フォーム
回答フォームでは4つ目の回答欄が「最終問題」になっています。final.php が最終問題であることを示しているのでした。
レインボーテーブルの順序
通常データはなにがしかのデータの昇順なり、降順なりで並べることが多いです。しかし、今回の挑戦の性質上、データをきれいに並べるのはあまりうまくないです。
そこでレインボーテーブルは実は単純なアルファベット順ではなく、適当なところで2分割して交互にデータをいれています。次がレインボーテーブルの元データです。
abroad,2547a15eb550aa0c858dc8dbde1f3b3c501a1a4fc0901226b5c8da3f8187e52c
tea,3261bc082fc2a4a36d7fa504e50ad38932f501d701cefac34288889c107c103e
absent,19919262b1b6a6b31b34ac418311dc243378beaec8ab6377cf59ac43066c7c5c
teach,f4ec850f0f312110165852a3f723b5e886ea24273dc1936b9053ff4dc900f5a6
accident,f35a77bebe402810ddbe88a9f09c19226e09910ada6cc3b35403b2e15e57fe2e
team,0ace4d858467a5e58f41c3e1cb63d2fd243bf03bbe955e88bc4ccd14934fe845
add,20b5b52c9957f32530b8c986dc1b782aa9fae3753e4cd2d285c2a0bb5cbf07b4
tell,dc47a3faae0814c7f0582d048258a589e919ef2fab1c6e60f6428bca8cd82f33
: