【PHP】本の文章で暗号化する「書籍暗号」をつくってみた

さてさて、このブログでは主に「私が過去に経験した内容や手順」をまとめ直した内容を書いています。

ただ、せっかくブログを書くのだからそれだけでは面白くないと思いましたので、たまにですが「自由研究」的な内容も公開してきました。

そして、今回もその中に入ると思いますが、内容としては、

『書籍暗号』をPHPで実装する 

です。

書籍暗号とは、元々は子供の頃によく観ていた「世界ふしぎ発見」の中で紹介されていた暗号なのですが、どういうものかというと、

「ある特定の本を使うことを事前に決めておき、そのテキストの中にある「何ページの何文字目を見ろ」という数字が暗号の文章になる

というものです。

つまり、特定の本を持っていないと暗号は絶対に解けないという仕組みになっているわけです。(やっぱりこういうものを考える人って頭いいですよね)

そして、この仕組みを使ってPHPでも同様のものをつくってみることにしました。

ぜひ、今回は「読みもの」的なカンジでみていただけると嬉しいです😊✨

開発環境: PHP 7.2

準備する

先ほども言いましたように、書籍暗号には通信する2者が共通の「本」を持っている必要があります。

そのため、今回は著作権が切れた名作をインターネット上で公開してくれている青空文庫こちらのページから『吾輩は猫である』のテキストをダウンロードしました。

※ もしご自身で試してみたい人はどんな本でもOKですが、文字のパターンが少ないと暗号化出来ない文字が多くなるので書籍暗号には向きません。この理由から『注文の多い料理店』は文字数が少なく、あまりうまくいきませんでした。

専用クラスをつくる

では、今回のメインになる書籍暗号を実装するクラスをつくっていきます。

本のテキストをセットする部分

まずは、暗号/復号どちらにも必要になってくる「本の文章をセットする」部分をつくります。

<?php

namespace App;

class BookEncryption {

    private $book_text = '';

    public function setBookText($text) {

        $this->book_text = $text;

    }

}

とはいっても、セットしたテキストをメンバ変数に格納するだけのシンプルなものです。

暗号部分

続いては暗号化する部分です。

class BookEncryption {

    // 省略

    public function encrypt($text) {

        $encrypted_codes = [];
        $characters = mb_str_split($text);

        foreach($characters as $character) {

            $positions = $this->getCharacterPositions($character);

            if(count($positions) > 0) { // 文章中に該当文字が存在している場合

                $index = array_rand($positions);
                $encrypted_codes[] = $positions[$index];

            } else {    // 存在していない場合(文字はそのまま)

                $encrypted_codes[] = $character;

            }

        }

        return implode(' ', $encrypted_codes);

    }

    private function getCharacterPositions($character) {

        $positions = [];
        $tmp_book_text = $this->book_text;

        while($current_position = mb_strrpos($tmp_book_text, $character)) {

            array_push($positions, $current_position);
            $tmp_book_text = substr($tmp_book_text, 0, $current_position);

        }

        return $positions;

    }

    // 省略

}

まずencrypt()では、暗号化したいテキストをmb_str_split()で一文字ずつに分解してループしていきます。

そのループの中で、getCharacterPositions()を使って「その文字が本($book_text)中で現れる全ての位置(何文字目かが分かる数字)」を配列で取得します。

最後に、その配列の中からランダムでひとつだけ位置を取得し、それらを半角スペースで繋げて暗号文をつくります。

つまり、暗号文は以下のようなものになります。

377216 367633 1793 83886 268909 (...続く)

※ なお、本の中に該当する文字がない場合はそのままの文字が使われることになります。そのため、珍しい漢字をつかってしまうと暗号化出来ない部分がでてくることになりますが、その場合でもひらがなを使えば問題ないでしょう。

復号部分をつくる

では、最後に暗号化された文章を復号する部分です。

class BookEncryption {

    // 省略

    public function decrypt($text) {

        $decrypted_character = '';
        $encrypted_codes = explode(' ', $text);

        foreach($encrypted_codes as $code) {

            if(is_numeric($code)) {

                $decrypted_character .= mb_substr($this->book_text, $code, 1);

            } else {

                $decrypted_character .= $code;

            }

        }

        return $decrypted_character;

    }

}

暗号文は、「●文字目を見ろ」という意味になりますから、半角スペースで分割した数字を1つずつループしていき、その数字が示す場所にある文字をピックアップしていくことになります。

使い方

では、実際の使い方です。

$encrypting_text = '月末は渋谷で15時半に待ち合わせしましょう。';

$book_path = public_path('/texts/wagahaiwa_nekodearu.txt');
$book_text = mb_convert_encoding(
    file_get_contents($book_path), 'utf8', 'sjis-win'   // 青空文庫はShift_JISなのでUTF-8へ変換
);

$be = new \App\BookEncryption();
$be->setBookText($book_text);

// 暗号化
$encrypted_text = $be->encrypt($encrypting_text);

// 復号
$decrypted_text = $be->decrypt($encrypted_text);

まずBookEncryptionのインスタンスを作って本のテキストをセットします。

後は、encrypt()decrypt()を使うだけで暗号化/復号が完了します。

お疲れ様でした!

※ ちなみに「吾輩は猫である」には「駅」という文字が使われていないので暗号化できませんでしたが、文字数が多いので暗号化成功率は高いようでした。

おわりに

ということで、今回は気分を変えて自由研究的な記事をお届けしました。

ちなみに書籍暗号は今でこそ「アイデア・テクニック」的に聞こえるかもしれませんが、どうやら第二次世界大戦の中でスパイが実際に使っていたそうです。

今の時代では「SSL」や「BlowFish」、「AES」など多くの暗号技術がありますが、当時としては画期的だったのかもしれませんね。

ぜひ皆さんも映画の中に登場するスパイになった気分で書籍暗号を試してみてくださいね。

ではでは!

この記事が役立ちましたらシェアお願いします😊✨