OpenSSL と CryptoJS を使って HTML ソースを暗号化してみた

まぁ、タイトル通り。

何の役に立つんでしょうね。まぁ、せっかく調べたので。

概要

目的としては、 HTML ソースを覗かれたときに、 ソースコードが暗号化されていて、 一目では中身を見れないようにしたい。 一方、パスワードを知っている特定の人は見れるようにしたい。

イメージとしては、以下のような流れ。

  1. 挿入する HTML ソース input.html を作成し、暗号化したデータ (CIPHER_HTML と表記) を作成する。
  2. 本文の HTML の JavaScript 変数等に CIPHER_HTML を代入する。
  3. 入力ボックス等を通じてパスワードを読み込み、 CIPHER_HTML を復号化して、本文中に input.html を表示する。

まぁ、お察しの通り、大してセキュアではないので注意です。 あくまで一般人から隠したい程度の目的にしか使えません。 普通は全部サーバー側の処理に任せましょう。 *1

OpenSSL のインストール

まず OpenSSL をインストールする。 こんな記事を読んでる Windows ユーザーなら chocolatey 入れてるよね (ド偏見)。

choco install openssl

挿入したい HTML ソースの作成

じゃあ input.html を用意しよう。 内容は以下のようにしてみる。

<ul>
    <li>目次</li>
    <li>OpenSSL のインストール</li>
    <li>挿入したい HTML ソースの作成
        <ul>
            <li>ul/li だけを使った簡単な HTML</li>
            <li>これが挿入された HTML ができる</li>
        </ul>
    </li>
    <li>OpenSSL で HTML ソースを暗号化
        <ul>
            <li>暗号化</li>
            <li>オプション解説</li>
            <li>復号化テスト</li>
        </ul>
    </li>
    <li>挿入対象の HTML を作成</li>
    <li>Javascript を作成
        <ul>
            <li>CryptoJS を用いて復号</li>
            <li>復号文の HTML への挿入</li>
        </ul>
    </li>
</ul>

え?「Hello World とかもっと簡単なのでいいだろ」?

まぁ、うん、それでもいいです。 敢えて理由付けするなら、複雑に入れ子になってる HTML 文書でもいいっていうことが分かった方が良いかなと。

OpenSSL で HTML ソースを暗号化

暗号化

次は、この input.html を openssl で暗号化する。

openssl enc -e -aes-256-cbc -in input.html -out cipher.txt -pass pass:"password" -base64 -A -md md5

password にはパスワードを入れる。

出力結果は cipher.txt に出力される。

下のような感じ (長すぎるので省略)。

U2FsdGVkX18Fj3ws/[..]YGi5kqUV5499MizKUEk/LuA==

オプション解説

  • -e: エンコードモード。つまり、 openssl enc で暗号化することを明示する。
  • -aes-256-cbc: AAES-256-CBC 方式で暗号化する。正直よく知らないが、後述の復号工程と同じ必要あり。
  • -in <filename>: 入力ファイル名を指定する。
  • -out <filename>: 出力ファイル名を指定する。
  • -pass pass:<password>: パスワードを指定する。
  • -base64: 出力を Base64 形式にする。 (デフォルトはバイナリ形式)
  • -A: -base64 と一緒に使うと、出力が1行になる。 (固定幅で改行しなくなる)
  • -md md5: パスワードから鍵を作る際のダイジェストアルゴリズムを MD5 にする。
    • デフォルトでは SHA256。後述の CryptoJS がこれしか対応していないので指定。

復号化テスト

とりあえず、さきの暗号化文がちゃんと復号できることを、 openssl で確認しておこう。

コマンドは以下。

openssl enc -d -aes-256-cbc -in cipher.txt -out decode.html -pass pass:"password" -base64 -A -md md5

-e が -d になっただけでほぼ同じ。これはデコードモードであることを指定するオプション。

ちゃんとデコードされましたか? (確認)

デコードされましたね? (威圧)

じゃあ次行きます。(横暴) *2

挿入対象の HTML を作成

とりあえず、以下の要素だけ入った HTML を作ろう。

  • パスワード入力ボックス
  • 入力確定用のボタン
  • 入力後に中身を書き換える予定の div ã‚¿ã‚°

ここではこれを index.html と呼ぼう。

サンプルは以下の通り。

<!DOCTYPE html>
<html>

<head>
    <title>暗号化・復号サンプル</title>
</head>

<body>
    <input type="text" id="pass_input" placeholder="パスワードを入力" value="password">
    <button onclick="decrypt()" id="pass_button">復号化</button>
    <div id="output">-</div>

    <!-- 後で この辺に script を追加。 -->

</body>

</html>

これで、以下のようなページが表示されるはずだ。

-

button には onclick=decrypt() で、後で script 内で定義する関数を呼べるようにしておく。 今は何も起きない。

input にはデフォルトで "password" というパスワードが入るようになっている。 本番では消すべき。

input に入っているパスワードが正しければ、 <div id="output">-</div> となっているところに input.html の中身が入る。 というか、そうなるようにしたい。

Javascript を作成

CryptoJS を用いて復号

上述の「この辺に script を追加」とかいう雑なコメントを入れたところに script タグをぶち込んでいく。

具体的には以下のようなコードを書く。

<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>

<script>
    function decrypt() {
        const ciphertext = "U2FsdGVkX18Fj3ws/[..]YGi5kqUV5499MizKUEk/LuA==";
        const key = document.getElementById('pass_input').value;
        const originalText = CryptoJS.AES.decrypt(ciphertext, key).toString(CryptoJS.enc.Utf8);
        document.getElementById('output').innerHTML = originalText;
    }
</script>

ciphertext は、省略しているが、 cipher.txt の文字列をコピペする。 とんでもなく長いと思うが気にせずコピペする。 改行も入れずにコピペする。

CryptoJS を使って ciphertext がデコードされ、 input.html の中身が復元されて originalText に入る。 それが <div id="output">-</div> に挿入されるという形だ。

復号文の HTML への挿入

実際にやってみよう。

index.html に上述の script を入れて、 input に "password" と入力し、 ボタンを押したら、 input.html が挿入される。

以下はそのデモである。

-

最終的な index.html

もう一度、全体をおさらいしておく。

<!DOCTYPE html>
<html>

<head>
    <title>暗号化・復号サンプル</title>
</head>

<body>
    <input type="text" id="pass_input" placeholder="パスワードを入力">
    <button onclick="decrypt()" id="pass_button">復号化</button>
    <div id="output">-</div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>

    <script>
        function decrypt() {
            const ciphertext = "U2FsdGVkX18Fj3ws/[..]YGi5kqUV5499MizKUEk/LuA==";
            const key = document.getElementById('pass_input').value;
            const originalText = CryptoJS.AES.decrypt(ciphertext, key).toString(CryptoJS.enc.Utf8);
            document.getElementById('output').innerHTML = originalText;
        }
    </script>

</body>

</html>

これで、 HTML ソース中には全く本文の文字が入らないようにしつつ、 パスワードを知ってる人だけが中身を表示できる仕組みができあがった。

ただ、これだと、平文に対して常に同じ暗号文が生成される。 多分、分かる人にはすぐクラックされてしまうと思うので要注意。 あくまでお遊び用。

おまけ

今のままだと何回パスワードを入力してもいいようになっている。

折角なので、ボタンを押したら入力フォームもボタンも消えるようにして、 一回しか入力できないようにしておこう。 *3

-

尚、パスワードを間違えた場合の苦情は一切受け付けておりません。

*1:でもまぁ、はてなブログだと HTML しか弄れないので……。

*2:いや、これでデコードできてないとか言われたとしても、 ファイル名間違えてるんじゃないかとかくらいしか言えないので……。

*3:更新するたびに戻るが。