LSB基本原理
LSB隐写LSB全称least significant bit,最低有效位PNG文件中的图像像素一般是由RGB三原色(红绿蓝)组成,每一种颜色占用8位,取值范围为0x00~0xFF,即有256种颜色,一共包含了256的3次方的颜色,即16777216 种颜色人类的眼睛可以区分约1000万种不同的颜色这意味着人类的眼睛无法区分余下的颜色大约有6777216种LSB隐写就是修改RGB颜色分量的最低二进制位(LSB),而人类的眼睛不会注意到这前后的变化,所以可以利用这一特性进行信息隐藏
载体图片选择
图片选择 png图片是一种无损压缩的位图片形格式,也只有在无损压缩或者无压缩的图片(BMP)上实现lsb隐写。如果图像是jpg图片的话,就没法使用lsb隐写了,原因是jpg图片对像数进行了有损压缩,我们修改的信息就可能会在压缩的过程中被破坏。而png图片虽然也有压缩,但却是无损压缩,这样我们修改的信息也就能得到正确的表达,不至于丢失。BMP的图片也是一样的,是没有经过压缩的。BMP图片一般是特别的大的,因为BMP把所有的像数都按原样储存,没有进行压缩。
LSB隐写示例
例如想把字符’a’隐藏起来,可以将其转换成16进制的数字0x61,然后转换成二进制的01100001,将其隐藏到红色通道中每一个颜色的最低位
我的实现思路
隐藏秘密信息:
1.对秘密信息编码,转换为由01组合而成的字符串
2.把编码后的生成的01字符串写入图片R通道的最低位
提取秘密信息:
1.提取R通道的最低位,还原出01字符串
2.根据编码规则由01字符串还原出秘密信息
具体编码规则
**编码(**没有使用ASCII码)
//密钥数组,里面保存a-z,A-Z,还有标点符号们,某个字符的index就是那个字符对应的编码,改变字符在数组中的位置就会实现加密,这个数组相当于一个密钥
QString _keyArray;
index:0123456789…
_keyArray=“abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ,.!?”;
abc编码为:000000000001000010,前8位用来存字符个数abc是3
实际存储的是00000011000000000001000010
解码
根据前8bit 00000011记录的字符个数3,计算出存储的字符串长度3*6,提取出:000000000001000010 也就是000000 000001 000010
对应:abc
index:0123456789…
_keyArray=“abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ,.!?”;
安全性(只有使用与编码时用的_KeyArray才可以正确解码)
改变_keyArray如下, 则000000000010000011就会解码为def
_keyArray=“defabcghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQ
RSTUVWXYZ ,.!?”;
_KeyArray相当于一个对称密钥
项目构成![在这里插入图片描述](https://img-blog.csdnimg.cn/20200521144840611.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0Fsc244Ng==,size_16,color_FFFFFF,t_70)
需要放在运行目录的载体图片
程序运行
startwindow
mainwindow
lsbdecode
源代码
startwindow.h
#ifndef STARTWINDOW_H
#define STARTWINDOW_H
#include "mainwindow.h"
#include "lsbdecode.h"
#include <QMainWindow>
#include <QString>
#include <QFileDialog>
namespace Ui {
class startwindow;
}
class startwindow : public QMainWindow
{
Q_OBJECT
public:
explicit startwindow(QWidget *parent = 0);
~startwindow();
private slots:
void on_ptn_gotocreate_clicked();
void on_ptn_gotodecode_clicked();
private:
Ui::startwindow *ui;
lsbdecode *_LSB;
MainWindow *_MWD;
};
#endif // STARTWINDOW_H
startwindow.cpp
#include "startwindow.h"
#include "ui_startwindow.h"
startwindow::startwindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::startwindow)
{
ui->setupUi(this);
}
startwindow::~startwindow()
{
delete this->_LSB;
delete this->_MWD;
delete ui;
}
void startwindow::on_ptn_gotocreate_clicked()
{
this->_MWD=new MainWindow;
_MWD->show();
}
void startwindow::on_ptn_gotodecode_clicked()
{
this->_LSB=new lsbdecode;
_LSB->show();
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QImage>
#include <QPixmap>
#include <QDebug>
#include <QColor>
#include <QRgb>
#include <QString>
#include <QTime>
#include <QMessageBox>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
void WritePicture(QString keystring);//把明文编码后生成的01字符串(_KeyString)写入图片
~MainWindow();
private slots:
void on_ptb_create_clicked();
private:
Ui::MainWindow *ui;
QImage _srcimg;
QImage _oldimg;
QImage _newimg;
QPixmap _mypixmap;
QString _imgAddr;//图片地址
//密钥数组,里面保存a-z,A-Z,还有标点符号们,某个字符的index就是那个字符对应的编码,改变字符在数组中的位置就会实现加密,这个数组相当于一个密钥
QString _keyArray;
QString _keyString;//编码后的总密文
};
#endif // MAINWGEINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//初始化密钥数组26+26+‘ ’+‘,’+'.'+'?'+'!'=57,一个字符需要6位二进制位表示,比如'c'的编码就是2(000010),可以通过打乱数组里面的顺序来加强安全性
_keyArray="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ,.!?";
_imgAddr="bigear.bmp";
if(!_srcimg.load(_imgAddr))
{
qDebug()<<"图片加载失败";
}
//相片统一为578*578,本程序只使用rbg中的r通道最低的一位存储秘密信息,6(每个字符的二进制位)*95=570,570+8=578;
//也就是本程序设置为最大存储量为95个字符
_srcimg=_srcimg.scaled(ui->lbl_img->width(),ui->lbl_img->height());
_oldimg=_srcimg;
_newimg=_srcimg;
ui->lbl_img->setPixmap(QPixmap::fromImage(_srcimg));
}
//把明文编码后生成的01字符串(_KeyString)写入图片的第一行的Rgb的R通道的最低位
void MainWindow:: WritePicture(QString keystring)
{
//这里其实应该验证一下keystring的长度
if(keystring.size()>_oldimg.width())//写不下了,程序只给了一行的r通道用来写
{
QMessageBox::information(NULL,"错误","本程序只支持存储95个字符",QMessageBox::Cancel);
}
else
{
for(int i=0;i<keystring.size();i++)//把每一位写入图片
{
int R,G,B,newR;
QColor pixelcolor=_oldimg.pixel(i,0);//写一行,横着写
R=pixelcolor.red();
G=pixelcolor.green();
B=pixelcolor.blue();
//改变r的最后一位无非也就是改变最后一位为0还是1
if(R%2==0)//这一点的R本身是偶数(最后一位是0)
{
if(keystring[i]=='1')//要写1
newR=R+1;
if(keystring[i]=='0')//要写0
newR=R;
}
else//R本身是奇数(最后一位是1)
{
if(keystring[i]=='1')//要写1
newR=R;
if(keystring[i]=='0')//要写0
newR=R-1;
}
_oldimg.setPixelColor(i,0,QColor(newR,G,B));
}
}
}
void MainWindow::on_ptb_create_clicked()//对明文进行编码和并且写入图片,并且保存图片
{
QString rawString=ui->ldt_key->text();
qDebug()<<"明文是:"<<rawString;
_keyString="";//保存的是编码后的密文
qDebug()<<"rawString:"<<rawString;
qDebug()<<"_keyArray:"<<_keyArray;
for(int i=0;i<rawString.size();i++)//为每一个明文编码
{
for(int index=0;index<_keyArray.size();index++)//根据keyArray编码
{
if(rawString[i]==_keyArray[index])//找到字符对应的下标index,index就是该字符的编码
{
QString temp=QString::number(index,2);//把编码转化为二进制
switch (temp.size()) //不足6位的补足6bit,方便切割
{
case 1:temp="00000"+temp;
break;
case 2:temp="0000"+temp;
break;
case 3:temp="000"+temp;
break;
case 4:temp="00"+temp;
break;
case 5:temp="0"+temp;
break;
default:
break;
}
_keyString=_keyString+temp;
break;//编码下一个字符
}
}
}
//记录字符数目,添加在_KeyString的开头
QString charallcount=QString::number(_keyString.size()/6,2);//转换为二进制字符串
switch (charallcount.size()) //用头8位代表明文的字符数,以便于提取密文时确定密文在图片的哪里结束
{
case 1:charallcount="0000000"+charallcount;
break;
case 2:charallcount="000000"+charallcount;
break;
case 3:charallcount="00000"+charallcount;
break;
case 4:charallcount="0000"+charallcount;
break;
case 5:charallcount="000"+charallcount;
break;
case 6:charallcount="00"+charallcount;
break;
case 7:charallcount="0"+charallcount;
break;
default:
break;
}
qDebug()<<"字符数目:"<<charallcount<<endl;
qDebug()<<"密文:"<<_keyString;
_keyString=charallcount+_keyString;//字符个数(8bit)+编码后的字符
qDebug()<<"字符数目+密文:"<<_keyString;
WritePicture(_keyString);//把_keyString写入图片
if(_oldimg.save("newbigear.bmp"))
{
QMessageBox::information(NULL,QString("提示"),QString("秘密图片已经保存到运行路径下"),QMessageBox::Ok);
}
else
{
QMessageBox::information(NULL,QString("提示"),QString("保存失败"),QMessageBox::Ok);
}
}
MainWindow::~MainWindow()
{
delete ui;
}
lsbdecode.h
#ifndef LSBDECODE_H
#define LSBDECODE_H
#include <QMainWindow>
#include <QImage>
#include <QPixmap>
#include <QDebug>
#include <QColor>
#include <QRgb>
#include <QString>
#include <QTime>
#include <QMessageBox>
#include <cmath>
#include <QByteArray>
#include <QFileDialog>
namespace Ui {
class lsbdecode;
}
class lsbdecode : public QMainWindow
{
Q_OBJECT
public:
explicit lsbdecode(QWidget *parent = 0);
QString Decode(QImage newimg);//从图片中提取密文01序列
QString getraw(QString rawbyte);//根据密文01序列,依据_keyArray转换出原文
~lsbdecode();
private:
Ui::lsbdecode *ui;
QImage _newimg;
QString _imgAddr;//图片地址,public是为了startwindow可以选择要破解的图片
QPixmap _mypixmap;
//QString _imgAddr;//图片地址
//密钥数组,里面保存a-z,A-Z,还有标点符号们,某个字符的index就是那个字符对应的编码,改变字符在数组中的位置就会实现加密,这个数组相当于一个密钥
QString _keyArray;
QString _rawbyte;//编码后的总密文
QString _raw;//还原出来的原文
};
#endif // LSBDECODE_H
lsbdecode.cpp
#include "lsbdecode.h"
#include "ui_lsbdecode.h"
lsbdecode::lsbdecode(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::lsbdecode)
{
ui->setupUi(this);
//初始化密钥数组26+26+‘ ’+‘,’+'.'+'?'+'!'=57,一个字符需要6位二进制位表示,比如'c'的编码就是3,可以通过打乱数组里面的顺序来加强安全性
_keyArray="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ,.!?";
QString pictureaddr=QFileDialog::getOpenFileName(this,"选择图片","C:\\","*.png *.bmp");
//_imgAddr="newbigear.bmp";//存储着秘密的图片
_imgAddr=pictureaddr;
if(!_newimg.load(_imgAddr))
{
qDebug()<<"图片加载失败";
}
//相片统一为578*578,本程序只使用rbg中的r通道最低的一位存储秘密信息,6(每个字符的二进制位)*95=570,570+8=578;
//也就是本程序设置为最大存储量为95个字符
_newimg=_newimg.scaled(ui->lbl_decodeimg->width(),ui->lbl_decodeimg->height());
ui->lbl_decodeimg->setPixmap(QPixmap::fromImage(_newimg));
_rawbyte=lsbdecode::Decode(_newimg);
qDebug()<<"密文:"<<_rawbyte;
_raw=lsbdecode::getraw(_rawbyte);
qDebug()<<"提取出的内容:"<<_raw;
ui->ldt_result->setText(_raw);
}
QString lsbdecode::Decode(QImage newimg)
{
QString charallcount="";//取出前8个像素点的RGB的R的最后一位,得到密文总字符数,密文长度=密文总字符数*6
for(int i=0;i<8;i++)
{
QColor color=newimg.pixel(i,0);
int R=color.red();
if(R%2==0)
charallcount=charallcount+"0";
else
charallcount=charallcount+"1";
}
qDebug()<<"前八位R8bit:"<<charallcount;
int allcount=0;
for(int i=0;i<8;i++)//得到密文的字符数
{
if(charallcount[i]=='1')
allcount=allcount+pow(2,7-i);
}
QString raw="";//保存提取出来的原文
qDebug()<<"总字符数"<<allcount;
for(int start=8;start<8+allcount*6;start++)//每个字符6位,提取出密文串
{
QColor color=newimg.pixel(start,0);
int R=color.red();
if(R%2==1)
raw=raw+"1";
else
raw=raw+"0";
}
return raw;
}
QString lsbdecode::getraw(QString rawbyte)//根据密文01序列,依据_keyArray转换出原文
{
QString raw="";//最终的还原结果
int count=rawbyte.size()/6;//总字符个数
QString *charbytecode=new QString[count];
int nowcount=0;//当前已经截取出来的个数
while(nowcount!=count)//直到全部截取出来
{
charbytecode[nowcount]=rawbyte.mid(nowcount*6,6);//从nowcount往后切割6个,也就是一个字符的编码
nowcount++;
}
for(int i=0;i<count;i++)
{
qDebug()<<charbytecode[i];
}
int *indexArray=new int[count];
for(int i=0;i<count;i++)
{ indexArray[i]=0;
QString tempsix=charbytecode[i];
for(int j=0;j<6;j++)
{
if(tempsix[j]=='1')
indexArray[i]=indexArray[i]+pow(2,5-j);
}
}
for(int i=0;i<count;i++)
{
raw=raw+_keyArray[indexArray[i]];
}
return raw;
}
lsbdecode::~lsbdecode()
{
delete ui;
}
main.cpp
#include "mainwindow.h"
#include "lsbdecode.h"
#include "startwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
/*MainWindow w;
w.show();
lsbdecode l;
l.show();*/
startwindow s;
s.show();
return a.exec();
}