该项目已上传至GitHub:链接地址
我的个人博客:谋仁·Blog
微信公众号:谋仁的麻袋
CSDN:曹谋仁


摘要&思维导图

本项目是基于MATLAB实现的数字图像加密解密系统,加密算法由笔者自主设计。此加密系统可以百分百无损恢复到原图,支持对单通道、三通道数字图像图像的加密及解密,支持对jpg、jpeg、png、tif、tiff、bmp等众多主流图像格式加密及解密。此外,用户可以自己选择加密次数,加密次数越多,像素越混乱,安全系数越高。但对于接收方而言,无需知道加密次数,一键解密图像,方便又快捷。
同时,该项目算法简单,易于学习,是个不错的数字图像处理练手项目。
算法整体思维导图如下:
思维导图

图像加密算法(附源码)

笔者认为,所谓加密,就是“有规律的破坏”。即要在能保证复原的前提下尽可能地去“破坏”。这个“破坏”的程度越大,与原信息相关性越低,被攻破的难度就越大,加密效果就越好。
因此,为了尽可能的“破坏”原图像,在此加密系统中,加密算法先后进行了像素数值加密和像素分布加密两部分,从两方面先后“破坏”原图,加密效果更好。

像素值加密

像素值加密分为两步。
第一步,由于RBG三通道图像以及单通道灰度图的像素取值范围是[0,255],所以可以先用255减去各个像素值做第一次像素值变换。
测试图像如下所示:
测试图像

经过像素值第一步变换,结果如下:
像素值加密第一步后

第二步,打乱RGB三通道顺序,将RGB变换为BRG。这一步只针对RGB三通道图像,如果传入图像是灰度图就只完成第一步。
在MATLAB中,传入的三通道彩色图像存储格式是无符号整形8位(uint8)的三维矩阵。R(Red)通道的值存放在(*,*,1),G(Green)通道的值存放在(*,*,2),B(Blue)通道的值存放在(*,*,3)。经过变换,将R(Red)通道的值存放在(*,*,2),G(Green)通道的值存放在(*,*,3),B(Blue)通道的值存放在(*,*,1)。下图是在第一步输出图像的基础上进行第二步变换后的图像,也是测试图像完成像素值加密后的结果:
像素值加密完成图像

至此,像素值加密已经完成。
像素值变换函数源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function imSrc = Figure_Transform(imSrc,mode)
%该函数对传入图像进行像素值加密或解密
%imSrc:传入三通道或单通道图像
%mode:选择加密或解密。0:加密 1:解密


[~,~,channel]=size(imSrc);%获取传入图像的尺寸信息
%-----------------像素值加密/解密-----------------%
%---step 1:对最大像素值255取差(加密解密相同)---%
imSrc=abs(255-imSrc);

%下面的rgb通道顺序改变只针对3通道图像,单通道灰度图不能进行该操作
if channel==3
%---step 2:打乱/恢复 颜色通道顺序---%
%彩色图像三通道分布顺序:
%(:,:,1)--red (:,:,2)--green (:,:,3)--blue
%加密:rgb->brg 解密:brg->rgb
%---1:前两位交换(加密解密相同)---%
temp=imSrc(:,:,2);
imSrc(:,:,2)=imSrc(:,:,1);
imSrc(:,:,1)=temp;
%---2:加密:第一位g和第三位b交换得到brg---%
%---2:解密:第二位b和第三位g交换得到rgb---%
temp=imSrc(:,:,3);
imSrc(:,:,3)=imSrc(:,:,1+mode);
imSrc(:,:,1+mode)=temp;
end


end


像素分布加密

从像素值加密完成后输出的图像来看,虽然颜色上已经与原图有较大偏差,但我们仍能从中很明显的看出原图的轮廓线信息。所以接下来我们要做的工作是打乱像素的秩序排列,破坏轮廓线信息。
为了打乱像素的排列,类似于拧魔方,我们可以先后打乱每一行、每一列的像素分布。那么,行或者列的打乱要依照怎样的打乱规则才能保证像素分布的混乱和可复原性呢?这里我设计了一个秘钥矩阵:(下列秘钥矩阵是目前系统中的秘钥矩阵)
distriKey=

矩阵第一行是索引,第二行是行偏移秘钥,第三行是列偏移秘钥。秘钥正负号决定了偏移方向,绝对值决定了偏移的程度,一行代表了一个平移周期。偏移操作使用的是MATLAB循环移动函数circshift。
在进行行偏移或列偏移时,由于传入图像的尺寸都不同,为保证一致的打乱效果,秘钥值的绝对值不能代表偏移的位数。而应该将其转换成在所在列或行中的比例,所以要先对秘钥矩阵进行预处理,即将第二、三行的秘钥值的绝对值换算成在该行的比例,然后再带上原先的符号,代码如下:

1
2
3
4
5
6
7
8
9
10
[~,distrikeyCol]=size(distriKey);%获取密钥矩阵尺寸
%下面循环是将密钥值换成比例
for a=1:distrikeyCol
newRowKey=abs( distriKey(2,a) )/ sum( abs( distriKey(2,:) ) );
newColKey=abs( distriKey(3,a) )/ sum( abs( distriKey(3,:) ) );
%下面是将已经算得的比例值带上原先的正负号
distriKey(2,a)=newRowKey*(distriKey(2,a)/abs(distriKey(2,a)));
distriKey(3,a)=newColKey*(distriKey(3,a)/abs(distriKey(3,a)));
end

预处理后秘钥矩阵distriKey=
预处理后的秘钥矩阵
如何根据第一行的索引获取当前行或列对应的秘钥值?

  • 当前行号或列号对秘钥矩阵长度取余
  • 在秘钥矩阵第一行找与取余结果相同的值
  • 找到对应索引值后,索引所在列的第二行是行变换秘钥值,第三行是列变换秘钥值

如此循环,取完秘钥矩阵最后一列的值后,下一次会再回到第一列的值。一行就是一次偏移周期。
因此,我们可以通过增大偏移周期长度来扩大偏移的自由度,提升安全系数
下面将对像素值加密后的图像先后进行一次行变换和列变换:
行变换后:
行变换后

行变换+列变换后:
行变换+列变换后

至此,我们已经完成了像素值加密和一次像素分布加密。
当然,这个加密一次的图像的加密效果体现的是使用的刚刚列举的秘钥矩阵,不是最优秘钥。
我们可以改变秘钥值和秘钥矩阵长度来优化打乱规则。例如:

  • 相邻两个秘钥值的绝对值相差要大
  • 相邻的间隔要避免相同
  • 秘钥值的正负号设置要尽可能无规律
  • 增加长度增大偏移周期
  • ··· ···

因此,秘钥矩阵为加密算法带来了很大的自由度,不易被攻破。

源代码:
行变换函数源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
function imSrc = Row_Transform(imSrc,distriKey,mode)
% 对传入图像进行行变换(加密/解密)
% imSrc:传入三通道或单通道图像
% distriKey:传入秘钥矩阵
% mode:选择加密或解密。 1:加密 -1:解密


[row,col,channel]=size(imSrc);%获取传入图像的尺寸信息
[~,distrikeyCol]=size(distriKey);%获取密钥数组尺寸
%下面循环是将密钥值换成比例
for a=1:distrikeyCol
newRowKey=abs( distriKey(2,a) )/ sum( abs( distriKey(2,:) ) );
newColKey=abs( distriKey(3,a) )/ sum( abs( distriKey(3,:) ) );

distriKey(2,a)=newRowKey*(distriKey(2,a)/abs(distriKey(2,a)));
distriKey(3,a)=newColKey*(distriKey(3,a)/abs(distriKey(3,a)));
end



%移动的位数和方向由像素分布密钥决定
tempRowMove=zeros(channel,col);
for j=1:row
%下面循环是将当前行上所有像素值放到中间量tempRowMove中
for i=1:col
tempRowMove(:,i)=imSrc(j,i,:);
end
%下面选择结构用于判断当前行移动情况(方向和偏移量)
%在密钥中找到相应秘钥值
keyId=mod(j,distrikeyCol);
for m=1:distrikeyCol
if(distriKey(1,m)==keyId)
keyId=m;%如果找到keyId位置将该位置的列数赋给keyId
break;
end
continue;
end
%确定偏移的方向
moveDirect=distriKey(2,keyId)/abs(distriKey(2,keyId));
%偏移量和偏移方向
rowMove=mode*moveDirect*ceil(row*abs(distriKey(2,keyId)));

%下面的循环是对当前行上的像素进行移动
for n=1:channel
tempRowMove(n,:)=circshift(tempRowMove(n,:),rowMove);
end
%下面循环是将偏移后的像素重新赋值回去
for t=1:col
for s=1:channel
imSrc(j,t,s)=tempRowMove(s,t);
end
end
end


end


列变换函数源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
function imSrc = Col_Transform(imSrc,distriKey,mode)
% 对传入图像进行列变换(加密/解密)
% imSrc:传入三通道或单通道图像
% distriKey:传入秘钥矩阵
% mode:选择加密或解密。 1:加密 -1:解密

[row,col,channel]=size(imSrc);%获取传入图像的尺寸信息
[~,distrikeyCol]=size(distriKey);%获取密钥数组尺寸
%下面循环是将密钥值换成比例
for a=1:distrikeyCol
newRowKey=abs( distriKey(2,a) )/ sum( abs( distriKey(2,:) ) );
newColKey=abs( distriKey(3,a) )/ sum( abs( distriKey(3,:) ) );

distriKey(2,a)=newRowKey*(distriKey(2,a)/abs(distriKey(2,a)));
distriKey(3,a)=newColKey*(distriKey(3,a)/abs(distriKey(3,a)));
end

%移动的位数和方向由像素分布密钥决定
tempColMove=zeros(row,channel);
for j=1:col
%下面循环是将当前列上所有像素值放到中间量tempColMove中
for i=1:row
tempColMove(i,:)=imSrc(i,j,:);
end
%下面选择结构用于判断当前列移动情况(方向和偏移量)
%在密钥中找到相应偏移比例
keyId=mod(j,distrikeyCol);
for m=1:distrikeyCol
if(distriKey(1,m)==keyId)
keyId=m;%如果找到keyId位置将该位置的列数赋给keyId
break;
end
continue;
end
%确定偏移的方向
moveDirect=distriKey(3,keyId)/abs(distriKey(3,keyId));
%偏移量和偏移方向
colMove=mode*moveDirect*ceil(row*abs(distriKey(3,keyId)));
%下面的循环是对当前列上的像素进行移动
for n=1:channel
tempColMove(:,n)=circshift(tempColMove(:,n),colMove);
end
%下面循环是将偏移后的像素重新赋值回去
for t=1:row
for s=1:channel
imSrc(t,j,s)=tempColMove(t,s);
end
end
end


end


像素分布加密源代码:
(调用以上两个行变换函数和列变换函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function imSrc = Distribution_Transform(imSrc,mode)
% 该函数对传入图像进行像素排列上加密或解密
% imSrc:传入三通道或单通道图像
% mode:选择加密或解密。1:加密 -1:解密


%像素分布密钥(第一行是密钥值的索引)
%秘钥的值的绝对值大小就是偏移的程度,正负号表示偏移的方向
%为了保证加密效果,行变换和列变换的秘钥不同
%第二行是行秘钥,第三行是列秘钥
distriKey=[
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 0;
8, -1, -16, 11, 7, -4, 10, -4, 12, 2, 1, -20, -7, 18, 1;
-1, 25, 7, -9, 41, -10, 16, -28, -2, 11, -10, 6, 45, -18, 9
];
if mode==1%加密
imSrc=Row_Transform(imSrc,distriKey,mode);%打乱行
imSrc=Col_Transform(imSrc,distriKey,mode);%打乱列
else %解密
imSrc=Col_Transform(imSrc,distriKey,mode);%恢复列
imSrc=Row_Transform(imSrc,distriKey,mode);%恢复行
end


end

像素迭代加密

虽然我们从上图中基本上无从得出原图是什么样了,但是模模糊糊地仍可以看到不同区域的像素颜色不同。为了保证更高的安全系数,我们在不再更改秘钥矩阵的前提下可以进行像素迭代加密
迭代加密就是重复多次进行像素分布加密。之所以不带上像素值加密是因为像素值加密迭代的话会复原回原先的像素值,故迭代没有意义。

迭代加密2次的图像:
迭代2次
迭代加密3次的图像:
迭代3次
迭代加密5次的图像:
迭代5次
从这些结果来看,基本可以满足我们对加密的需求了。
但是为了解密时可以一键解密,不用输入解密次数,迭代加密后图像要携带迭代次数信息。
为了使输出图像携带迭代次数信息,我们可以在最左侧添加一冗余列,冗余列数值和相邻列相同,这样冗余列就很好地隐蔽到加密图像中了。然后将加密次数存放在第一行第一列第一页处,即输出图像的(1,1,1)。
通过以下图示可一目了然:
添加冗余列
迭代加密完成后,对图像的加密即已完成。
图像加密函数源代码:
(调用以上像素值加密和像素分布加密函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function imSrc = Digital_Image_Encryption(imSrc,count)
%该函数对传入图像进行加密
%imSrc:传入三通道或单通道图像
%count:像素排列加密次数


% %-----------------第一次加密:像素值加密-----------------%
imSrc=Figure_Transform(imSrc,0);%调用封装好的函数

for time=1:count
%-----------------第二次加密:像素排列加密-----------------%
imSrc=Distribution_Transform(imSrc,1);
end

%-----------------下面的操作是为了让输出图像携带有加密次数---------------%
[row,~,channel]=size(imSrc);
timesCol=zeros(row,1,channel);
%------为提升安全系数,多出的一列与邻近一列相同,只是第一个像素的像素值是加密次数%
for n=1:row
timesCol(n,1,:)=imSrc(n,1,:);%将imSrc第一列所有像素复刻到新列中
end
timesCol(1,1,1)=count;%在第一行第一列第一页位置像素值就是加密次数
imOut=[timesCol,imSrc];%将加密次数列并到加密图像上
imSrc=imOut;%将最终图像输出

end

图像解密算法(附源码)

加密完成后,解密就很简单了。图像解密是图像加密的逆过程。主要有以下几步:

  • 第一步,先获取(1,1,1)处的迭代次数信息,然后将第一列删除
  • 然后进行像素值解密,和加密时一样,先用255减去像素值,如果三通道图像的话再复原RGB通道顺序,将BRG变换回RGB
  • 最后再进行像素分布解密,在偏移方向上乘-1即可按照与加密时相反的方向,相同的位数进行复原,在外层套for循环,循环次数为已经获取的迭代次数,如此即可恢复原图

下面对迭代5次加密图像进行解密:
解密
可以无失真复原。

图像解密算法源代码:
(调用像素值变换函数和像素分布变换函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function imSrc = Digital_Image_Decryption(imSrc)
%该函数对传入已加密图像进行解密
%imSrc:传入三通道或单通道图像

count=imSrc(1,1,1);%获取加密次数
imSrc(:,1,:)=[];%删除冗余列
% %-----------------第一次解密:恢复像素值-----------------%
imSrc=Figure_Transform(imSrc,1);%调用封装好的函数

for time=1:count
%-----------------第二次解密:恢复像素排列-----------------%
imSrc=Distribution_Transform(imSrc,-1);
end

end

UI界面设计

此系统的UI界面是用MATLAB的APP Designer制作的。具体操作在此不多赘述,下面图像是UI界面的展示:

目录界面:
目录界面

加密系统界面:
加密系统界面

解密系统界面:
解密系统界面

logo设计

软件图标:
图标
原图:
logo原图

由于本人水平有限,算法和代码还有很多纰漏及不合理之处,还望大家多多批评指正!