使用BitSet实现DES加密算法(Java)

一、BitSet类的介绍

    BitSet类实现了一个按需增长的位向量。位Set的每一个组件都有一个boolean值,值只有0或1(即true 和 false)。用非负的整数将BitSet的位编入索引,可以对每个编入索引的位进行测试、设置或者清除。通过逻辑与、逻辑或和逻辑异或操作,可以使用一个 BitSet修改另一个 BitSet的内容。

    默认情况下,set 中所有位的初始值都是false。

    BitSet内部维护一个long数组,默认大小是64位,初始化只有一个long segement,所以BitSet最小的size是64。如果你要用的位超过了默认size,它会再申请64位,而不是报错。随着存储的元素越来越多,BitSet内部会自动扩充,一次扩充64位,最终内部是由N个long segement 来存储。

    下面是BitSet类的简单使用:

BitSet bitSet = new BitSet(16); // 使用long数组作为内部存储结构,64位
// 申请的位都是以64为倍数的,申请不超过64按64算,超过64按128算,大小可动态改变
bitSet.set(1);
bitSet.set(3);
bitSet.set(11);
System.out.println(bitSet.length()); // 最高位为1到最低位的长度
for (int i = 0; i < bitSet.size(); i++) {
    
     // 实际使用空间的位数
	if (bitSet.get(i) == true) // 每一位相当于boolean类型
		System.out.print("1");
	else
		System.out.print("0");
}

运行结果:
12
0101000000010000000000000000000000000000000000000000000000000000

    在后面的DES实现中我们会用到下面两个函数

/**
 * BitSet ---> long
 * BitSet使用long数组作为内部存储结构,大小可动态改变
 * @param bitSet
 * @return
 */
public static long BitSetToLong(BitSet bitSet) {
    
    
	return bitSet.toLongArray()[0];
}

/**
 * long ---> BitSet
 * @param num
 * @return
 */
public static BitSet LongToBitSet(long num) {
    
    
	return BitSet.valueOf(new long[] {
    
    num});
}

    BitSet类可用于对海量数据的操作。

更多关于BitSet类的介绍可以参考下面博主的文章
Java BitSet使用场景和示例:https://www.cnblogs.com/xupengzhang/p/7966755.html
JavaBitSet学习:https://www.cnblogs.com/xujian2014/p/5491286.html#_label2
java中的BitSet学习:https://blog.csdn.net/feihong247/article/details/7849317

二 、DES的实现

基本参数

  1. 分组长度:64 bit
  2. 密钥长度:64 bit
  3. 有效密钥长度:56 bit(其中8 bit用于校验)
  4. 迭代圈数:16 圈
  5. 每圈子密钥长度:48 bit
    从上面的参数我们可以看到,用BitSet类和long很容易实现DES。

    先看一下DES的流程图,仔细看DES其实不难实现。

在这里插入图片描述

1.初始置换IP(initial permutation)

    public static final int IP[] = {
    
     
			 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 };

    将输入的 64 位明文重新进行排序,即将第 58 位放到第 1 位,第 50 位放到第 2 位……以此类推。初始置换以后得到的是一个 64 位的输出。

   /**
	 * 初始置换
	 * @param plaintext  64位明文
	 * @return           64位乱序序列
	 */
	public static long Initial_Permutation(long plaintext) {
    
    
		BitSet temp=LongToBitSet(plaintext);
		BitSet bitSet=new BitSet();
		for(int i=63;i>=0;i--) {
    
      //数组的第0位是long数据的最右边的第一位,即最低位
			bitSet.set(i, temp.get(64-IP[63-i]));
		}
		return BitSetToLong(bitSet);
	}

2.生成子密钥 Ki(key generation)

    子密钥生成的流程图如下:

在这里插入图片描述

  • 用户输出的密钥是 64 位的,根据密钥置换表PC-1,将 64 位变成 56 位密钥。(去掉了奇偶校验位)
  • 将 PC-1 置换得到的 56 位密钥,分为前28位 C0 和后28位 D0,分别对它们进行循环左移,C0左移得到 C1,D0 左移得到 D1
  • 将 C1 和 D1 合并成 56 位,然后通过PC-2表进行压缩置换,得到当前这一轮的 48 位子密钥 K1
  • 然后对 C1 和 D1 进行左移和压缩置换,获取下一轮的子密钥……一共进行16轮,得到 16 个 48 位的子密钥。
    这里用到了表PC-1、PC-2:
	public static final int PC_1[] = {
    
    
		      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 };
	
	public static final int PC_2[] = {
    
    
			  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 };
   /**
	 * 密钥置换 PC-1
	 * @param key  64位密钥
	 * @return     56位密钥
	 */
	public static BitSet Permutation_Cipher_1(long key) {
    
    
		BitSet temp=LongToBitSet(key);
		BitSet bitSet=new BitSet();
		for(int i=63;i>=8;i--) {
    
    
			bitSet.set(i,temp.get(64-PC_1[63-i]));
		}
		return bitSet;
	}
   /**
	 * 密钥置换 PC-2
	 * @param temp  56位密钥
	 * @return      48位密钥
	 */
	public static long Permutation_Cipher_2(BitSet temp) {
    
    
		long subkey=0;
		BitSet bitSet=new BitSet();
		for(int i=63;i>=16;i--) {
    
    
			bitSet.set(i,temp.get(64-PC_2[63-i]));
		}
		subkey=BitSetToLong(bitSet)>>>16;  //无符号右移16位
		return subkey;
	}

    当我们加密时所用到的子密钥需要每轮左移56位密钥若干位,生成子密钥时,左右部分分别一共左移16次(28位),加密前和加密后的56位密钥相同:

	public static final int shiftBits[] = {
    
    1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1};
   /**
	 * 循环左移
	 * 左右部分分别一共左移16次、28位,加密前和加密后的56位密钥相同
	 * @param bitSet  56位密钥
	 * @param round   加密的圈数
	 * @return        56位左移后的密钥
	 */
	public static BitSet LeftShift(BitSet bitSet,int round) {
    
    
		boolean temp=false;
		for(int i=0;i<shiftBits[round];i++) {
    
    
			temp=bitSet.get(63);
			for(int j=63;j>=36;j--) {
    
              //左半部分
				bitSet.set(j,bitSet.get(j-1));
			}
			bitSet.set(36,temp);
			temp=bitSet.get(35);
			for(int j=35;j>=8;j--) {
    
               //右半部分
				bitSet.set(j,bitSet.get(j-1));
			}
			bitSet.set(8,temp);
		}
		return bitSet;
	}

    加密和解密可以使用相同的算法,DES算法解密过程是加密的逆运算。加密和解密唯一不同的是秘钥的次序是相反的。就是说如果每一轮的加密秘钥分别是K1K2K3K16,那么解密秘钥就是K16K15K14K1。加密是秘钥循环左移,解密是秘钥循环右移。
    解密秘钥每次移动的位数是:0、1、2、2、2、2、2、2、1、2、2、2、2、2、2、1

/**
	 * 循环右移
	 * @param bitSet  56位密钥
	 * @param round   加密的圈数
	 * @return        56位左移后的密钥
	 */
	public static BitSet RightShift(BitSet bitSet,int round) {
    
    
		boolean temp=false;
		for(int i=0;i<shiftBits[round];i++) {
    
    
			temp=bitSet.get(8);
			for(int j=8;j<36;j++) {
    
              //左半部分
				bitSet.set(j,bitSet.get(j+1));
			}
			bitSet.set(35,temp);
			temp=bitSet.get(36);
			for(int j=36;j<64;j++) {
    
               //右半部分
				bitSet.set(j,bitSet.get(j+1));
			}
			bitSet.set(63,temp);
		}
		return bitSet;
	}
   /**
	 * 子密钥生成
	 * @param bitSet  56位密钥
	 * @return        48位密钥
	 */
	public static long Subkey_Generation(BitSet bitSet) {
    
    
		return Permutation_Cipher_2(bitSet);
	}

3.轮函数 f(R,Ki)

    f(R,Ki)函数的流程图如下:

在这里插入图片描述
密码函数f(R,Ki)接受两个输入:32 位的数据和 48 位的子密钥。然后:

  • 通过表 E 进行扩展置换,将输入的 32 位数据扩展为 48 位;
  • 将扩展后的 48 位数据与 48 位的子密钥进行异或运算;
  • 将异或得到的 48 位数据分成 8 个 6 位的块,每一个块通过对应的一个 S 表产生一个 4 位的输出。其中,每个 S 表都是 4 行 16 列。具体的置换过程如下:A1×2+A6×1 作为 Si 表中的行数(0–3),A2×8+A3×4+A4×2+A5×1 作为 Si 表的列数(0–15)。查出 Si表中行列所对应的整数,将该整数转换为一个 4 位的二进制数。
  • 把通过 S 表置换得到的 8 个 4 位连在一起,形成一个 32 位的数据。然后将该 32 位数据通过表 P 进行置换(称为P-置换),置换后得到一个仍然是 32 位的结果数据,这就是f(R,Ki)函数的输出。
	public static final int E[] = {
    
    
		   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 };
	
	public static final int S_BOX[][][] = {
    
    
			{
    
       {
    
    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} },
			{
    
       {
    
    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} }, 
			{
    
       {
    
    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} }, 
			{
    
       {
    
    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} },
			{
    
       {
    
    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} },
			{
    
       {
    
    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} }, 
			{
    
       {
    
    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} }, 
			{
    
       {
    
    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} } };
	

	public static final int P[] = {
    
    
			   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 };
   /**
	 * f函数-E盒扩展置换
	 * @param R  数据的右32位
	 * @return   48位数据
	 */
	public static long E_Permutation(long R) {
    
    
		BitSet bitSet=LongToBitSet(R);
		BitSet bitSet2=new BitSet();
		for(int i=47;i>=0;i--) {
    
    
			bitSet2.set(i,bitSet.get(32-E[47-i]));
		}
		return BitSetToLong(bitSet2);
	}
	
	/**
	 * f函数-S盒压缩
	 * @param num  48位数据
	 * @return     32位数据
	 */
	public static long S_Box_Compression(long num) {
    
    
		long result=0;
		BitSet bitSet=LongToBitSet(num);
		for(int i=0,j=47;i<8;i++,j-=6) {
    
    
			int a,b,c,d,e,f;
			if(bitSet.get(j)==true)   a=1; else a=0;
			if(bitSet.get(j-1)==true) b=1; else b=0;
			if(bitSet.get(j-2)==true) c=1; else c=0;
			if(bitSet.get(j-3)==true) d=1; else d=0;
			if(bitSet.get(j-4)==true) e=1; else e=0;
			if(bitSet.get(j-5)==true) f=1; else f=0;
			result=(result<<=4)|(S_BOX[i][a*2+f][b*8+c*4+d*2+e]);
		}
		return result;
	}
	
	/**
	 * f函数-P盒置换
	 * @param num  32位数据
	 * @return     32位数据
	 */
	public static long P_Permutation(long num) {
    
    
		BitSet temp=LongToBitSet(num);
		BitSet bitSet=new BitSet();
		for(int i=31;i>=0;i--) {
    
    
			bitSet.set(i,temp.get(32-P[31-i]));
		}
		return BitSetToLong(bitSet);
	}
	
	/**
	 * f函数
	 * @param R       数据的右32位
	 * @param subkey  48位密钥
	 * @return        32位数据
	 */
	public static long f(long R,long subkey) {
    
    
		return P_Permutation(S_Box_Compression(E_Permutation(R)^subkey));
	}

4.末置换

	public static final int IP_1[] = {
    
    
			  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 };
   /**
	 * 末置换
	 * @param num  64位数据
	 * @return     64位数据
	 */
	public static long Final_Initial_Permutation(long num) {
    
    
		BitSet temp=LongToBitSet(num);
		BitSet bitSet=new BitSet();
		for(int i=63;i>=0;i--) {
    
    
			bitSet.set(i, temp.get(64-IP_1[63-i]));
		}
		return BitSetToLong(bitSet);
	}

5.加密与解密

加密与解密

   /**
	 * 加密
	 * @param plaintext  64位明文
	 * @param key        64位密钥
	 * @return           64位密文
	 */
	public static long encrypt(long plaintext,long key) {
    
    
		plaintext=Initial_Permutation(plaintext);       //初始置换
		long ciphertext=0,temp=0;
		long L=(plaintext&0xFFFFFFFF00000000L)>>>32;    //数据的左32位
		long R=plaintext&0x00000000FFFFFFFFL;           //数据的右32位
		long subkey=0;
		BitSet bitSet=Permutation_Cipher_1(key);        //密钥置换 PC-1
		for(int i=0;i<16;i++) {
    
    
			bitSet=LeftShift(bitSet,i);                 //循环左移shiftBits[i]位
			subkey=Subkey_Generation(bitSet);           //子密钥生成
			temp=R;
			R=L^f(R, subkey);
			L=temp;
			//System.out.println("第"+i+"轮 L= "+L+" R= "+R+" subkey= "+subkey);
		}
		ciphertext=Final_Initial_Permutation((R<<32)|L);//左右交换,末置换
		return ciphertext;
	}
	
	/**
	 * 解密
	 * @param ciphertext  64位密文
	 * @param key         64位密钥
	 * @return            64位明文
	 */
	public static long decrypt(long ciphertext,long key) {
    
    
		ciphertext=Initial_Permutation(ciphertext);
		long plaintext=0,temp=0;
		long R=(ciphertext&0xFFFFFFFF00000000L)>>>32;
		long L=ciphertext&0x00000000FFFFFFFFL;
		long subkey=0;
		BitSet bitSet=Permutation_Cipher_1(key);
		for(int i=15;i>=0;i--) {
    
    
			subkey=Subkey_Generation(bitSet);
			bitSet=RightShift(bitSet,i);
			temp=L;
			L=R^f(L, subkey);
			R=temp;
			//System.out.println("第"+i+"轮 L= "+L+" R= "+R+" subkey= "+subkey);
		}
		plaintext=Final_Initial_Permutation((L<<32)|R);
		return plaintext;
	}

    下面可以看出DES的对称性

明文= 12345678
密钥= 123456780轮  L= 16744550   R= 2176033604 subkey= 881528001768341轮  L= 2176033604 R= 1178814781 subkey= 887024213450952轮  L= 1178814781 R= 4261055420 subkey= 2294378066423163轮  L= 4261055420 R= 2805545884 subkey= 2470042114600434轮  L= 2805545884 R= 2798241892 subkey= 2469354913751455轮  L= 2798241892 R= 174267677  subkey= 2469195888960986轮  L= 174267677  R= 1824122588 subkey= 1812237719084107轮  L= 1824122588 R= 3532822421 subkey= 1828768032599848轮  L= 3532822421 R= 577220743  subkey= 421393299932809轮  L= 577220743  R= 133981690  subkey= 5202631149343610轮 L= 133981690  R= 2911133244 subkey= 1677548959297211轮 L= 2911133244 R= 1702589147 subkey= 3436661443396912轮 L= 1702589147 R= 3260427994 subkey= 3412581596625313轮 L= 3260427994 R= 2266627553 subkey= 2986098990939414轮 L= 2266627553 R= 3943749637 subkey= 2767912887992715轮 L= 3943749637 R= 842228128  subkey= 8925178018297615轮 L= 2266627553 R= 3943749637 subkey= 8925178018297614轮 L= 3260427994 R= 2266627553 subkey= 2767912887992713轮 L= 1702589147 R= 3260427994 subkey= 2986098990939412轮 L= 2911133244 R= 1702589147 subkey= 3412581596625311轮 L= 133981690  R= 2911133244 subkey= 3436661443396910轮 L= 577220743  R= 133981690  subkey= 167754895929729轮  L= 3532822421 R= 577220743  subkey= 520263114934368轮  L= 1824122588 R= 3532822421 subkey= 421393299932807轮  L= 174267677  R= 1824122588 subkey= 1828768032599846轮  L= 2798241892 R= 174267677  subkey= 1812237719084105轮  L= 2805545884 R= 2798241892 subkey= 2469195888960984轮  L= 4261055420 R= 2805545884 subkey= 2469354913751453轮  L= 1178814781 R= 4261055420 subkey= 2470042114600432轮  L= 2176033604 R= 1178814781 subkey= 2294378066423161轮  L= 16744550   R= 2176033604 subkey= 887024213450950轮  L= 16742485   R= 16744550   subkey= 88152800176834
明文= 12345678

运行结果:
加密过程如下:
明文(String)= Computer
明文(Decimal)= 4859222852730381682
明文(HEX)= 436F6D7075746572
密钥(String)= software
密钥(Decimal)= 8317979687181709925
密钥(HEX)= 736F667477617265
密文(Decimal)= 1970734137907446511
密文(HEX)= 1B597441CCB582EF
密文(Base64)= MWI1OTc0NDFjY2I1ODJlZg==
------发送base64编码的密文------
解密过程如下:
密文(Base64)= MWI1OTc0NDFjY2I1ODJlZg==
密文(HEX)= 1B597441CCB582EF
密文(Decimal)= 1970734137907446511
密钥(HEX)= 736F667477617265
密钥(Decimal)= 8317979687181709925
密钥(String)= software
明文(HEX)= 436F6D7075746572
明文(Decimal)= 4859222852730381682
明文(String)= Computer

三、源码下载

    由于时间问题,该DES的实现只能加密64bit的数据。至于加密任意长度的数据,其实只要分组就行了,每组64bit,不足64bit的后面补0,最后将加密的结果合起来就行了。解密也是分组解密。
同时,该DES的实现只能加密并解密ASCII字符。但如果想实现加密并解密中文字符的话,可以先将字符串转为字节,加密并解密,最后注意一下编码的问题。

使用BitSet实现DES加密算法(Java)

参考:
DES加密算法的C++实现:https://blog.csdn.net/lisonglisonglisong/article/details/41777413
Data Encryption Standard:https://www.tutorialspoint.com/cryptography/data_encryption_standard.htm
证明:DES算法解密过程是加密的逆运算:https://www.jianshu.com/p/559bd0fc97c5

猜你喜欢

转载自blog.csdn.net/H_X_P_/article/details/104122168