2024-07-14  2024-07-14    2830 字  6 分钟

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,末尾补$");
    }
}