DES加密
DES加密的过程
具体的原理可以参考:
DES实现
文章给出了DES加密算法的一个实现。未做任何优化,仅供学习参考,计算速度跟专业的库相比应该有很大差距。虽然密码学算法是安全的,但是工程实现中可能存在其他攻击,没有足够的信心,不要使用自己写的加密算法做加密。
/**
* @author miracle
* @date 2022/8/18 10:47
* @description: <br/>
* DES加密算法
* 可使用下面网站上的例子测试: https://blog.csdn.net/m0_51088812/article/details/124407368
* - 明文:String plaintext = "0000000100100011010001010110011110001001101010111100110111101111";
* - 密钥:String key = "0001001100110100010101110111100110011011101111001101111111110001";
*/
public class Main
{
// ---------------------------------------------------- 准备工作 ------------------------------------------------------------------
//记录所有的子秘钥
String[] K = new String[16];
//记录左移次数 ,该数组长度位16
int[] leftMove = new int[]{
1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1
};
// 8个 S盒 (8*16*4)
int[][][] SBox = {
//S1
{
{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}
}
};
//p盒置换
int[][] P = 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}
};
//E扩展
int[][] Extend = 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}
};
//秘钥置换
int[][] keySwap = 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}
};
//密文初始置换
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}
};
//密文逆置换
int[][] IP_1 = 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}
};
//64->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}
};
//56->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}
};
// ---------------------------------------------------- 核心函数 ------------------------------------------------------------------
/**
* 初始置换,即IP置换
* <p>
* 这里给除一个测试字符串,用来测试IP置换是否正确:
* - IP置换前:plaintext = "0110001101011100001110111011010100010111100000010011100000100110"
* - IP置换后:plaintext = "0000001101011110100110100011110100101000110011010100011010010101"
*
* @param plaintext 明文
* @return 明文经过IP置换后得到的结果
*/
private String IP(String plaintext){
return transfer(plaintext,IP);
}
// 初始逆置换,和初始置换类似
private String IP_1(String p){
return transfer(p,IP_1);
}
// ----------------------------------- 密钥生成算法 -------------------------------------
// 很多变换是类似的,用该函数表示这一类变换
private String transfer(String str,int[][] matrix){
StringBuilder builder = new StringBuilder(str);
int idx = 0;
for(int[] rows : matrix){
for(int row : rows){
builder.setCharAt(idx++,str.charAt(row - 1));
}
}
return builder.toString();
}
/**
* 对密钥进行 PC_1置换,去除64bit数据中的8bit校验位,得到 56bit的密钥
* <p>
* 这里给除一个测试字符串,用来测试 PC_1 置换是否正确:
* - PC_1置换前:key = "0001001100110100010101110111100110011011101111001101111111110001"
* - PC_1置换后:key = "11110000110011001010101011110101010101100110011110001111"
*
* @param key 含校验位的密钥
* @return 不含校验位的密钥
*/
private String PC_1(String key){
return transfer(key,PC_1).substring(0,56);
}
/**
* 对 PC_1的到的结果进行 PC_2置换,得到 48bit的子密钥
* <p>
* 这里给除一个测试字符串,用来测试 置换是否正确:
* - 输入一: C = "1111000011001100101010101111"
* - 输入二: D = "0101010101100110011110001111"
* - 返回的: K = ""
*
* @param C PC_1置换结果的左半部分(即左边28bit)
* @param D PC_1置换结果的右半部分(即右边28bit)
* @return 返回一个子密钥
*/
private String PC_2(String C,String D){
String key = C + D;
return transfer(key,PC_2).substring(0,48);
}
private String[] getSubKeys(String key){
// PC_1去掉校验位
key = PC_1(key);
//
String C = key.substring(0,28);
String D = key.substring(28,56);
//记录所有的子秘钥
String[] K = new String[16];
// 依次获取子密钥
for(int i = 0; i < leftMove.length; i++){
// 循环左移
int move_len = leftMove[i];
C = leftMove(C,move_len);
D = leftMove(D,move_len);
// PC_2产生子密钥
K[i] = PC_2(C,D);
}
return K;
}
// 反转指定位置的字符串
public String reverseStr(String str,int start,int end){
char[] chars = str.toCharArray();
while(start < end){
char temp = chars[start];
chars[start] = chars[end];
chars[end] = temp;
start++;
end--;
}
return new String(chars);
}
// 将字符串向左循环移动 n 位
public String leftMove(String str,int n){
str = reverseStr(str,0,n - 1);
str = reverseStr(str,n,str.length() - 1);
return reverseStr(str,0,str.length() - 1);
}
// ----------------------------------- F(Ri,K(i+1))函数 -------------------------------------
/**
* F函数
*
* @param Ri 输入的32bit数据
* @param subKey 用来异或的子密钥
* @return 经F桉树处理后的数据
*/
private String F(String Ri,String subKey){
// 1. E扩展
String extend = Extend(Ri);
// 2. 与子密钥异或
String xor = xor(extend,subKey);
// 3. S盒压缩
String sBox = SBox(xor);
// 4. P置换后返回
return Permute(sBox);
}
private String xor(String extend,String subKey){
StringBuilder builder = new StringBuilder();
for(int i = 0; i < extend.length(); i++){
if(extend.charAt(i) == subKey.charAt(i)){
builder.append(0);
}
else{
builder.append(1);
}
}
return builder.toString();
}
/**
* E 扩展,将 32bit的Ri扩展为48bit
* <p>
* 这里给除一个测试字符串,用来测试 PC_1 置换是否正确:
* - 扩展前:Ri = "11010001001101000010001100111011"
* - 扩展后:Ri = "111010100010100110101000000100000110100111110111"
*
* @param Ri Feistel体制中,F函数的一个输入
* @return 返回E扩展后的 Ri
*/
private String Extend(String Ri){
StringBuilder builder = new StringBuilder(Ri).append(new char[16]);
Ri = builder.toString();
return transfer(Ri,Extend);
}
/**
* S盒压缩
* 将输入的48bit的Ri分成8组,每组含有6个字符。
* 1. 对于一个组号为i的分组中的六个字符,取首字符和尾字符组成一个二进制的数字,将其转化为十进制作为S盒的行号row
* 2. 剩下的中间4个字符组成的二进制数转为十进制作为S盒的列号col
* 3. 则 将 SBox[i][row][col] 转化为长度为4的二进制(不够四位,高位补零),这样就把6个字符压缩为4个字符
* 4. 重复1,2,3将八组全部转换后,即可将48bit的数据压缩为32bit
*
* @param Ri E扩展后的数据与对应的子密钥异或得到的结果
* @return 压缩后的数据
*/
private String SBox(String Ri){
StringBuilder res = new StringBuilder();
String[] group = new String[8];
for(int i = 0; i < 8; i++){
group[i] = Ri.substring(i * 6,i * 6 + 6);
}
int i = 0;
for(String str : group){
String rowStr = new String(new char[]{str.charAt(0),str.charAt(5)});
int row = Integer.parseInt(rowStr,2);
String colStr = str.substring(1,5);
int col = Integer.parseInt(colStr,2);
int num = SBox[i++][row][col];
res.append(toFixedLengthBinary(num,4));
}
return res.toString();
}
/**
* 将一个给定的整数num转换为长度为 size 的二进制,如果转化后长度不够,在高位补零
*
* @param num 待转化的十进制数
* @param size 转化后的二进制数长度
* @return num转化而来的定长二进制数
*/
private String toFixedLengthBinary(int num,int size){
StringBuilder builder = new StringBuilder();
for(int i = size - 1; i >= 0; i--){
builder.append(num >>> i & 1);
}
return builder.toString();
}
/**
* P置换
* 对S盒处理后的数据进行P置换得到F函数的输出
*
* @param RK S盒变换后得到的字符串
* @return P置换后得到的字符串R(i + 1)
*/
private String Permute(String RK){
return transfer(RK,P);
}
// ----------------------------------- Feistel结构 -------------------------------------
/**
* 单轮 Feistel
*
* @param ip 经过初始置换(IP)得到的密文
* @return 单论Feistel置换后的密文
*/
private String Feistel(String ip,String subKey){
// 将 ip 分为左右两部分,每部分长度32bit
String L = ip.substring(0,32);
String R = ip.substring(32,64);
// 获取一轮Feistel后的结果
return R + xor(L,F(R,subKey));
}
// ----------------------------------- 加密解密函数 -------------------------------------
/**
* DES加密函数
*
* @param plaintext 明文
* @param key 密钥
* @return 密文
*/
private String encryptCore(String plaintext,String key){
// 获取16个子密钥
String[] subKeys = getSubKeys(key);
// 1. 初始IP置换
String ip = IP(plaintext);
// 2. 16轮 Feistel 迭代
for(int i = 0; i < 16; i++){
ip = Feistel(ip,subKeys[i]);
}
// 3. 32bit互换
ip = ip.substring(32,64) + ip.substring(0,32);
// 3. IP逆置换得到密文并返回
return IP_1(ip);
}
private String decryptCore(String cipher,String key){
// 获取16个子密钥
String[] subKeys = getSubKeys(key);
// 1. 初始IP置换
String ip = IP(cipher);
// 2. 16轮 Feistel 迭代
for(int i = 15; i >= 0; i--){
ip = Feistel(ip,subKeys[i]);
}
// 3. 32bit互换
ip = ip.substring(32,64) + ip.substring(0,32);
// 3. IP逆置换得到密文并返回
return IP_1(ip);
}
// ----------------------------------- 加密解密数据 -------------------------------------
/**
* 加密字符串
* 可以优化,子密钥不用每次都求,这里不再优化
*
* @param plaintext
* @param key
* @return
*/
private String encrypt(String plaintext,String key){
byte[] bytes = plaintext.getBytes();
int remainder = bytes.length % 8;
StringBuilder res = new StringBuilder();
if(remainder != 0){
// 先将前面被八整除的字节加密
for(int i = 0; i < bytes.length - remainder; ){
StringBuilder p = new StringBuilder();
for(int j = 0; j < 8; j++){
p.append(toFixedLengthBinary(bytes[i++],8));
}
String c = encryptCore(p.toString(),key);
res.append(c);
}
// 剩下的字节不够8个,用 "$" 即 36 填充到 8 个,并加密
StringBuilder temp = new StringBuilder();
for(int i = bytes.length - remainder; i < bytes.length; i++){
temp.append(toFixedLengthBinary(bytes[i],8));
}
for(int i = 0; i < 8 - remainder; i++){
temp.append(toFixedLengthBinary(36,8));
}
String c = encryptCore(temp.toString(),key);
res.append(c);
}
else{
for(int i = 0; i < bytes.length; ){
StringBuilder p = new StringBuilder();
for(int j = 0; j < 8; j++){
p.append(toFixedLengthBinary(bytes[i++],8));
}
String c = encryptCore(p.toString(),key);
res.append(c);
}
}
return res.toString();
}
private String decrypt(String cipher,String key){
StringBuilder res = new StringBuilder();
for(int i = 0; i < cipher.length();){
StringBuilder c = new StringBuilder();
for(int j = 0; j < 64; j++){
c.append(cipher.charAt(i++));
}
String p = decryptCore(c.toString(),key);
String string = binaryToString(p);
res.append(string);
}
return res.toString();
}
/**
* 将字符串表示的64bit带符号的二进制转化为byte
* @param binary
* @return
*/
private String binaryToString(String binary){
int i = 0;
int len = binary.length();
byte[] bytes = new byte[8];
int idx = 0;
while(i < len){
StringBuilder builder = new StringBuilder();
boolean isPositve = true;
isPositve = binary.charAt(i++) == 1 ? false : true;
for(int j = 0; j < 7; j++){
builder.append(binary.charAt(i++));
}
if(isPositve){
bytes[idx++] = Byte.parseByte(builder.toString(),2);
}else {
bytes[idx++] = Byte.parseByte("-" + builder.toString(),2);
}
}
return new String(bytes);
}
// ----------------------------------- 主函数 -------------------------------------
public static void main(String[] args){
Main des = new Main();
// 加密核心函数的测试
String plaintext = "0000000100100011010001010110011110001001101010111100110111101111";
String key = "0001001100110100010101110111100110011011101111001101111111110001";
String cipher = des.encryptCore(plaintext,key);
System.out.println("加密后的密文: " + cipher);
String decrypt = des.decryptCore(cipher,key);
System.out.println("解密后的原文: " + decrypt);
// 加密字符串测试
plaintext = "0123456789 DES I love you!";
String c = des.encrypt(plaintext,key);
System.out.println("将字符串加密得: " + c);
String p = des.decrypt(c,key);
System.out.println("将字符串解密得: " + p);
System.out.println("注意,解密过程中,最后一部分不足16byte,末尾补$");
}
}