在Part 1中,我有一个简单的LSB隐肌术例子。今天,我将展示另一个简单的步骤如何提高弹性,并使经典的切解工具更难检测。
注意:第1部分中我没有提到的一件事是,这些代码将始终试图维护图像的完整性,而不是可以嵌入的数据的完整性和数量。我假设对手可以访问图像的原始副本。这意味着可以存储的数据量始终低于某些更严重地改变图像的其他算法。
改善LSB隐肌的最简单方法是改变数据的嵌入方式。有一些建议的方法,但就目前而言,让我们使用简单的陆循环来创建像素块,例如JPEG压缩中使用的过程。
from PIL import Image
def encode(filepath):
start = '#####'
stop = '*****'
full = start + 'Some string that you want to encode into an image' + stop
binary_text = ''.join('{0:08b}'.format(ord(x), 'b') for x in full)
print(binary_text, len(binary_text))
with Image.open(filepath) as im:
i = 0
w, h = im.size
# A good block size is 8x8 or a multiple of 8
min_block_size = 24
print(f'Minimum block size: {min_block_size}')
if min_block_size > w or min_block_size > h:
print('Data too large to store in image')
return
for x in range(0, w - min_block_size, min_block_size):
for y in range(0, h - min_block_size, min_block_size):
for j in range(x, x + min_block_size):
for k in range(y, y + min_block_size):
if i >= len(binary_text):
i = 0
bit = binary_text[i]
pixel = im.getpixel((j, k))
if bit == "0":
# Is odd, should be even.
if pixel[0] % 2 != 0:
new_pix = (pixel[0] - 1, pixel[1], pixel[2])
im.putpixel((j, k), new_pix)
else:
# Is even, should be odd.
if pixel[0] % 2 == 0:
new_pix = (pixel[0] + 1, pixel[1], pixel[2])
im.putpixel((j, k), new_pix)
i += 1
im.save(f'encoded_{filepath}')
您可以看到以上几乎与上一个示例相同,唯一的区别是循环的内部循环:
for j in range(x, x + min_block_size):
for k in range(y, y + min_block_size):
if i >= len(binary_text):
i = 0
bit = binary_text[i]
pixel = im.getpixel((j, k))
if bit == "0":
# Is odd, should be even.
if pixel[0] % 2 != 0:
new_pix = (pixel[0] - 1, pixel[1], pixel[2])
im.putpixel((j, k), new_pix)
else:
# Is even, should be odd.
if pixel[0] % 2 == 0:
new_pix = (pixel[0] + 1, pixel[1], pixel[2])
im.putpixel((j, k), new_pix)
i += 1
这将在min_block_size X min_block_size
的正方形上进行迭代,并在此依次编码数据。从理论上讲,这使得编码更加坚固,更难为标准的切解工具提取,因为您必须知道块大小才能检索它。这是这种方法的优势和劣势。您必须定义一个块大小,输入文本的长度或某种方式发送块大小,以便任何人都可以知道要使用什么块大小。
说到解码,此方法基本上是相同的,只是再次使用内部双循环。
def decode(filepath, block_size=None):
start = '#####'
stop = '*****'
found = False
binary_stop = ''.join('{0:08b}'.format(ord(x), 'b') for x in stop)
bit_count = 0
message = ''
with Image.open(filepath) as im:
w, h = im.size
binary_text = ''
# A good block size is 8x8 or a multiple of 8
min_block_size = block_size or 24
while not found:
for x in range(0, w - min_block_size, min_block_size):
for y in range(0, h - min_block_size, min_block_size):
if message.endswith(stop):
found = True
break
for j in range(x, x + min_block_size):
for k in range(y, y + min_block_size):
if bit_count == 8:
char = chr(int(binary_text, 2))
if char in string.printable:
message += char
bit_count = 0
binary_text = ''
pixel = im.getpixel((j, k))
# Since we always want to get the LSB, we
# can just use the result of the modulo as
# our value
binary_text += f'{pixel[0] % 2}'
bit_count += 1
if found:
start_point = message.find(start) + len(start)
end = message.find(stop)
message = message[start_point:end]
return message
您可以看到,这本质上是与编码相同的循环,在本示例中,块大小作为参数传递,已知24作为备份。我还添加了对发现的char的检查,以确保其可打印(对本示例的帮助较小,但稍后我们尝试处理裁剪的图像)。
结论
将两个部分与一个小argparse
一起放在一起,以便于命令行使用,我们得到:
import argparse
import string
from PIL import Image
def encode(filepath):
start = '#####'
stop = '*****'
full = start + 'Some string that you want to encode into an image' + stop
binary_text = ''.join('{0:08b}'.format(ord(x), 'b') for x in full)
print(binary_text, len(binary_text))
with Image.open(filepath) as im:
i = 0
w, h = im.size
# A good block size is 8x8 or a multiple of 8
min_block_size = 24
print(f'Minimum block size: {min_block_size}')
if min_block_size > w or min_block_size > h:
print('Data too large to store in image')
return
for x in range(0, w - min_block_size, min_block_size):
for y in range(0, h - min_block_size, min_block_size):
for j in range(x, x + min_block_size):
for k in range(y, y + min_block_size):
if i >= len(binary_text):
i = 0
bit = binary_text[i]
pixel = im.getpixel((j, k))
if bit == "0":
# Is odd, should be even.
if pixel[0] % 2 != 0:
new_pix = (pixel[0] - 1, pixel[1], pixel[2])
im.putpixel((j, k), new_pix)
else:
# Is even, should be odd.
if pixel[0] % 2 == 0:
new_pix = (pixel[0] + 1, pixel[1], pixel[2])
im.putpixel((j, k), new_pix)
i += 1
im.save(f'encoded_{filepath}')
def decode(filepath, block_size=None):
start = '#####'
stop = '*****'
found = False
binary_stop = ''.join('{0:08b}'.format(ord(x), 'b') for x in stop)
bit_count = 0
message = ''
with Image.open(filepath) as im:
w, h = im.size
binary_text = ''
# A good block size is 8x8 or a multiple of 8
min_block_size = block_size or 24
while not found:
for x in range(0, w - min_block_size, min_block_size):
for y in range(0, h - min_block_size, min_block_size):
if message.endswith(stop):
found = True
break
for j in range(x, x + min_block_size):
for k in range(y, y + min_block_size):
if bit_count == 8:
char = chr(int(binary_text, 2))
if char in string.printable:
message += char
bit_count = 0
binary_text = ''
pixel = im.getpixel((j, k))
# Since we always want to get the LSB, we
# can just use the result of the modulo as
# our value
binary_text += f'{pixel[0] % 2}'
bit_count += 1
if found:
start_point = message.find(start) + len(start)
end = message.find(stop)
message = message[start_point:end]
return message
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-a", "--action", help="encode or decode")
parser.add_argument("-f", "--filepath", help="path to image")
parser.add_argument("-b", "--block_size", required=False, type=int, help="block size")
args = parser.parse_args()
if args.filepath:
if args.action == "encode":
encode(args.filepath)
elif args.action == "decode":
print(decode(args.filepath))
else:
print("Invalid action")
else:
print("No filepath provided")
使用这个非常简单的脚本,您拥有自己的PNG隐化工具。只需确保您安装了枕头,然后从终端运行类似:
python ./advanced.py -a encode -f file.png
然后:
python ./advanced.py -a decode -f encoded_file.png -b 24
您将拥有自己的,秘密编码的消息传递系统。即使是原始的。
,这些变化也无法发现。