0
点赞
收藏
分享

微信扫一扫

DES加密原理与java实现

想溜了的蜗牛 2022-04-24 阅读 82
java后端

1、概述

DES是对称性加密里常见的一种,所谓对称性加密,加密和解密秘钥相同。对称性加密一般会按照固定长度,把待加密字符串分成块。不足一整块则最后特殊填充字符

2、特点

​ DES算法具有极高安全性,除了用穷举搜索法对DES算法进行攻击外,还没有发现更有效的办法。而56位长的密钥的穷举空间为256,这意味着如果一台计算机的速度是每一秒钟检测一百万个密钥,则它搜索完全部密钥就需

要将近2285年的时间,可见,这是难以实现的。然而,这并不等于说DES是不可破解的。而实际上,随着硬件技

术和Internet的发展,其破解的可能性越来越大,而且,所需要的时间越来越少。使用经过特殊设计的硬件并行处

理要几个小时。为了克服DES密钥空间小的缺陷,人们又提出了三重DES的变形方式

3、秘钥初始化

​ ■ 取16进制秘钥K为:

	K = 133457799BBCDFF1

​ ■ 可以得到 K 的二进制形式:

	K = 00010011 00110100 01010111 01111001 10011011 10111100 11011111 11110001

​ 这里虽然得到64位秘钥,但是我们需要去掉为8的整数倍的奇偶校验位,共8个,这样得到56位秘钥。

​ ■ 去掉 8 的整数倍的奇偶校验位后,得到 K 为:

	K = 0001001 0011010 0101011 0111100 1001101 1011110 1101111 1111000

​ ■ 然后将这个 K 根据表格 PC-1 进行变换,表格 PC-1 如下:

​ 注意:这个表格以及后面所用到的表格都是固定的,每个 DES 加密都是这些根据这些表格来完成加密的

在这里插入图片描述

​ 上图置换表一共有56个数,此表格的作用是将原来64位秘钥数据 K 的第57位换到第1位,依次类推

置换表中的第2个数是49:即将 K 的第49位将被换到第2位

​ 得到56位新秘钥:

	K+ = 1111000 0110011 0010101 0101111 0101010 1011001 1001111 0001111 

​ ■ 然后,我们将这个密钥拆分为左右两个部分,C0 和 D0,每半边都有28位。

​ 比如,对于新密钥 K+,我们得到 C0、D0

	K+ = 1111000 0110011 0010101 0101111 0101010 1011001 1001111 0001111 
	C0 = 1111000 0110011 0010101 0101111
	D0 = 0101010 1011001 1001111 0001111

4、秘钥迭代

​ des 加密过程需要用到 16 个秘钥,称这些秘钥为子秘钥,具体子秘钥迭代过程如下:

​ ■ 对于上一步生成的 C0、D0,将 C0、D0 左移 1 位,并将第 1 位移补到最后一位,得到 C1、D1;依次类推,将C0、D0 移动 16 位,总共得到 C0,C1…C16 以及 D0,D1…D16

​ 比如,对于原始秘钥 C0、D0,我们得到:

C0 = 1111000011001100101010101111		D0 = 0101010101100110011110001111
C1 = 1110000110011001010101011111		D1 = 1010101011001100111100011110
C2 = 1100001100110010101010111111		D2 = 0101010110011001111000111101
C3 = 0000110011001010101011111111		D3 = 0101011001100111100011110101
C4 = 0011001100101010101111111100		D4 = 0101100110011110001111010101
C5 = 1100110010101010111111110000		D5 = 0110011001111000111101010101
C6 = 0011001010101011111111000011		D6 = 1001100111100011110101010101
C7 = 1100101010101111111100001100		D7 = 0110011110001111010101010110
C8 = 0010101010111111110000110011		D8 = 1001111000111101010101011001
C9 = 0101010101111111100001100110		D9 = 0011110001111010101010110011
C10 = 0101010111111110000110011001		D10 = 1111000111101010101011001100
C11 = 0101011111111000011001100101		D11 = 1100011110101010101100110011
C12 = 0101111111100001100110010101		D12 = 0001111010101010110011001111
C13 = 0111111110000110011001010101		D13 = 0111101010101011001100111100
C14 = 1111111000011001100101010101		D14 = 1110101010101100110011110001
C15 = 1111100001100110010101010111		D15 = 1010101010110011001111000111
C16 = 1111000011001100101010101111		D16 = 0101010101100110011110001111

​ ■ 现在就可以得到第n轮的新秘钥Kn(1<=n<=16)了, 具体做法是,对每一对拼合后的子秘钥Cn、Dn,按照表PC-2执行变换

在这里插入图片描述

​ 每对子秘钥有56位,但是PC-2仅仅使用其中48位。

​ 于是,第 n 轮新秘钥 Kn 的第一位来自组合秘钥 Cn、Dn 的第14位,第2位来自第17位,以此类推,直到新秘钥的第48位来自组合秘钥的第32位。

​ 例如:K1 的第 1 位 来自 C1、D1 的第 14 位,第 2 位来自第 17 位,依次类推

​ 比如, 对于第一轮的组合秘钥,我们有:

		C1D1 = 1110000 1100110 0101010 1011111 1010101 0110011 0011110 0011110

​ 通过PC-2的变换后,得到:

		K1 = 000110 110000 001011 101111 111111 000111 000001 110010

​ ■ 同理,对于其他秘钥,我们得到:

        K2 = 011110 011010 111011 011001 110110 111100 100111 100101
        K3 = 010101 011111 110010 001010 010000 101100 111110 011001
        K4 = 011100 101010 110111 010110 110110 110011 010100 011101
        K5 = 011111 001110 110000 000111 111010 110101 001110 101000
        K6 = 011000 111010 010100 111110 010100 000111 101100 101111
        K7 = 111011 001000 010010 110111 111101 100001 100010 111100
        K8 = 111101 111000 101000 111010 110000 010011 101111 111011
        K9 = 111000 001101 101111 101011 111011 011110 011110 000001
        K10 = 101100 011111 001101 000111 101110 100100 011001 001111
        K11 = 001000 010101 111111 010011 110111 101101 001110 000110
        K12 = 011101 010111 000111 110101 100101 000110 011111 101001
        K13 = 100101 111100 010111 010001 111110 101011 101001 000001
        K14 = 010111 110100 001110 110111 111100 101110 011100 111010
        K15 = 101111 111001 000110 001101 001111 010011 111100 001010
        K16 = 110010 110011 110110 001011 000011 100001 011111 110101

​ 秘钥的生成到此为止。生成的秘钥在后面由明文生成密文时需要用到

5、加密

​ 因为DES是一种block cipher,一个block要8个字节,所以要加密的东西要分成8字节的整数倍,不足的就填充

​ ■ 比如明文,M为:

	M = 0123456789ABCDEF

​ ■ 这里的M是16进制的,将M写成二进制,我们得到一个64位的区块,每个64位的区块被分为2个32位的部分,左半部分 L 和右半部分 R :

	M = 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
	L = 0000 0001 0010 0011 0100 0101 0110 0111
	R = 1000 1001 1010 1011 1100 1101 1110 1111

​ ■ 对应 M,我们通过下面表格进行初始变换, IP是重新变换数据M的每一位产生的

在这里插入图片描述

​ 比如,对M的区块,执行初始变换,得到 IP,并将其分为 32 位的左半边和 32 位的右半边,得到:

	M  = 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
	IP = 1100 1100 0000 0000 1100 1100 1111 1111 1111 0000 1010 1010 1111 0000 1010 1010
	L0 = 1100 1100 0000 0000 1100 1100 1111 1111
	R0 = 1111 0000 1010 1010 1111 0000 1010 1010

​ ■ 接着执行16个迭代,对 1 <= n <= 16,生成 L1、L2…L16 以及 R0、R1…R16,具体生成步骤如下:

	Ln = Rn-1	表示 L1 = R0,L2 = R1,依次类推
	Rn = Ln-1 + f(Rn-1, Kn)		表示 R1 = L0 + f(R0, K1),R2 = L1 + f(R1, K2),依次类推
	其中:Kn 是前面生成的子秘钥

​ 比如,对于n=1,我们有:

	K1 = 000110 110000 001011 101111 111111 000111 000001 110010
	L1 = R0 = 1111 0000 1010 1010 1111 0000 1010 1010
	R1 = L0 + f(R0,K1)

​ 剩下就是 f 函数是如何工作的了,步骤如下:

​ ● 首先扩展每个Rn-1, 将其从32位扩展到48位, 这是通过使用一张表来将 Rn-1 由 32 位变为 48 位。我们称这个过程为函数E。也就是说函数E(Rn-1)输入32位输出48位 ,表格如下:

在这里插入图片描述

​ 这个表格有 48 个数据,里面的数值是从 1 到 32,也就是说,可以将 32 位数变为 48 位数。比如对于 R0,新产生的数的第1、2、3位分别为 R0 的第 32、1、2 位,依次类推

​ 比如:给定R0,我们可以计算出E(R0) :

	R0 =    1111 0000 1010 1010 1111 0000 1010 1010
	E(R0) = 011110 100001 010101 010101 011110 100001 010101 010101
		● 接着在f函数中,我们对输出E(R<sub>n-1</sub>)和秘钥 K<sub>n</sub> 执行异或运算: K<sub>n</sub>⊕E(R<sub>n-1</sub>) 

​ 比如对 K1,E(R0),我们有:

	K1 = 	   000110 110000 001011 101111 111111 000111 000001 110010
	E(R0) =    011110 100001 010101 010101 011110 100001 010101 010101
	K1⊕E(R0) = 011000 010001 011110 111010 100001 100110 010100 100111

​ 这样经过 16 轮迭代后,会产生 L1、L2、…、L6 以及 R1、R2、…、R16

​ ● 到这里我们还没有完成 f 函数的运算,我们仅仅使用一张表将Rn-1从32位扩展为48位,并且对这个结果和秘钥Kn执行了异或运算。我们现在有了 48 位的结果,或者说 8 组 6 比特位数据。我们现在要对每组的 6 比特执行一些奇怪的操作:

​ 我们将这些 6 比特位的数据作为一张被称为“S盒”的表格的地址, 每组 6 比特数据都将给我们一个位于

不同S盒中的地址,S盒存放着一个 4 比特位的数据, 这个 4 比特的数字将会替换掉原来的6个比特。最终结果就

是8组6比特的数据被转换为8组4比特(一共32位)的数据。

​ 将上一步的48位的结果写成如下形式:

		Kn+E(Rn-1)=B1B2B3B4B5B6B7B8

​ 每个 Bn 都是一个6比特的分组,我们现在计算:

		S1(B1)S2(B2)S3(B3)S4(B4)S5(B5)S6(B6)S7(B7)S8(B8)

​ 其中,Sn(Bn) 指的是第 n 个S盒的输出

​ 其中 S1、S2、…、S8 的表格如下:

​ 如果 S1 是定义在这张表上的函数,B 是一个 6 位的块,那么计算 S1(B)的方法是:

​ B的第一位和最后一位组合起来的二进制数决定一个介于0和3之间的十进制数(或者二进制00到11之

间)。设这个数为 i,B的中间4位二进制数代表一个介于0到15之间的二进制数(二进制0000到1111)。设这个

数为 j。查表找到第 i 行第 j 列的那个数,这个是一个介于0和15之间的数,并且它是能由一个唯一的4位区块表示

的。这个区块就是函数S1输入B得到的输出S1(B)。比如,对输入B=011011,第一位是0,最后一位是1,决定了行

号是01,也就是十进制的1,中间4位是1101,也就是十进制的13,查表第一行第13列我们得到数字5。这就决定

了输出 5 的二进制是0101,所以输出就是0101,即S1(011011) = 0101

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

​ 例子:对 n = 1,我们得到这8个S盒的输出:

		K1+E(R0)=011000 010001 011110 111010 100001 100110 010100 100111

​ 即:

S1(B1)S2(B2)S3(B3)S4(B4)S5(B5)S6(B6)S7(B7)S8(B8)=0101 1100 1000 0010 1011 0101 1001 0111

​ ● 函数 f 的最后一步就是对S盒的输出进行一个变换来产生最终值:

​ f=P(S1(B1)S2(B2)S3(B3)S4(B4)S5(B5)S6(B6)S7(B7)S8(B8))

​ 变换P由如下表格定义。P 输入32位数据,通过下表产生32位输出:

在这里插入图片描述

​ 比如对8个S盒的输出:

​ S1(B1)S2(B2)S3(B3)S4(B4)S5(B5)S6(B6)S7(B7)S8(B8)= 0101 1100 1000 0010 1011 0101 1001 0111

​ 我们得到:

​ f = 0010 0011 0100 1010 1010 1001 1011 1011

​ 那么:

R1 = L0+f(R0,K1) = 1100 1100 0000 0000 1100 1100 1111 1111 

​ 在下一轮迭代中,我们的 L2 = R1,这就是我们刚刚计算的结果。之后,我们必须计算R2 = L1+f(R1,K2),一直完

成16个迭代之后,我们有了区块L16和R16。接着我们逆转两个区块的顺序得到一个64位的区块: R16L16 , 然

后,我们对其执行一个最终的 IP-1,其定义如下:

在这里插入图片描述

​ 也就是说,该变换的输出的第一位是输入的第40位,第二位是输入的8位,一直到将输入的第25位作为输出的最后一位。比如,我们使用上述方法得到了第16轮的左右两个区块:

	L16 = 0100 0011 0100 0010 0011 0010 0011 0100
	R16 = 0000 1010 0100 1100 1101 1001 1001 0101

​ 我们将两个区块调换位置,然后执行最终变换:

	R16L16 = 00001010 01001100 11011001 10010101 01000011 01000010 00110010 00110100
	IP-1 =   10000101 11101000 00010011 01010100 00001111 00001010 10110100 00000101

​ 写成16进制得到:

	85E813540F0AB405

​ 这就是明文 M = 0123456789ABCDEF 的密文:85E813540F0AB405

6、解密

​ 解密就是加密的反过程,执行上述步骤,只不过在那16轮迭代中,调转左右子秘钥的位置而已

7、java代码

package cn.com.sinosafe.common.security;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.io.*;
import java.security.Key;
import java.util.Base64;

/**
 * @Author: lipeng13
 * @Description:
 * @Date: Created in 2022/4/24 10:11
 * @Modified By:
 */
public class DESUtil {
    /**
     * 偏移变量,固定占8位字节
     */
    private final static String IV_PARAMETER = "12345678";
    /**
     * 密钥算法
     */
    private static final String ALGORITHM = "DES";
    /**
     * 加密/解密算法-工作模式-填充模式
     */
    private static final String CIPHER_ALGORITHM = "DES/CBC/PKCS5Padding";
    /**
     * 默认编码
     */
    private static final String CHARSET = "utf-8";

    /**
     * 生成key
     *
     * @param password
     * @return
     * @throws Exception
     */
    private static Key generateKey(String password) throws Exception {
        DESKeySpec dks = new DESKeySpec(password.getBytes(CHARSET));
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
        return keyFactory.generateSecret(dks);
    }


    /**
     * DES加密字符串
     *
     * @param password 加密密码,长度不能够小于8位
     * @param data 待加密字符串
     * @return 加密后内容
     */
    public static String encrypt(String password, String data) {
        if (password== null || password.length() < 8) {
            throw new RuntimeException("加密失败,key不能小于8位");
        }
        if (data == null)
            return null;
        try {
            Key secretKey = generateKey(password);
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            IvParameterSpec iv = new IvParameterSpec(IV_PARAMETER.getBytes(CHARSET));
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
            byte[] bytes = cipher.doFinal(data.getBytes(CHARSET));

            //JDK1.8及以上可直接使用Base64,JDK1.7及以下可以使用BASE64Encoder
            //Android平台可以使用android.util.Base64
            return new String(Base64.getEncoder().encode(bytes));

        } catch (Exception e) {
            e.printStackTrace();
            return data;
        }
    }

    /**
     * DES解密字符串
     *
     * @param password 解密密码,长度不能够小于8位
     * @param data 待解密字符串
     * @return 解密后内容
     */
    public static String decrypt(String password, String data) {
        if (password== null || password.length() < 8) {
            throw new RuntimeException("加密失败,key不能小于8位");
        }
        if (data == null)
            return null;
        try {
            Key secretKey = generateKey(password);
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            IvParameterSpec iv = new IvParameterSpec(IV_PARAMETER.getBytes(CHARSET));
            cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
            return new String(cipher.doFinal(Base64.getDecoder().decode(data.getBytes(CHARSET))), CHARSET);
        } catch (Exception e) {
            e.printStackTrace();
            return data;
        }
    }

    /**
     * DES加密文件
     *
     * @param srcFile  待加密的文件
     * @param destFile 加密后存放的文件路径
     * @return 加密后的文件路径
     */
    public static String encryptFile(String password, String srcFile, String destFile) {

        if (password== null || password.length() < 8) {
            throw new RuntimeException("加密失败,key不能小于8位");
        }
        try {
            IvParameterSpec iv = new IvParameterSpec(IV_PARAMETER.getBytes(CHARSET));
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, generateKey(password), iv);
            InputStream is = new FileInputStream(srcFile);
            OutputStream out = new FileOutputStream(destFile);
            CipherInputStream cis = new CipherInputStream(is, cipher);
            byte[] buffer = new byte[1024];
            int r;
            while ((r = cis.read(buffer)) > 0) {
                out.write(buffer, 0, r);
            }
            cis.close();
            is.close();
            out.close();
            return destFile;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    /**
     * DES解密文件
     *
     * @param srcFile  已加密的文件
     * @param destFile 解密后存放的文件路径
     * @return 解密后的文件路径
     */
    public static String decryptFile(String password, String srcFile, String destFile) {
        if (password== null || password.length() < 8) {
            throw new RuntimeException("加密失败,key不能小于8位");
        }
        try {
            File file = new File(destFile);
            if (!file.exists()) {
                file.getParentFile().mkdirs();
                file.createNewFile();
            }
            IvParameterSpec iv = new IvParameterSpec(IV_PARAMETER.getBytes(CHARSET));
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, generateKey(password), iv);
            InputStream is = new FileInputStream(srcFile);
            OutputStream out = new FileOutputStream(destFile);
            CipherOutputStream cos = new CipherOutputStream(out, cipher);
            byte[] buffer = new byte[1024];
            int r;
            while ((r = is.read(buffer)) >= 0) {
                cos.write(buffer, 0, r);
            }
            cos.close();
            is.close();
            out.close();
            return destFile;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) throws Exception {
        String encryptPwd = encrypt("e10adc3949ba59abbe56e057f20f883e", "123456");
        System.out.println(encryptPwd);
        String decryptPwd = decrypt("e10adc3949ba59abbe56e057f20f883e", encryptPwd);
        System.out.println(decryptPwd);
    }

}

1672021
29122817
1152326
5183110
282414
322739
1913306
2211425
585042342618102
605244362820124
625446383022146
645648403224168
57494133251791
595143352719113
615345372921135
635547393123157
举报

相关推荐

0 条评论