スキップしてメイン コンテンツに移動

Python: 簡単ステガノグラフィ…テキストを画像に埋め込む

Pythonでなんちゃってステガノグラフィを作ってみた。画像データにテキストを埋め込んで、メッセージを隠してしまうというわけだ。お遊びで適当に作っただけなので、本格的な使用に耐えうるものではないことをお断りしておく。

上に示す画像は何も埋め込まれていないもの。下に示す画像にはPython: PaSoRiでSuicaの履歴を読み出すの全文が埋め込まれている。

これなら変更したことを誰にも気づかれないだろう。使用したソース(covgraph.py)は下に示す。使い方は、

covgraph.py cover.png stego.png input.txt

とする。cover.pngにinput.txtの中身を埋め込み、stego.pngとして出力する。input.txtに代えて直接文字列を指定することもできる。

covgraph.py cover.png stego.png "hello, world"

とすれば、stego.pngには"hello, world"が埋め込まれることになる。

埋め込まれた画像からテキストを取り出す場合は、

covgraph.py stego.png

もしくは、

covgraph.py stego.png output.txt

とする。前者は標準出力、後者はファイルに書き出す。

追記: 今回のソースは書き殴りで汚いしアルゴリズムとしてもよくないと思うので、修正版のPython: 続・簡単ステガノグラフィ…画像を画像に埋め込むの方がましだと思う。

#!/usr/bin/env python # -*- coding: utf-8 -*- """covgraph.py by nox, 2008.4.15""" import sys, os, bz2, base64 from PIL import Image def cover(input_image, output_image, text_data): """テキストデータを画像ファイルに埋め込む. input_image: 入力画像ファイル. output_image: 出力画像ファイル(PNG等、アルファ画像が扱えること). text_data: 埋め込むテキストもしくはテキストファイル. """ if os.path.isfile(text_data): text = file(text_data).read() else: text = text_data bz2_text = bz2.compress(text) b16_text = base64.b16encode(bz2_text) im = Image.open(input_image).convert("RGB").convert("RGBA") data = list(im.getdata()) if len(data) < len(b16_text) * 4: print >>sys.stderr, "Error: text too large." sys.exit(1) for i in range(len(b16_text)): for j in range(4): n = i * 4 + j data[n] = (data[n][0], data[n][1], data[n][2], 255 - (int(b16_text[i], 16) >> j & 1)) data[n+1] = (data[n+1][0], data[n+1][1], data[n+1][2], 253) im.putdata(data) im.save(output_image) def uncover(image_file): """画像ファイルからテキストデータを抜き出す. image_file: テキストが埋め込まれた画像ファイル. 戻り値: テキストデータ. """ im = Image.open(image_file) data = list(im.getdata()) end = [d[3] for d in data].index(253) data = [hex((255 - a[3]) + ((255 - b[3]) << 1) + ((255 - c[3]) << 2) + ((255 - d[3]) << 3)).upper()[-1] for a, b, c, d in zip(data[:end:4], data[1:end:4], data[2:end:4], data[3:end:4])] b16_text = "".join(data) bz2_text = base64.b16decode(b16_text) text = bz2.decompress(bz2_text) return text def main(args): if len(args) < 2 or len(args) > 4: print >>sys.stderr, u"画像ファイルにテキストを埋め込む:" print >>sys.stderr, u"Usage: %s <input image> <output image> <text or text-file>" % os.path.basename(args[0]) print >>sys.stderr, u"input image: 入力画像ファイル" print >>sys.stderr, u"output image: 出力画像ファイル(PNG等、アルファ画像が扱えること)" print >>sys.stderr, u"text: 埋め込む文字列" print >>sys.stderr, u"text-file: 埋め込むテキストファイル" print >>sys.stderr print >>sys.stderr, u"画像ファイルからテキストを抜き出す:" print >>sys.stderr, u"Usage: %s <input image>" % os.path.basename(args[0]) print >>sys.stderr, u"Usage: %s <input image> <output text-file>" % os.path.basename(args[0]) print >>sys.stderr, u"input image: 入力画像ファイル" print >>sys.stderr, u"output text-file: テキストの出力ファイル" sys.exit(1) # 画像ファイルにテキストを埋め込む. if len(args) == 4: cover(args[1], args[2], args[3]) # テキストが埋め込まれた画像からテキストを取り出す. elif len(args) == 3: # テキストをファイルに書き出す. file(args[2], "w").write(uncover(args[1])) elif len(args) == 2: # テキストを標準出力に書き出す. print uncover(args[1]) if __name__ == "__main__": main(sys.argv)

コメント