ステガノグラフィで画像に文章を隠蔽してみた(Python)

rotor-cipher-machine Python

png画像の中に文章を隠蔽する方法を思いついたので、実装してみました。
画像が多少劣化しますが、元の画像と比較しなければわからないはずです。

色の情報に別の意味をもたせる

png画像は赤・青・緑の3色の濃さを、それぞれ0~255の数字で決めています。
なので、この数字を特定の数で割った余りで文字列を判別できるようにします。

今回対応させる文字列は、アルファベットの小文字、数字、空白、ピリオド、?の39種類としました。

仮に、赤色(R)に文字を隠蔽する場合、

R = R // 39 * 39

とすることで、Rの数値を39で割り切れる数にします。
そして、文字に応じて数字を足します。

室井

次の項目で表を使って説明するから、わからなくても続きを読んでね。

極端な色の劣化を防ぐ

上記の方法だと、色の数値が最大で元の画像から39もずれてしまいます。
これでは不自然なので、RGBの3色で1文字を表すことにします。

1ピクセルで1文字を表すので、640 x 360の画像なら230,400文字を埋め込むことができます。

対応表
  • Rを3で割った余り
  • Gを4で割った余り
  • Bを5で割った余り

この3つの数字を上の表と照らし合わせて文字を判別します。
Rの余りが0のときは、飛ばして処理することにしました。

文章の終端は!で表しました。
文章を取り出す際に、Rが2、Gが3、Bが4のピクセルで解析を終了します。

文章を埋め込むコード

埋め込む文章を書いたテキストファイルを6行目に、画像を11行目に指定します。

import sys
import os
from PIL import Image

#画像に隠蔽するテキストファイルを開く
f = open('test.txt', 'r')
sys.stdin = f
txt = list(input())

#元画像を開く
img = Image.open("./internet_screenshot_computer.png")
rgba_img = img.convert('RGBA')
size = rgba_img.size
result = Image.new('RGBA',size)
result.paste(rgba_img, (0, 0))

flag = False
cnt = 0
for y in range(size[1]):
    for x in range(size[0]):

        r,g,b,a = rgba_img.getpixel((x,y))
        r = r // 3 * 3
        g = g // 4 * 4
        b = b // 5 * 5

        get_ord = ord(txt[cnt])
        #Rの処理
        # 空白 . ? 数字 a ~ g
        if get_ord == 32 or get_ord == 46 or get_ord == 63 \
            or (get_ord >= 48 and get_ord <= 57) \
            or (get_ord >= 97 and get_ord <= 103):
            r += 1
        # h ~ z or end(!)
        elif (get_ord >= 104 and get_ord <= 122) or get_ord == 33:
            r += 2

        #Gの処理
        # 2 ~ 6 m ~ q
        if (get_ord >= 50 and get_ord <= 54) \
            or (get_ord >= 109 and get_ord <= 113):
            g += 1
        # 7 ~ 9 a ~ b r ~ v
        elif (get_ord >= 55 and get_ord <= 57) \
            or get_ord == 97 or get_ord == 98 \
            or (get_ord >= 114 and get_ord <= 118):
            g += 2
        # c ~ g w ~ z
        elif (get_ord >= 99 and get_ord <= 103) \
            or (get_ord >= 119 and get_ord <= 122) \
            or get_ord == 33:
            g += 3

        #Bの処理
        if r % 3 == 1:
            # .
            if get_ord == 46: b += 1
            # ?
            elif get_ord == 63:b += 2
            # 数字
            elif (get_ord >= 48 and get_ord <= 57):
                b += (get_ord - 45) % 5
            # a ~ g
            elif (get_ord >= 97 and get_ord <= 103):
                b += (get_ord - 94) % 5
        elif r % 3 == 2:
            # h ~ z
            if get_ord >= 104 and get_ord <= 122:
                b += (get_ord - 104) % 5
            # end(!)
            elif  get_ord == 33:
                b += 4

        result.putpixel((x,y),(r,g,b,a))
        #終了処理
        if cnt == len(txt) - 1 or (r % 3 == 2 and g % 4 == 3 and b % 5 == 4):
            flag = True
            break

        if cnt < len(txt) - 1:
            cnt += 1

    if flag:
        break

#画像の出力
result.save( 'result.png' )

指定したテキストファイルの中身は下記の文章です。
once there was a boy. he was-let us say-something like fourteen years old; long and loose-jointed and towheaded. he wasn't good for much, that boy. his chief delight was to eat and sleep; and after that-he liked best to make mischief.!

元画像はいらすとやの画像を使いました。いらすとやの画像このコードを実行すると、result.pngが出力されます。

文章を取り出すコード

上記のコードで出力したファイルを3行目に記述します。

from PIL import Image

img = Image.open("./result.png")
rgba_img = img.convert('RGBA')
size = rgba_img.size

flag = False
cnt = 0
for y in range(size[1]):
    for x in range(size[0]):

        r,g,b,a = rgba_img.getpixel((x,y))
        r = r % 3
        g = g % 4
        b = b % 5
        if r == 0:
            print(" ", end="")
        elif r == 1:
            if g == 0:
                if b == 0:
                    print(" ", end="")
                elif b == 1:
                    print(".", end="")
                elif b == 2:
                    print("?", end="")
                elif b == 3:
                    print("0", end="")
                elif b == 4:
                    print("1", end="")
            if g == 1:
                if b == 0:
                    print("2", end="")
                elif b == 1:
                    print("3", end="")
                elif b == 2:
                    print("4", end="")
                elif b == 3:
                    print("5", end="")
                elif b == 4:
                    print("6", end="")
            if g == 2:
                if b == 0:
                    print("7", end="")
                elif b == 1:
                    print("8", end="")
                elif b == 2:
                    print("9", end="")
                elif b == 3:
                    print("a", end="")
                elif b == 4:
                    print("b", end="")
            if g == 3:
                if b == 0:
                    print("c", end="")
                elif b == 1:
                    print("d", end="")
                elif b == 2:
                    print("e", end="")
                elif b == 3:
                    print("f", end="")
                elif b == 4:
                    print("g", end="")
        elif r == 2:
            if g == 0:
                if b == 0:
                    print("h", end="")
                elif b == 1:
                    print("i", end="")
                elif b == 2:
                    print("j", end="")
                elif b == 3:
                    print("k", end="")
                elif b == 4:
                    print("l", end="")
            if g == 1:
                if b == 0:
                    print("m", end="")
                elif b == 1:
                    print("n", end="")
                elif b == 2:
                    print("o", end="")
                elif b == 3:
                    print("p", end="")
                elif b == 4:
                    print("q", end="")
            if g == 2:
                if b == 0:
                    print("r", end="")
                elif b == 1:
                    print("s", end="")
                elif b == 2:
                    print("t", end="")
                elif b == 3:
                    print("u", end="")
                elif b == 4:
                    print("v", end="")
            if g == 3:
                if b == 0:
                    print("w", end="")
                elif b == 1:
                    print("x", end="")
                elif b == 2:
                    print("y", end="")
                elif b == 3:
                    print("z", end="")
                elif b == 4:
                    pass

        #終了処理
        if r == 2 and g == 3 and b == 4:
            flag = True
            print(" ")
            break
    if flag:
        break

コードを実行すると、文章が表示されます。

出力結果
室井

BASE64エンコードを使えば、大文字、小文字、数字に対応させるだけで日本語対応できそう。