PNG向けのLSB方式のsteganographyを書いた。

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方式という難しそうなのもあるので、それのコーディングにもチャレンジしたい。