LSB方式とは

LSBとはleast significant bitの略で、末尾のビットのことである。
LSB方式のsteganographyとはLSBを書き換えることで、あまり見た目に影響させずにデータを隠すものである。

コード

Python2、3両対応。RGBまたはRGBAなPNGにのみ対応。Pillowという画像処理ライブラリを使っている

#!/usr/bin/env python
# coding: UTF-8

import binascii
from PIL import Image 

def stegano(source_path, message): 
    
    convert LSB steganography
    
    img = Image.open(source_path)

    if img.mode in (RGBA):
        message_bit = str("{0:b}".format(int(binascii.hexlify(message.encode(UTF-8)), 16))) + 11111111111111110
        old_data = img.getdata()
        new_data = []
        counter = 0

        for i, item in enumerate(old_data): # item is (r, g, b). int in tuple
            if counter < len(message_bit):
                if i % 2 == 0:
                    new_red = int(str("{0:b}".format(item[0]))[:-1] + message_bit[counter], 2)
                    item = (new_red, item[1], item[2])
                    counter += 1

                elif i % 5 == 0:
                    new_green = int(str("{0:b}".format(item[1]))[:-1] + message_bit[counter], 2)
                    item = (item[0], new_green, item[2])
                    counter += 1

                elif i % 11 == 0:
                    pass

                else:
                    new_blue = int(str("{0:b}".format(item[2]))[:-1] + message_bit[counter], 2)
                    item = (item[0], item[1], new_blue)
                    counter += 1

            new_data.append(item)


        img.putdata(new_data) # change old_data to new_data
        img.save(source_path, "PNG")
        print(Completed!)

    else:
        print(failed)


def unstegano(source_path):
    
    extract str
    
    img = Image.open(source_path)

    if img.mode in (RGBA):
        datas = img.getdata()
        binary = 

        for i, item in enumerate(datas): # item is (r, g, b)
            if binary[-17:] != 11111111111111110:
                if i % 2 == 0:
                    binary += str("{0:b}".format(item[0]))[-1] # red

                elif i % 5 == 0:
                    binary += str("{0:b}".format(item[1]))[-1] # green

                elif i % 11 == 0:
                    pass

                else:
                    binary += str("{0:b}".format(item[2]))[-1] # blue

            else:
                break

        print(binascii.unhexlify(str("{0:x}".format(int(binary[:-17], 2)))).decode(UTF-8))

    else:
        print(failed)

if __name__ == __main__:
    stegano(./test.png, kyapikyapisitai)
    unstegano(./test.png)

感想

bin()とかhex()を使うと先頭に0bや0xが、長くなると末尾にLがついてしまうが、
.format()で変換することでつかなくなるという知見が得られた。
あと、当たり前だけどデータ埋め込む際のアルゴリズムは、プログラムによって違うし、steganographyの検出は難しそうだなあと思った。
また、LSB方式の他にBPCS方式という難しそうなのもあるので、それのコーディングにもチャレンジしたい。