画像でファイルを暗号化してみよう

昨今、金融情報や個人情報など、機密重要データがWeb上でやりとりされるようになり、暗号化の重要性は増している。そして、さまざまな手法で暗号化処理が行われている。今回は、自作の暗号化ツールを作ってデータ暗号化の雰囲気を楽しんでみよう。自作ならではのアイデアとして、画像ファイルを利用して暗号化するツールを作ってみよう。

  • 画像を用いてテキストを暗号化したところ

    画像を用いてテキストを暗号化したところ

最強の暗号手法 - 使い捨てパッドについて

非常に簡単な暗号化手法でありながら、解読が難しい手法に「使い捨てパッド(One Time Pad)」がある。これは、暗号化対象のファイルと同じ長さの乱数表を用いて暗号を行う。暗号化の方法は、非常に簡単で、元データと乱数表をXOR演算を用いて計算を行う。

例えば、「test」というデータを、乱数表「1234」で暗号化してみよう。PythonでビットXOR演算を行うには、「a ^ b」のように記述する。最初に、Pythonの実行環境Colaboratoryを用いてテストしてみよう。

a = 'test'    # データ
b = '1234' # 乱数表 
# 暗号化
c = []
for aa, bb in zip(a, b):
  cc = chr(ord(aa) ^ ord(bb))
  c += [cc]
print("".join(c))  

このプログラムを実行してみよう。すると、暗号文として「EW@@」という値が得られた。ここで、ord()が文字から数値に変換する関数で、chr()が数値から文字に変換する関数だ。文字同士はXOR演算できないので、ord()とchr()を使うことで、文字と数値を変換している。

  • 使い捨てパッドの手法で暗号化したところ

    使い捨てパッドの手法で暗号化したところ

そして、先ほどと同じ乱数表「1234」さえあれば、作成した暗号文「EW@@」を解読(復号)することができる。それでは、復号してみよう。暗号化の時と同じように、3行ほどのプログラムで復号できる。

c = "EW@@"
b = "1234"
# 復号化
a = []
for bb, cc in zip(b, c):
  aa = chr(ord(bb) ^ ord(cc))
  a += [aa]
print("".join(a))

このプログラムを実行すると、正しく元のデータ「test」が得られることが分かるだろう。

  • 乱数表を用いて暗号文を復号化したところ

    乱数表を用いて暗号文を復号化したところ

このように、XOR演算を使うと、乱数表さえあれば、元のデータを暗号化したり、復号化したりすることができる。これは、XOR演算の便利な性質だ。つまり、値Pに対して、値Kを二度XOR演算すると、元の値Pと等しくなる。

(P ^ K) ^ K = P

ただし、この「使い捨てパッド」を効果的に使うには、いくつか注意点がある。まず、元のデータと乱数表が同じサイズであることが必要だ。これは、元データを漏れなくXORする必要があるためだ。また、同じ乱数表を使って異なるデータを暗号化するなら、それが乱数表を解読する手がかりとなってしまう。「使い捨てパッド」という名前の通り、一回の暗号化につき、一つの乱数表を用いることで、この暗号化は最大の効果を発揮する。

画像ファイルを乱数表として使うアイデア

とは言え、乱数表をどのように用意したら良いだろうか。もちろん、乱数表を適当な乱数を利用して作成することもできる。しかし、乱数表に画像ファイルを用いるのはどうだろうか。最近のスマートフォンで撮影した画像であれば、それなりのサイズもあり乱数表として申し分ない。調べてみると、画像をキーファイルとして利用して、データを暗号化するソフトもいくつかある。それでは、これに倣って、画像ファイルを用いてファイルを暗号化するツールを作ってみよう。

ただし、JPEG画像には、固定文字列を持つヘッダ部分があり、そのまま乱数表として使うなら、固定ヘッダ部分のデータが容易に読み取られてしまう可能性がある。そこで、さらにもう一工夫して、ここでは、独自の擬似乱数を生成し、それを加えることで、データを暗号化してみよう。

#!/usr/bin/env python3
# コマンドラインから渡されたファイルを得る --- (*1)
import sys
if len(sys.argv) < 3:
    print("USAGES:")
    print("env.py targetfile keyfile")
    exit
target_file = sys.argv[1]
key_file = sys.argv[2]
out_file = target_file.replace('.txt', '') + ".out.txt"

# ファイルをバイナリモードで開く --- (*2)
out_fd = open(out_file, "wb")
target = open(target_file, "rb").read()
key = open(key_file, "rb").read()

# 独自の乱数生成関数 --- (*3)
rx, ry, rz, rw = (15424343, 949583, 991234, 11223344)
def randint(n):
    global rx, ry, rz, rw
    t = (rx ^ (rx << 11))
    rx, ry, rz = ry, rz, rw
    rw = rw = (rw ^ (rw >> 19)) ^ (t ^ (t >> 8))
    return rw % n

# 一バイトずつXOR処理 --- (*4)
res = bytearray()
for i, a in enumerate(target):
    b = key[i % len(key)]
    r = randint(256)
    c = a ^ b ^ r
    res.append(c)

# 結果を書き込む --- (*5)
out_fd.write(res)

まずは、上記のファイルを「enc.py」という名前をつけて保存しよう。そして、暗号化したいテキストファイル(test.txt)と、暗号化に使うキーファイル(image.jpg)を用意しよう。

そして、コマンドライン(WindowsならPowerShell、macOSならターミナル.app)を起動して、以下のようにコマンドを入力して実行してみよう。

Windows版

 python enc.py test.txt image.jpg

Mac版

 python3 enc.py test.txt image.jpg

すると、test.txtの内容が暗号化され、test.out.txtというファイルが生成される。内容を確認すると、次のように、暗号化されていることが確認できるだろう。

  • 暗号化したところ

    暗号化したところ

そして、この暗号化されたファイルを、復号化するには、同じキーファイルを用いて、以下のようにコマンドを実行しよう。

Windows版

python enc.py test.out.txt image.jpg

Mac版

python3 enc.py test.out.txt image.jpg

コマンドを実行すると、test.out.out.txtというファイルが生成される。このファイルを確認すると、内容が復号化され、元のファイルと全く同じ内容であることが確認できるだろう。

使い方が分かったところでプログラムを確認してみよう。プログラムの(*1)の部分では、コマンドラインから渡されたファイル名を取得する。Pythonでコマンドライン引数を得るには、sys.argvを確認すれば良い。そして、(*2)の部分では、ファイルをバイナリモードで開く。対象ファイルとキーファイルについては、開いたついでに、全内容を読み取るようにしている。

プログラムの(*3)では、独自の乱数生成関数を定義している。変数rx, ry, rz, rwにそれぞれ適当な初期値を与えているので、この値を変更すれば、異なる暗号データが生成されるようになる。

プログラムの(*4)では、一バイトずつXOR演算を行う。ここでは、元データ(a)とキーファイルの内容(b)と疑似乱数(c)の三つをXORしている。そして、XOR演算を行った結果を(*5)でファイルに書き出す。

まとめ

今回、比較的単純なXOR演算を使った暗号化の手法を紹介した。なお、何の外部ライブラリも使わず書いたわずか35行のプログラムだが、キーファイルがない状態で、この元データを復元するのは至難の業だろう。もちろん、実用面で安全に暗号を使うには、専用の暗号化ツールを使うことだ。しかし、実際に、自分の手で暗号化ツールを作るなら、暗号化に対する理解を深めることができるだろう。本稿を参考にして、オリジナルの暗号化ツールを考案してみるのも面白いだろう。