原理:对于无损压缩的图片来说,最小值是像素(pixel),每个像素的颜色都是有RGB,三原色组成,颜色的范围就是0 ~ 255,转换成二进制,就是00000000 ~ 11111111。密文可以被拆分成一个个char,并且每个char可以变成二进制。这样利用最低显著位LSB把信息藏到图片中。

我之前写过一个文章,介绍了这类隐写术的原理

隐写术主要分为四步

  • 第一步:读取图片,把每个像素中的RGB值转变成二进制,并保存
  • 第二步:读取密文信息,把信息拆分成char,转变成二进制,并保存
  • 第三步:把第二步得到的数据,利用LSB算法隐藏在第一步的数据中,并保存
  • 第四步:利用第三步得到的数据绘制新的隐写图片

需要import的库有:

import math
import imageio
import matplotlib.pyplot as plt

第一步:读取图片,把每个像素中的RGB值转变成二进制,并保存

path = "test.png"
message = "I am alex" * 15
bits = 3
picture = load_img(path)

# plt.imshow(pic)

# 第一步:读取图片每一个像素的 rgb 并转成二进制,补足八位
img_binary = img_in_binary(picture)

首先,声明load_img() function, 然后利用imageio把图片读进来。

def load_img(path):
    img = imageio.imread(path)
# plt.figure(figsize = (50,50))
    return img

然后,声明img_in_binary() function,把图片每个像素点的RGB保存在list中。 比如[h,w,0] 代表图片中height为h,width为w这点像素的Red数值,1是Green,2是Blue

def img_in_binary(pic):
    height = int(pic.shape[0])
    width = int(pic.shape[1])

    img_rgb_decimal = []

    for h in range(height):
        for w in range(width):
            Red = int(pic[h, w, 0])
            img_rgb_decimal.append(Red)
            Green = int(pic[h, w, 1])
            img_rgb_decimal.append(Green)
            Blue = int(pic[h, w, 2])
            img_rgb_decimal.append(Blue)
    
    img_list = decimal_to_binary(img_rgb_decimal)
    return img_list

由于,读取的RGB值是十进制整数,所以声明了一个decimal_to_binary() function, 把十进制变成二进制。Python中,用bin()这个方法可以转变成二进制,但是结果前面会带有0b两个char,所以我从第三位开始取,就是[2:]。最后声明 fill_up_binary() function把每一个二进制补足八位,以防止后期用多位数的LSB算法藏数据的时候,位数不够的问题。

def fill_up_binary(message):
    if len(message) != 8:
        gap = 8 - len(message)
        for i in range(gap):
            message = "0"+message
    return message

    
def decimal_to_binary(data):
    result = []
    for i in data:
        temp_bin = bin(i)[2:]
        full_bin = fill_up_binary(temp_bin)
        result.append(full_bin)
    return result

第二步:读取密文信息,把信息拆分成char,转变成二进制,并保存

声明 message_to_binary() function, 把密文信息转变成二进制,然后补足八位。同样会调用fill_up_binary() function. 每个char补足8位是为了提取文字的时候可以提取出来,不然位数不统一,最后无法提取密文。

def message_to_binary(message):
    result=""
    for i in message:
        temp_asc = ord(i)
        temp_bin = bin(temp_asc)[2:]
        full_bin = fill_up_binary(temp_bin)
        result = result + full_bin
    return result

# 第二步:把信息变成二进制补足八位
message_binary = message_to_binary(message)

第三步:把第二步得到的数据,利用LSB算法存在第一步的数据中,并保存

LSB核心的数据隐藏就在这步。声明了exchange() function,把密文藏进图片,计算出stego图片的新的RGB值,存到list中,以便于之后的stego图片的生成。

其中,考虑到多位数LSB算法的问题,比如LSB3的话,每个颜色信道会交换最后3位的数据,但是这样的话,最后一个颜色只有最后2位的数据可以交换,因为一个char,8为,3位在R,3位在G, 最后只有2位在B。所以要对于这种 信息长度%算法bits != 0 的情况,单独的做最后一个颜色信道的考虑。

exchange_count 是为了计算一共要多个颜色信道,要用来隐藏数据。

list储存exchange_count位的颜色隐藏信息后,并把原来图片中剩余的颜色RGB值补充进来,生成一个完整的stego图片RGB值。

def exchange(bits,data,img):
    list = []
    
    exchange_count = math.ceil(len(data)/bits)
    
    for i in range(exchange_count):
        index = i *bits
        rest = len(data) % bits
        if len(data) == rest:
            temp_str = img[i][0:-rest] + data[0:]
        else:
            temp_str = img[i][0:-bits]+ data[0:bits]
            data= data[bits:]
        list.append(temp_str)
    
    if len(list) != len(img):
        for i in range(len(list),len(img)):
            list.append(img[i])
        
    return list

第四步:利用第三步得到的数据生成新的隐写图片

最后一步,声明generate_stego_img() function, 把第三步保存的RGB值,分别覆写原图的每个像素的每个RGB中,最终可以生成新的stego图片。 注意的是,由于第三步储存的结果是二进制,所以这里声明了binary_to_decimal() function把二进制变成十进制。

def binary_to_decimal(stego_binary):
    result = []
    for i in stego_binary:
        result.append(int(i,2))
    return result

def generate_stego_img(stego_list,pic):
    height = int(pic.shape[0])
    width = int(pic.shape[1])
    
    for h in range(height):
        for w in range(width):
            pic[h, w, 0] = stego_list[0]
            pic[h, w, 1] = stego_list[1]
            pic[h, w, 2] = stego_list[2]
            stego_list = stego_list[3:]
    return pic

# 第四步:把新的 rgb 值放到图片里,生成新的图片
stego_img = generate_stego_img(stego_decimal,picture)

plt.imshow(stego_img)