成都信息工程学院
课程设计报告
DES加密解密算法的实现(Java)
课程名称:应用密码算法程序设计
学生姓名: 李文浩
学生学号: 2007122054
专业班级: 信安072班
任课教师: 万武南
20##年 11 月 11 日
目 录
1.引言... 1
1.1 背景... 1
1.2 目的... 1
1.3 本设计的主要任务... 1
2.系统设计... 2
2.1系统主要目标... 2
2.1.1主要软件需求(运行环境)... 2
2.2 系统结构... 2
2.2.2 软件操作流程... 2
2.2.3功能模块... 3
3 系统功能程序设计... 3
3.1循环移位... 3
3.2获取RoundKey. 4
3.3 IP置换,逆置换,E扩展,置换P. 5
3.4 Fnew函数(F函数). 5
3.5异或... 7
3.6 S盒代替... 7
3.7十六进制转二进制... 7
3.8二进制转十六进制... 8
3.9加密... 9
3.10解密... 9
3.11程序界面预览... 10
4. 测试报告... 11
5.结论... 14
参考文献... 14
1.引言
1.1 背景
数据加密标准(Data Encryption Standard,,DES)曾被美国国家标准局(NBS,现为国家标准与技术研究所NIST)确定为联邦信息处理标准(FIPS PUB 46),得到过广泛使用,特别是在金融领域,曾是对称密码体制事实上的世界标准。目前在国内,随着三金工程尤其是金卡工程的启动,DES算法在POS,ATM,智能卡,加油站,高速公路收费站等领域被广泛使用,以此来实现关键数据加密,如信用卡持卡人的PIN的加密传输,IC卡与POS间的双向认证、金融交易数据包的MAC校验等,均用到DES算法。
DES起源于1973年美国国家标准局NBS征求国家密码标准方案。IBM就提交了其在20世纪60年代末设立的一个计算机密码编码学方面的研究项目的结果,这个项目在1971年结束时研制出了一种称为Lucifer算法。它是当时提出来的最好的算法,因而在1977年被选为数据加密标准,有效期为5年,随后在1983年、1987年、和1993年三次再度授权该算法续用5年。
DES使用一个56位的密钥以及附加的8位奇偶校验位,产生最大64位的分组大小。这是一个迭代的分组密码,使用称为 Feistel 的技术,其中将加密的文本块分成两半。使用子密钥对其中一半应用循环功能,然后将输出与另一半进行“异或”运算;接着交换这两半,这一过程会继续下去,但最后一个循环不交换。DES 使用 16 个循环。
1.2 目的
在一些特殊场合,人们交换信息并不希望被其他人知道,需要把消息进行一系列转换后在公共信道中传输,接收方再通过一些手段将消息转换回来,本程序用DES对称密钥加密算法加解密一组长度为64bit的十六进制数,达到保密传递信息的目的.
1.3 本设计的主要任务
根据DES加密算法实现一组长度为64bit的十六进制数加密解密。向用户提供良好的交互,具有良好的操作性。可完成一个明文分组的加解密,明文和密钥是十六进制,长度都为64比特(16个16进制数),输入明文和密钥,输出密文,进行加密后,能够进行正确的解密;程序运行时,要求输出每一轮使用的密钥,以及每一轮加密或解密之后的16进制表示的值。
2.系统设计
2.1系统主要目标
2.1.1主要软件需求(运行环境)
本软件适用java语言编写,编译成功后的.class文件可以在装有JDK开发环境的任何计算机上使用。本软件设计符合DES程序设计基本要求:
(1)在深入理解DES加密/解密算法理论的基础上,设计一个DES加密/解密软件系统;
(2)完成一个明文分组的加解密,明文和密钥是十六进制,长度都为64比特(16个16进制数),输入明文和密钥,输出密文,进行加密后,能够进行正确的解密;
(3)程序运行时,要求输出每一轮使用的密钥,以及每一轮加密或解密之后的16进制表示的值;
(4)要求提供所设计系统的报告及完整的软件。
输入:
加密:8字节定长的十六进制明文(16个十六进制数)。
解密:8字节定长的十六进制密文(16个十六进制数)。
输出:
加密:8字节定长的十六进制已加密的密文(16个十六进制数)。
解密:8字节定长的十六进制的正常解密的明文(16个十六进制数)。
测试平台:Windows Vista Home Basic/Windows XP Professional
2.2 系统结构
2.2.2 软件操作流程
加密:
(1)按提示选择“1”进行加密过程,程序将正常执行下面的加密过程。
(2)按提示输入你加密所需要的长度为16个十六进制的密钥。
(3)按提示输入你要加密的长度为16个十六进制的明文。
(4)加密后,每轮加密结果和最终加密结果(即密文)将显示给用户。
解密:
(1)按提示选择“0”进行解密过程,程序将正常执行下面的解密过程。
(2)按提示输入你解密所需要的长度为16个十六进制的密钥。
(3)按提示输入你要解密的长度为16个十六进制的密文。
(4)解密后,每轮解密的结果和最终解密出的明文将显示给用户。
2.2.3功能模块
3 系统功能程序设计
3.1循环移位
实现循环移位,由于DES算法涉及到得只有循环左移一位或者两位,我考虑用穷举的方法,因为只有两种情况,因此用if条件语句判断每轮循环的位数。我定义了4个中间变量co,do,c1,d1存放中间结果。
Void LwhDES::LeftBitMove(int[] k, int offset)
{
int i;
// 循环移位操作函数
int[] c0 = new int[28];
int[] d0 = new int[28];
int[] c1 = new int[28];
int[] d1 = new int[28];
for (i = 0; i < 28; i++){
c0[i] = k[i];
d0[i] = k[i + 28];
}
if (offset == 1) {
for (i = 0; i < 27; i++) { // 循环左移一位
c1[i] = c0[i + 1];
d1[i] = d0[i + 1];
}
c1[27] = c0[0];
d1[27] = d0[0];
}
else if (offset == 2) {
for (i = 0; i < 26; i++) { // 循环左移两位
c1[i] = c0[i + 2];
d1[i] = d0[i + 2];
}
c1[26] = c0[0];
d1[26] = d0[0];
c1[27] = c0[1];
d1[27] = d0[1];
}
for (i = 0; i < 28; i++) {
k[i] = c1[i];
k[i + 28] = d1[i];
}
}
3.2获取RoundKey
生成轮子密钥前调用了PC1变换函数,循环过程中调用了循环移位函数和PC2变换函数,最终生成了KeyArray[16][48]的轮子密钥。
int[][] LwhDES::KeyInitialize(int[] key)
{
int i;
int j;
int[][] keyarray=new int[16][48];
int[] K0 = new int[56];
for (i = 0; i < 56; i++) {
K0[i] = key[PC_1[i] - 1]; // 密钥进行PC-1变换
}
for (i = 0; i < 16; i++) {
LeftBitMove(K0, LeftMove[i]);
for (j = 0; j < 48; j++) {
keyarray[i][j] = K0[PC_2[j] - 1]; // 生成子密钥keyarray[i][j]
}
}
return keyarray;
}
3.3 IP置换,逆置换,E扩展,置换P
置换使用的是简单的查表置换,table置换,即将输出的数据与输入的数据相应的数组的对应项查出来。
IP置换:
for (i = 0; i < 64; i++)
{
M[i] = timeData[IP[i] - 1]; // 明文IP变换, IP[]为IP置换表
……}
IP逆置换:
for (i = 0; i < 64; i++)
{
MIP_1[i] = M[IP_1[i] - 1]; // 进行IP-1运算, IP_1[]为IP逆置换表
……}
E扩展:
for (i = 0; i < 48; i++) {
RE[i] = R0[E[i] - 1]; // 经过E变换扩充,由32位变为48位}
置换P:
for (i = 0; i < 32; i++)
{
RP[i] = sValue[P[i] - 1]; // 经过P变换, P[]为P置换表
……}
3.4 Fnew函数(F函数)
F函数为该加密程序最关键的地方,里面涉及到E扩展,与密钥异或,S盒压缩,P置换,每轮都要使用此函数,此函数编写的好坏很大程度上影响整个程序的执行效率。注意,此处的Fnew函数与标准F函数略有不同, Fnew函数将F函数后Lo,Ro的异或和换位都已经实现。
Void LwhDES::private void LoopF(int[] M, int times, int flag, int[][] keyarray)
{
int i,j;
int[] L0 = new int[32];int[] R0 = new int[32];int[] L1 = new int[32];int[] R1 = new int[32];
int[] RE = new int[48]; //存放经过E扩展的消息
int[][] S = new int[8][6]; //存放成8行六列,以便进行S盒压缩
int[] sBoxData = new int[8]; //每一行查出的数
int[] sValue = new int[32]; //
int[] RP = new int[32]; //经过置换P的数
for (i = 0; i < 32; i++) {
L0[i] = M[i]; // 明文左侧的初始化
R0[i] = M[i + 32]; // 明文右侧的初始化
}
for (i = 0; i < 48; i++) {
RE[i] = R0[E[i] - 1]; // 经过E变换扩充,由32位变为48位
RE[i] = RE[i] + keyarray[times][i]; // 与KeyArray[times][i]按位作不进位加法运算
if (RE[i] == 2) { //与48位密钥异或
RE[i] = 0;
}
}
for (i = 0; i < 8; i++) { // 48位分成8组
for (j = 0; j < 6; j++) { /每组六位
S[i][j] = RE[(i * 6) + j];
}
// 下面经过S盒,得到8个数
sBoxData[i] = S_Box [i] [(S[i][0] << 1) + S[i][5]] [ (S[i][1] << 3) + (S[i][2] << 2) + (S[i][3] << 1) + S[i][4] ];
// 8个数变换输出二进制
for (j = 0; j < 4; j++) {
sValue[((i * 4) + 3) - j] = sBoxData[i] % 2; //从右边一位一位的取数存放
sBoxData[i] = sBoxData[i] / 2;
}
}
for (i = 0; i < 32; i++) {
RP[i] = sValue[P[i] - 1]; // 经过P变换
L1[i] = R0[i]; // 右边移到左边
R1[i] = L0[i] + RP[i];
if (R1[i] == 2) { //异或
R1[i] = 0;
}
// 重新合成M,返回数组M
// 最后一次变换时,左右不进行互换。此处采用两次变换实现不变
if (((flag == 0) && (times == 0)) || ((flag == 1) && (times == 15)))
{
M[i] = R1[i];
M[i + 32] = L1[i];
}
else
{
M[i] = L1[i];
M[i + 32] = R1[i];
}}}
3.5异或
Java语言我使用” ^ ”符号进行异或,结果没有成功,于是利用循环实现各个位相加,再每位检查,若等于2,则置为0。
for (i = 0; i < 48; i++) { ……
RE[i] = RE[i] + keyarray[times][i]; // 两个数进行不进位加法
if (RE[i] == 2) //若加出的结果为2则置为0,实现了异或
{
RE[i] = 0;
}}
3.6 S盒代替
S盒替换采用了将48个bit数先分为8组,每一组对应一个S盒,再通过行列计算查出S表里的十进制值,再转化为二进制。
int[] RE = new int[48];
int[][] S = new int[8][6];
int[] sBoxData = new int[8];
int[] sValue = new int[32];
for (i = 0; i < 8; i++) // 48位分成8组
{
for (j = 0; j < 6; j++) //每组6位
{
S[i][j] = RE[(i * 6) + j];
}
// 下面经过S盒,得到8个数
sBoxData[i] = S_Box[i] [(S[i][0] << 1) + S[i][5]] [ (S[i][1] << 3) + (S[i][2] << 2) + (S[i][3] << 1) + S[i][4] ];
// 8个数变换输出二进制
for (j = 0; j < 4; j++)
{
sValue[((i * 4) + 3) - j] = sBoxData[i] % 2;
sBoxData[i] = sBoxData[i] / 2;
}
}
3.7十六进制转二进制
本函数主要用于十六进制数向二进制数的转换,十六进制数只有十六种情况,本程序使用穷举方法,利用switch、case语句实现十六进制对二进制的转换,避免移位出现不可预计的错误。
int[] LwhDES::changeHtoB(String s) //实现十六进制到二进制的转换;
{
int [] Message=new int[64];
StringBuffer buf = new StringBuffer();
String str=s;
String message="";
int i,j;
for(i=0;i<str.length();i++)
{
char ch=str.charAt(i);
switch(ch){
case '0' : message = "0000"; break; case '1' : message = "0001"; break;
case '2' : message = "0010"; break; case '3' : message = "0011"; break;
……
case 'e' : message = "1110"; break; case 'f' : message = "1111"; break;
default: break;
}
buf=buf.append(message);
}
//System.out.print(buf);
for(j=0;j<64;j++)
{
if (buf.charAt(j)=='0')
{ Message[j]=0; }
else Message[j]=1;
}
return Message;
}
3.8二进制转十六进制
本函数主要用于将结果二进制数转换成十六进制向用户输出,实现二进制转十六进制数,即4个bool型变量转化为一个十六进制数,先将bool流写入StringBuffer,再调用toString转化成字符串,对字符串进行取子串,再调用Integer.toHexString方法转换为十六进制。
String LwhDES::changeBtoH(int[] data)
{ //实现二进制到十六进制的转换;
StringBuffer buf=new StringBuffer();
String s=new String();
for(int i=0;i<data.length;i++)
{
if(data[i]==0) buf.append('0');
else if(data[i]==1) buf.append('1');
}
String string=buf.toString(); //形成一个全是bool变量(0或1)的字符串
int temp=0;
StringBuffer outputbuf=new StringBuffer();
String s1=string.substring(0,data.length);
for(int i=0;i<s1.length()/4;i++) //每四位取一次,表示一个十六进制数
{
temp=Integer.valueOf( s1.substring(i*4,(i+1)*4) , 2 );
outputbuf=outputbuf.append(Integer.toHexString(temp));
}
//System.out.print(outputbuf);
s=outputbuf.toString();
return s;
}
3.9加密
加密过程其实就是调用以写好的模块,对消息进行16次迭代加密,但在每一轮中要进行中间一些加密结果的显示,又因为加密和解密我写在了同一个Encrypt函数里,因此只用标志位flag区别加解密,1为加密,0为解密。
int[] LwhDES::Encrypt(int[] timeData, int flag, int[][] keyarray)
{
int i;
int flags = flag;
int[] M = new int[64];
int[] MIP_1 = new int[64];
for (i = 0; i < 64; i++)
{
M[i] = timeData[IP[i] - 1]; // 明文IP变换
}
if (flags == 1)
{ // 加密
for (i = 0; i < 16; i++)
{
Fnew(M, i, flags, keyarray); //注意,LoopF函数将每轮迭代运算的 Li=Ri-1, //Ri=Li-1^F(Ri-1,Ki)一起写了进去
……//显示中间结果
}
}
3.10解密
解密过程与加密相逆,其实也是调用写好的模块,对密文进行16次迭代解密,但在每一轮中要进行中间一些加密结果的显示,又因为加密和解密我写在了同一个Encrypt函数里,因此只用标志位flag区别加解密,1为加密,0为解密。
int[] LwhDES::Encrypt(int[] timeData, int flag, int[][] keyarray)
{
else if (flags == 0)
{ // 解密
for (i = 15; i > -1; i--)
{
Fnew(M, i, flags, keyarray); //关键的解密步骤,下面代码都是为显示中间结果
int n,j;
int [] k=new int[48];
System.out.println("第" + (16-(i+1)) +"轮输出的密文的十六进制形式:");
System.out.println(changeBtoH(M));
for(j=0;j<48;j++)
{
k[j]=keyarray[i][j];
}
System.out.println("第" + (16-(i+1)) +"轮输出的轮子密钥十六进制形式:");
System.out.println(changeBtoH(k));
}
}}
3.11程序界面预览
在命令行窗口进行操作,界面比较友好,易于操作,下图为开始界面:
图1
4. 测试报告
(1) 对十六进制数加密
明文:31 32 33 34 35 36 37 38
密钥:31 32 33 34 35 36 37 38
密文:96 d0 02 88 78 d5 8c 89
解密后的明文: 31 32 33 34 35 36 37 38
选择加密初始化明文和密钥:
图2
加密结果与标准一致:
图3
图4
对密文进行直接解密,得出正确的结果:
图5
图6
(2) 对十六进制数解密
明文:96 d0 02 88 78 d5 8c 89
密钥:31 32 33 34 35 36 37 38
密文:31 32 33 34 35 36 37 38
经过检测:
1.本软件符合DES基本要求,能对16个十六进制数进行正常的加密和解密,加密和解密结果没有错误,并能显示每轮加密的中间结果。
2.对于错误的输入,能以提示性语言加以提示。
3.没有发现导致致命错误的操作方法。
5.结论
本软件可以实现DES加密解密算法的基本要求,可以正确地对8个字节的明文进行加密,或者对8个字节的密文进行正常的解密。通过本次的课程,我对DES的算法有了进一步深入的了解,深刻了解了DES加密的每轮迭代过程,对简单的异或,移位有了更深的了解,灵活运用可以实现许多功能,通过对DES算法的异或,置换,查S盒压缩等方便的编写,对java语言编程有了更深刻的掌握,比如编写异或事由于没查到Java语言中异或的运算符,我就简单的使用各位相加后,若为2则置为零来代替。本来希望使用c++实现DES加密解密算法,虽然能正常加解密,但是中间过程输出结果与标准有差异,而且,由于Java中String, StringBuffer提供许多方法,使程序比C++方便的实现了十六进制的转换问题。
参考文献
[1] 胡向东、魏琴芳,应用密码学,电子工业出版社,2008
[2] Mary Campione,语言导学,机械工业出版社,2003
[3] 孙卫琴,Java面向对象编程,电子工业出版社,2006
[4] (美)埃克尔,Java编程思想,机械工业出版社,2007