该项目已上传至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) [~,~,channel]=size (imSrc); imSrc=abs (255 -imSrc); if channel==3 temp=imSrc(:,:,2 ); imSrc(:,:,2 )=imSrc(:,:,1 ); imSrc(:,:,1 )=temp; 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) [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 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; 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) [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 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; 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) 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次的图像: 迭代加密3次的图像: 迭代加密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=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 ,:); 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) 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设计 软件图标: 原图:
由于本人水平有限,算法和代码还有很多纰漏及不合理之处,还望大家多多批评指正!