深入理解并实现DES算法


  1. DES简介

  • DES算法属于分组加密算法
  • 信息按照固定长度进行分组,分组长度为64位
  • 混淆和扩散是它采用的两个最重要的安全特性
    • 混淆是指通过密码算法使明文和密文以及密钥的关系非常复杂,无法从数学上描述或者统计。
    • 扩散是指明文和密钥中的每一位信息的变动,都会影响到密文中许多位信息的变动,从而隐藏统计上的特性,增加密码的安全。

需要注意的地方是掌握DES算法的16轮加、解密流程以及子密钥的产生流程。

2.DES实现大纲

在我的另一篇博客《加密算法之对称加密–DES》已经提到过DES算法的大致过程,整个流程如下图:

这里写图片描述

为了深入理解整个流程,我们将细化讲解DES算法里十六轮迭代变化中的子密钥生成以及F函数

3.DES算法之子密钥生成

(1)子密钥生成流程梗概:

这里写图片描述

(2)子密钥置换选择1(把6464位的密钥变为5656位长):

假设原密钥为Ks=k1,k2,k3,......,k64K_s= k_1,k_2,k_3,......,k_{64}

DES算法的实际密钥长度为5656,因为6464位原密钥每88位的最后一位即第8162432404856648,16,24,32,40,48,56,64位为校验位。通过如图的置换规则,将实际的5656为位密钥选择出来并置乱:这里写图片描述

因此置换选择1之后,5656位密钥为Ks=k1,k2,k3......k56K_{s' }= k_1,k_2,k_3......k_ {56},其中k1=Ks[57],k2=Ks[49],k3=Ks[41],......k56=Ks[4].k_1 = K_{s[57]}, k_2 = K_{s[49]},k_3 = K_{s[41]},......k_{56} = K_{s[4]}.

(3)子密钥迭代变换

实际密钥Ks=k1,k2,k3......k56K_{s' }= k_1,k_2,k_3......k_ {56}

1)分组

将56位实际密钥分为Ci和Di两组,其中:

  • Ci=k1,k2,......,k28C_i = k_1,k_2,......,k_{28}
  • Di=k29,k30,......k56D_i = k_{29},k_{30},......k_{56}

2)循环移位(左移)

  • 移位判断表:这里写图片描述
    第一列和第三列代表迭代次数,第二列和第四列代表对应的循环左移位数。
  • 第1,2,9,16轮迭代时循环左移一位,其余循环左移两位
  • 最终得到:
    • 循环左移一位:Ci=k2,k3,......k28,k1Di=K30,k31,......k56,k29C_{i'}=k_2,k_3,......k_{28},k_1;D_{i'}=K_{30},k_{31},......k_{56},k_{29}
    • 循环左移两位:Ci=k3,k4,......k1,k2Di=K31,k32,......k29,k30C_{i'}=k_3,k_4,......k_1,k_2;D_{i'}=K_{31},k_{32},......k_{29},k_{30}

3)选择置换2
将第2)步得到的CiC_{i'}以及DiD_{i'}拼接起来形成56位密钥Ks=[Ci,Di]K_{s''} = [C_{i'},D_{i'}],
然后进行选择置换2,置换列表如图:这里写图片描述

最后得到每一轮迭代所用到的48位子密钥ki=k1,k2,k3,......,k48k_i = k_1,k_2,k_3,......,k_{48},其中k1=Ks[14],k2=Ks[17],......k48=Ks[32]k_1 = K_{s''[14]}, k_2 = K_{s''[17]},......k_48 = K_{s''[32]}.

4.DES算法之F函数变换

DES算法第i轮圈变换流程如下图:

这里写图片描述

根据此图可以得到迭代公式:

{Li=Ri1Ri=Li1f(Ri1,Ki),i=1,2,,,,16\left\{\begin{matrix} L_i=R_{i-1}\\R_i=L_{i-1}\bigoplus f(R_{i-1},K_i),i=1,2,,,,16 \end{matrix}\right.

并且可以很清晰的看到每一轮迭代所进行的操作,接下来将详细讲一下F函数中的操作

(1)FF函数梗概:

这里写图片描述

(2)EE扩展置换

扩展方式:

  • 将输入的3232比特RiR_i44比特为一组分为8块;

  • 分别将第m1m-1块的最右比特和第m+1m+1块的最左比特添到第mm块的左边和右边,形成输出的第mm66比特块.

  • 形如:
    这里写图片描述

  • 理解:将1321-32位看成一个环,因此第11位前是第3232位,第44位后是第55位。。。

(3)异或加密

  • 将前一步得到的4848位经过扩展序列与每一轮的密钥kik_i进行按位异或

(4)SS盒变换

  • 目的:将4848位序列压缩为$32v位序列

  • 过程详解:

    • 66位输入记为B1B2B3B4B5B6B1,B2,B3,B4,B5,B6
    • B1B6B1B6作为SS盒的行号,B2B3B4B5B2B3B4B5作为SS盒的列号
  • SS盒内容

这里写图片描述

  • 举例

这里写图片描述

(5)PP置换

经过SS盒变换的序列最后进行P置换:这里写图片描述方式类似于之前的置换方式。

5.DES算法Java实现

public class DES_Key {
	
	public static void main(String[] args) {
		//Scanner input = new Scanner(System.in);
//		System.out.println("请输入十六进制明文:");
//		String plaintxt = input.next();
//		Integer tmpInt = Integer.valueOf(plaintxt,16);
//		String binary_plain = Integer.toBinaryString(tmpInt);
//		System.out.println(binary_plain);
//		System.out.println("请输入十六进制明文密钥:");
//		String Key = input.next();
		String Key = "1234567890ABCDFE";
		String binary_Key = hexString2binaryString(Key);
		System.out.print("binary_Key:");
		//0001 0010 0011 0100 0101 0110 0111 1000 1001 0000 1010 1011 1100 1101 1111 1110
		System.out.println(binary_Key);
		ArrayList<char[]> resultKi = new ArrayList<char[]>();//存放16个Ki
		GenerateKey(binary_Key,resultKi);
		
		Iterator<char[]> it = resultKi.iterator();
		int KiIndex = 0;
		while(it.hasNext()){
			char []temp = (char[]) it.next();
			System.out.print("第"+(KiIndex+1)+"轮密钥:");
			System.out.println(temp);
			KiIndex++;
		}
		
		String Plain = "1111111111111110";
		String binary_Plain = hexString2binaryString(Plain);
		System.out.print("binary_Plain:");
		System.out.println(binary_Plain);
		
		char[] DESEncrypt = EncryptCircleTrans(binary_Plain, resultKi);
		System.out.print("DES加密结果:");
		System.out.println(DESEncrypt);
		
		char[] DESDecrypt = DecryptCircleTrans(ToString(DESEncrypt), resultKi);
		System.out.print("DES解密结果:");
		System.out.println(DESDecrypt);
			
	}
	
	//辅助函数:将char转为String
	public static String ToString(char[]a){
		String b = "";
		for(int i=0;i<a.length;i++){
			b += a[i];
		}
		return b;
	}
	
	//char数组转为int
		public static int[] trsnChar2Int(char[] CharArr){
			int [] resultArr = new int[CharArr.length];
			for(int i=0;i<CharArr.length;i++){
				resultArr[i] = CharArr[i]-'0';
			}
			return resultArr;
		}
		
	//十六进制转2进制字符串
	public static String hexString2binaryString(String hexString)  
    {  
        if (hexString == null)  
            return null;  
        String bString = "", tmp;  
        for (int i = 0; i < hexString.length(); i++)  
        {  
            tmp = "0000"  
                    + Integer.toBinaryString(Integer.parseInt(hexString  
                            .substring(i, i + 1), 16));  
            bString += tmp.substring(tmp.length() - 4);  
        }  
        return bString;  
    }  

	//密钥选择置换PC_1 (64-56)
	public static char[] KeyPermutation1(String Key){
		char [] key = Key.toCharArray();//Get 64bits key array
		//System.out.println(key);
		char [] resultKey = new char[56];
		
		int PC_1[] = new int[]{57,49,41,33,25,17,9,1,58,50,42,34,26,18,
				10,2,59,51,43,35,27,19,11,3,60,52,44,36,
				63,55,47,39,31,23,15,7,62,54,46,38,30,22,
				14,6,61,53,45,37,29,21,13,5,28,20,12,4};
		
		for(int i=0;i<resultKey.length;i++){
			resultKey[i] = key[PC_1[i]-1];	
		}
		return resultKey;
		
	}
	//CD选择置换PC_2 (56-48)
	public static char[] KeyPermutation2(char[] CD){
		char [] cd = CD;//Get 56bits CD array
		//System.out.println(cd.length);
		char [] resultCd = new char[48];
		int PC_2[] = new int[]{14,17,11,24,1,5,3,28,15,6,21,10,
				23,19,12,4,26,8,16,7,27,20,13,2,
				41,52,31,37,47,55,30,40,51,45,33,48,
				44,49,39,56,34,53,46,42,50,36,29,32};
		
		for(int i=0;i<48;i++){
			resultCd[i] = cd[PC_2[i]-1];
		}
		return resultCd;
		
	}
	
	//循环生成密钥Ki
	public static char[] CircleGenKey(char[]C,char[]D,int flag){
		char [] Ci = C;
		char [] Di = D;

		if(flag == 0 || flag == 1 || flag == 8 || flag == 15 ){
			//循环左移1
			for(int i=0;i<28;i++){
				C[i] = Ci[(i+1)%28];
				D[i] = Di[(i+1)%28];
			}
			if(flag == 15)
				C[27] = '0';
		}
		else{//循环左移2
	        char temp0 = C[0];
	        char temp00 = D[0];
			for(int i=0;i<28;i++){	
				C[i] = C[(i+1)%28];
				D[i] = D[(i+1)%28];
			}
			C[27]=temp0;
			D[27]=temp00;
			
			char temp1=C[0];
			char temp11=D[0];
			for(int i=0;i<28;i++){
				C[i] = C[(i+1)%28];
				D[i] = D[(i+1)%28];		
			}
			C[27]=temp1;
			D[27]=temp11;
		}
		//System.out.println(C);
		//System.out.println(D);
		char [] ResultKey = new char[56];
		for(int i=0;i<28;i++){
			ResultKey[i]= C[i];
		}
		for(int j=28;j<56;j++){
			ResultKey[j]= D[j-28];
		}
		char[] ResultKi = KeyPermutation2(ResultKey);
		return ResultKi;//ki	
	}
	
	//生成密钥16轮Ki
	public static void GenerateKey(String Key,ArrayList<char[]> resultKi){
		
		char [] keySeed = KeyPermutation1(Key);//Get 56bits key that after OptPermutation PC_1
		char [] LeftC0 = new char[28];
		for(int i=0;i<LeftC0.length;i++){
			LeftC0[i] = keySeed[i];
		}
		char [] RightD0 = new char[28];
		for(int j=0;j<RightD0.length;j++){
			RightD0[j] = keySeed[j+28];
		}
		char [] LeftC = LeftC0;
		char [] RightD = RightD0;

		for(int i=0;i<16;i++){
			char[] temp = CircleGenKey(LeftC,RightD,i);
			resultKi.add(temp);
			//System.out.println(temp);
		}
	}

	//将原明文进行IP置换
	public static char[] InitialPermutation(char[] plaintxt)
	{
		char plain[] = plaintxt;
		int[] IP = new int[]{58,50,42,34,26,18,10,2,60,52,44,36,28,20,12,4,
				62,54,46,38,30,22,14,6,64,56,48,40,32,24,16,8,
				57,49,41,33,25,17,9,1,59,51,43,35,27,19,11,3,
				61,53,45,37,29,21,13,5,63,55,47,39,31,23,15,7};

		char[]result = new char[64];
		for(int i=0; i< plaintxt.length; i++){
			result[i]= plain[IP[i]-1];
		}
		return result ;
	}
	//IP 逆置换
	public static char[] ReverseInitialPermutation(char[]afterCircle)
	{
		char[] afterCircleTmp = afterCircle;
		char[] resultReverseIP = new char[64];
		int [] IPReverse = new int[]{
				40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31,
				38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29,
			    36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27,
				34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41,  9, 49, 17, 57, 25
		};
		
		for(int i=0;i<resultReverseIP.length;i++){
			resultReverseIP[i] = afterCircleTmp[IPReverse[i]-1];
		}
		
		return resultReverseIP;
	}
	
	//异或操作
	public static char[] XorAandB(char[]A,char[]B){
		char[]tmpA = A;
		char[]tmpB = B;
		int []OperateA = trsnChar2Int(tmpA);
		int []OperateB = trsnChar2Int(tmpB);
		if(OperateA.length != OperateB.length){
			return null;
		}
		
		char[] XorResult = new char[OperateA.length];
		
		for(int i=0;i<OperateA.length;i++){
			int temp = OperateA[i]^OperateB[i];
			XorResult[i] = (char) ('0'+temp);
		}
		return XorResult;
		
	}
	
	//E 扩展
	public static char[] FunctionExtension(char[] Ri){
		char[] tempRi = Ri;
		char[] resultRi = new char[48];
		int[] Extension = new int[]{32,1,2,3,4,5,4,5,6,7,8,9,8,9,10,11,12,13,
				12,13,14,15,16,17,16,17,18,19,20,21,20,21,22,23,24,25,
				24,25,26,27,28,29,28,29,30,31,32,1};
		for(int i=0;i<48;i++){
			resultRi[i] = tempRi[Extension[i]-1];
		}
		
		return resultRi;
	}

	//加密函数f 置换函数P
	public static char[] FunctionPermutation(char[] Sout ){
		char[] tempSout = Sout;
		char[] resultPout = new char[32];
		int [] Permutation = new int[]{16,7,20,21,29,12,28,17,1,15,23,26,5,18,31,10,
				2,8,24,14,32,27,3,9,19,13,30,6,22,11,4,25};
		for(int i=0;i<32;i++){
			resultPout[i] = tempSout[Permutation[i]-1];
		}
		
		return resultPout;
	}
	
	//加密函数f S盒变换
	public static char[] SBoxTrans(char[] E_nor_Ki){
		int [][][] Sbox = new int[][][]{
			{{14,	 4,	13,	 1,  2, 15, 11,  8,  3, 10,  6, 12,  5,  9,  0,  7},
			 {0, 15,  7,  4, 14,  2, 13,  1, 10,  6, 12, 11,  9,  5,  3,  8},
			 {4,  1, 14,  8, 13,  6,  2, 11, 15, 12,  9,  7,  3, 10,  5,  0},
		     {15, 12,  8,  2,  4,  9,  1,  7,  5, 11,  3, 14, 10,  0,  6, 13}},
			// S2 
		    {{15,  1,  8, 14,  6, 11,  3,  4,  9,  7,  2, 13, 12,  0,  5, 10},
			 {3, 13,  4,  7, 15,  2,  8, 14, 12,  0,  1, 10,  6,  9, 11,  5},
			 {0, 14,  7, 11, 10,  4, 13,  1,  5,  8, 12,  6,  9,  3,  2, 15},
		     {13,  8, 10,  1,  3, 15,  4,  2, 11,  6,  7, 12,  0,  5, 14,  9}},
			// S3 
		    {{10,  0,  9, 14,  6,  3, 15,  5,  1, 13, 12,  7, 11,  4,  2,  8},
			 {13,  7,  0,  9,  3,  4,  6, 10,  2,  8,  5, 14, 12, 11, 15,  1},
			 {13,  6,  4,  9,  8, 15,  3,  0, 11,  1,  2, 12,  5, 10, 14,  7},
		     {1, 10, 13,  0,  6,  9,  8,  7,  4, 15, 14,  3, 11,  5,  2, 12}},
			// S4 
		     {{7, 13, 14,  3,  0,  6,  9, 10,  1,  2,  8,  5, 11, 12,  4, 15},
			  {13,  8, 11,  5,  6, 15,  0,  3,  4,  7,  2, 12,  1, 10, 14,  9},
			  {10,  6,  9,  0, 12, 11,  7, 13, 15,  1,  3, 14,  5,  2,  8,  4},
			  { 3, 15,  0,  6, 10,  1, 13,  8,  9,  4,  5, 11, 12,  7,  2, 14}},
			// S5 
		     {{2, 12,  4,  1,  7, 10, 11,  6,  8,  5,  3, 15, 13,  0, 14,  9},
			  {14, 11,  2, 12,  4,  7, 13,  1,  5,  0, 15, 10,  3,  9,  8,  6},
		      { 4,  2,  1, 11, 10, 13,  7,  8, 15,  9, 12,  5,  6,  3,  0, 14},
		      {11,  8, 12,  7,  1, 14,  2, 13,  6, 15,  0,  9, 10,  4,  5,  3}},
			// S6 
		    {{12,  1, 10, 15,  9,  2,  6,  8,  0, 13,  3,  4, 14,  7,  5, 11},
			 {10, 15,  4,  2,  7, 12,  9,  5,  6,  1, 13, 14,  0, 11,  3,  8},
			 {9, 14, 15,  5,  2,  8, 12,  3,  7,  0,  4, 10,  1, 13, 11,  6},
		     {4,  3,  2, 12,  9,  5, 15, 10, 11, 14,  1,  7,  6,  0,  8, 13}},
			// S7 
		     {{4, 11,  2, 14, 15,  0,  8, 13,  3, 12,  9,  7,  5, 10,  6,  1},
			  {13,  0, 11,  7,  4,  9,  1, 10, 14,  3,  5, 12,  2, 15,  8,  6},
			  {1,  4, 11, 13, 12,  3,  7, 14, 10, 15,  6,  8,  0,  5,  9,  2},
		      {6, 11, 13,  8,  1,  4, 10,  7,  9,  5,  0, 15, 14,  2,  3, 12}},
			// S8 
		    {{13,  2,  8,  4,  6, 15, 11,  1, 10,  9,  3, 14,  5,  0, 12,  7},
			 {1, 15, 13,  8, 10,  3,  7,  4, 12,  5,  6, 11,  0, 14,  9,  2},
			 {7, 11,  4,  1,  9, 12, 14,  2,  0,  6, 10, 13, 15,  3,  5,  8},
		     {2,  1, 14,  7,  4, 10,  8, 13, 15, 12,  9,  0,  3,  5,  6, 11}}};
		    char outBox[] = new char[32];
			for(int i=0;i<8;i++){
				char[] temp6in = new char[6];
				
				for(int i1=0;i1<6;i1++){
					 temp6in[i1] = E_nor_Ki[i*6+i1];
				}
				//System.out.println(temp6in);
				int [] tem6in_int = trsnChar2Int(temp6in);
				int row = tem6in_int[0]*2+tem6in_int[5];
				int column = tem6in_int[1]*8+tem6in_int[2]*4+tem6in_int[3]*2+tem6in_int[4];
				int soutInt = Sbox[i][row][column];
				String HexSoutmp = Integer.toHexString(soutInt);
				
				char[] binarySoutChar = hexString2binaryString(HexSoutmp).toCharArray();
				
				int [] binarySoutInt = trsnChar2Int(binarySoutChar);
				
				for(int j=0;j<4;j++){
					outBox[i*4+j] = (char) ('0'+binarySoutInt[j]);
				}
			}
			return outBox;
	}
	
	//F函数
	public static char[] EcryptFunction(char[]Ri_1,char[]Ki){
		char []tmpRi_1 = Ri_1;
		char []tmpKi = Ki;
		char[] ExtensionR = FunctionExtension(tmpRi_1);//获得Ri经过E扩展后的数组32-48
		//Ri'与Ki异或操作
		char [] resultXorAandB = XorAandB(tmpKi,ExtensionR);

		char[]resultSBoxTrans = SBoxTrans(resultXorAandB);
		
		char[] FunctionResult = FunctionPermutation(resultSBoxTrans);
		
		return FunctionResult;
	}
	
	//DES 16轮加密圈变换
	public static char[] EncryptCircleTrans(String IPtxt,ArrayList<char[]> listKi){
		char[] IPtxt_tmp = IPtxt.toCharArray();
		char []resultOfIP = InitialPermutation(IPtxt_tmp);//明文IP初始置换
		char[] afterIPRi = new char[32];
		char[] afterIPLi = new char[32];
		
 		for(int i=0;i<32;i++){//获得初始L0和R0
			afterIPLi[i] = resultOfIP[i];
			afterIPRi[i] = resultOfIP[i+32];
		}

 		char[] resultRi = afterIPRi;
 		char[] resultLi = afterIPLi;
 
		for(int i=0;i<16;i++){//循环16次
			char[]resultRitmp = resultRi;
			char[]resultLitmp = resultLi;
			resultLi = resultRitmp;//Li = Ri-1

			char[] FOut = EcryptFunction(resultRitmp,listKi.get(i));
			resultRi = XorAandB(resultLitmp,FOut);
		}
		char[] finalCircleResult = new char[64];
		for(int i=0;i<32;i++){
			finalCircleResult[i] = resultLi[i];
			finalCircleResult[32+i] = resultRi[i];
		}
		
		char[] resultEncrypted = ReverseInitialPermutation(finalCircleResult);
		
		return resultEncrypted;
	}
	//DES 16轮解密圈变换
	public static char[] DecryptCircleTrans(String Encrypted,ArrayList<char[]> listKi){
		char[] IEncrypted_tmp = Encrypted.toCharArray();
		char []resultOfIP = InitialPermutation(IEncrypted_tmp);//密文IP-1逆置换
		char[] afterIPRi = new char[32];
		char[] afterIPLi = new char[32];
		
 		for(int i=0;i<32;i++){//获得初始L16和R16
			afterIPLi[i] = resultOfIP[i];
			afterIPRi[i] = resultOfIP[i+32];
		}
 		
 		char[] resultRi = afterIPRi;
 		char[] resultLi = afterIPLi;
 
		for(int i=15;i>=0;i--){//循环16次
			char[]resultRitmp = resultRi;
			char[]resultLitmp = resultLi;
			resultRi = resultLitmp;//Ri-1 = Li
			
			char[] FOut = EcryptFunction(resultLitmp,listKi.get(i));
			resultLi = XorAandB(resultRitmp,FOut);
		}
		char[] finalCircleResult = new char[64];
		for(int i=0;i<32;i++){
			finalCircleResult[i] = resultLi[i];
			finalCircleResult[32+i] = resultRi[i];
		}
		
		char[] resultDecrypted = ReverseInitialPermutation(finalCircleResult);
		
		return resultDecrypted;
	}