浏览量:3644 最近编辑于:2020-11-01 23:47:51
# Img-Stegano
Python字符串图片隐写,每像素占用rgb三通道各一位,需安装OpenCV图像处理库
# Origin
隐写术是一门关于信息隐藏的技巧与科学,所谓信息隐藏指的是不让除预期的接收者之外的任何人知晓信息的传递事件或者信息的内容。隐写术的英文叫做**Steganography**,来源于特里特米乌斯的一本讲述密码学与隐写术的著作《Steganographia》,该书书名源于希腊语,意为“隐秘书写”
# Detail
图片是由一个个像素组成的,每个像素由(r,g,b)3个通道的值表示(png格式图片,多一个alpha透明度值)。单个r、g、b通道可由一个字节(8位表示),其范围在0~255之间。当改变每个通道最低位的值时,对于整个图片来说,肉眼是几乎看不出变化的。因此,可将一组待隐藏字符串转换为二进制格式,再将每个二进制数一位一位地存储在图片的像素中,每个像素可存3bit(rgb各一个bit)的数据。当然也可以每个像素存储2位,这样原图丢失的细节多一点,相关代码可自行修改
* 加密后图片保存的**格式必须是png,不能是jpg**,jpg格式的图片会对图像进行压缩存储,写进去的比特会丢失,原模板图片则无所谓
* 本repo**尚不支持中文**,目前只支持英文字符和ASCII字符,因为每个英文字母的utf-8编码正好可用一个字节(8位)表示,正好对应ASCII编码,方便用Python中chr函数将int类型转为char,后续会升级支持unicode字符的版本
# Implementation
1. 运用Python的OpenCV库,首先调用`imread()`函数读取的要隐写进的图片
2. 读取要进行隐写的字符串`s`,并转为bytes二进制格式
3. 调用`str_encode_to_img()`函数从第一个像素开始,依次将字符串s的二进制数据一位一位地写入像素每个通道的最低位(首先会写入字符串的长度信息)
4. 读取隐写后的图片进行上述运算的逆运算,调用`str_decode_from_img()`函数得到隐写的字符串
# Usage
* `python img_stega.py`
# Requirements
* Python3
* OpenCV
传送门:[字符串图片隐写](https://github.com/ivanwhaf/img-stegano)
#代码
```python
import cv2
def str_encode_to_img(s, img):
# 字符串加密并写进图片
X = img.shape[0] # height
Y = img.shape[1] # width
Z = img.shape[2] # depth
img_usable_bit = X*Y*Z*1 # 图片可用于加密的位数
print('Image usable bit number:', img_usable_bit)
s_bytes = s.encode(encoding='utf-8') # 编码字符串
print('String length:', len(s_bytes))
s_bit_length = len(s_bytes)*8 # 字符串位长度=字符串长度x8
print('String bit length:', s_bit_length)
assert s_bit_length <= img_usable_bit-24 # 前24位用于存放字符串长度信息
s_bit_length_bytes = bin(s_bit_length) # 字符串长度二进制编码对应的字符串 0b0001000
s_bit_length_bytes = s_bit_length_bytes.replace('0b', '') # 去除二进制字符串开头的Ob
print('String bit length bytes:', s_bit_length_bytes)
assert len(s_bit_length_bytes) < 24 # 字符串长度整形对应的二进制位数不得超过24 即3个字节
# 写入字符串长度信息
x, y, z = 0, 0, 0
for i in range(len(s_bit_length_bytes)-1, -1, -1):
bit = s_bit_length_bytes[i]
channel = img[x][y][z]
channel = channel & 0b11111110 # uint8->int32 最后一位置0
if bit == '1':
channel = channel ^ 0b00000001
elif bit == '0':
channel = channel ^ 0b00000000
img[x][y][z] = channel # 修改原图像像素通道值
if z < Z-1:
z = z+1
else:
z = 0
if y < Y-1:
y = y+1
else:
y = 0
if x < X-1:
x = x+1
# 确保前8个像素点中 除去已经写入文件长度信息外 剩下的每一通道的最后1位全为0
while(y < 8):
channel = img[x][y][z]
channel = channel & 0b11111110 # uint8->int32 最后一位置0
img[x][y][z] = channel
if z < Z-1:
z = z+1
else:
z = 0
y = y+1
x, y, z = 0, 8, 0
for s_byte in s_bytes:
s_byte = bin(s_byte).replace('0b', '')
# 不足8位补全8位 如101000
diff = 8-len(s_byte)
s_byte = '0'*diff+s_byte
for bit in s_byte:
channel = img[x][y][z]
channel = channel & 0b11111110 # uint8->int32 最后一位置0
if bit == '1':
channel = channel ^ 0b00000001
elif bit == '0':
channel = channel ^ 0b00000000
img[x][y][z] = channel # 修改原图像的像素通道值
if z < Z-1:
z += 1
else:
z = 0
if y < Y-1:
y += 1
else:
y = 0
if x < X-1:
x += 1
return img
def str_decode_from_img(img):
# 从加密图片中解密出字符串
X = img.shape[0] # height
Y = img.shape[1] # width
Z = img.shape[2] # depth
x, y, z = 0, 0, 0
n = 0
s = ''
# 解码字符串长度 前8个像素点 8*3=24位
while n < 24:
channel = img[x][y][z]
s += bin(channel)[-1] # 提取最后一位
if z < Z-1:
z += 1
else:
z = 0
if y < Y-1:
y += 1
else:
y = 0
if x < X-1:
x += 1
n += 1
s = s[::-1]
length = int(s, 2)
print('Decoded string bit length:', length)
# 解码字符串
s = ''
n = 0
x, y, z = 0, 8, 0 # 从第一行第8个像素点开始
while n < length/8:
n2 = 0
letter = '' # 每8位组成一个字母
while n2 < 8:
channel = img[x][y][z]
letter += bin(channel)[-1] # 提取最后一位
if z < Z-1:
z += 1
else:
z = 0
if y < Y-1:
y += 1
else:
y = 0
if x < X-1:
x += 1
n2 += 1
n += 1
s += chr(int(letter, 2))
return s
if __name__ == "__main__":
img = cv2.imread('raw.png')
s = 'Hello,world!'
print('String unencoded:', s)
img = str_encode_to_img(s, img)
cv2.imwrite('encoded.png', img)
img = cv2.imread('encoded.png')
s = str_decode_from_img(img)
print('Decoded string:', s)
```
评论