一、概述
1、 对称加密(Symmetric Cryptography)
对称加密是最快速、最简单的一种加密方式,加密(encryption)与解密(decryption)用的是同样的密钥(secret key)。
对称加密通常使用的是相对较小的密钥,一般小于256 bit。因为密钥越大,加密越强,但加密与解密的过程越慢。
对称加密的一大缺点是密钥的管理与分配,换句话说,如何把密钥发送到需要解密你的消息的人的手里是一个问题。在发送密钥的过程中,密钥有很大的风险会被黑客们拦截。现实中通常的做法是将对称加密的密钥进行非对称加密,然后传送给需要它的人。
对称加密,加密速度非常快,适合经常发送数据的场合。缺点是密钥的传输比较麻烦
2、 非对称加密(Asymmetric Cryptography)
非对称加密为数据的加密与解密提供了一个非常安全的方法,它使用了一对密钥,公钥(public key)和私钥(private key)。私钥只能由一方安全保管,不能外泄,而公钥则可以发给任何请求它的人。非对称加密使用这对密钥中的一个进行加密,而解密则需要另一个密钥。
非对称算法,加密解密的速度比较慢,适合偶尔发送数据的场合。优点是密钥传输方便。常见的非对称加密算法为RSA、ECC和EIGamal
二、DES
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 位的右半边,得到:
例子:对 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);
}
}
三、AES
1、概述
AES 属于对称加密算法;加密、解密使用相同的密钥, 用来替代原先的DES。
AES 算法在对明文加密的时候,并不是把整个明文一股脑加密成一整段密文,而是把明文拆分成一个个独立的
明文块,每一个明文块长度128 bit。
这些明文块经过 AES 加密器的复杂处理,生成一个个独立的密文块,这些密文块拼接在一起,就是最终的AES 加密结果 ,如下图所示:
2、明文填充
假如一段明文长度是192 bit,如果按每128 bit一个明文块来拆分的话,第二个明文块只有64 bit,不足128
bit。这时候怎么办呢?就需要对明文块进行填充(Padding)。 需要注意,如果加密使用了某种填充方式,解密
也需要使用相同的填充方式
填充有六种:NoPadding,PKCS#5,PKCS#7,ISO 10126,ANSI X9.23 和 ZerosPadding
■ NoPadding: 顾名思义,就是不填充。缺点就是只能加密长为128bits倍数的信息,一般不会使用
■ PKCS#7 & PKCS#5: 缺几个字节就填几个缺的字节数。
如果明文块少于16个字节(128bit),在明文块末尾补足相应数量的字符,且每个字节的值等于缺少的字符
数。比如明文:{1,2,3,4,5,a,b,c,d,e},缺少6个字节,则补全为{1,2,3,4,5,a,b,c,d,e,6,6,6,6,6,6}
■ ZerosPadding: 全部填充0x00
,无论缺多少全部填充0x00
■ ISO 10126: 最后一个字节是填充的字节数(包括最后一字节),其他全部填随机数
■ ANSI X9.23: 跟ISO 10126很像,只不过ANSI X9.23其他字节填的都是0而不是随机数
3、AES的基本结构
AES为分组密码,分组密码也就是把明文分成一组一组的,每组长度相等,每次加密一组数据,直到加密完整
个明文。在AES标准规范中,分组长度只能是128位,也就是说,每个分组为16个字节(每个字节8位)。密钥的
长度可以使用128位、192位或256位。密钥的长度不同,推荐加密轮数也不同,如下表所示:
AES | 密钥长度(32bit) | 分组长度(32bit) | 加密轮数 |
---|---|---|---|
AES-128 | 4 | 4 | 10 |
AES-192 | 6 | 4 | 12 |
AES-256 | 8 | 4 | 14 |
这里实现的是AES-128,也就是密钥的长度为128位,加密轮数为10轮。
AES 的加密公式为 C = E(K,P),在加密函数E中,会执行一个轮函数,并且执行10次这个轮函数,这个轮函数的
前9次执行的操作是一样的,只有第10次有所不同。也就是说,一个明文分组会被加密10轮。AES的核心就是实现
一轮中的所有操作。
AES的处理单位是字节,128位的输入明文分组P和输入密钥K都被分成16个字节,分别记为P = P0 P1 … P15 和
K = K0 K1 … K15。如,明文分组为P = abcdefghijklmnop,其中的字符a对应P0,p对应P15。一般地,明文分组用
字节为单位的正方形矩阵描述,称为状态矩阵。在算法的每一轮中,状态矩阵的内容不断发生变化,最后的结果作
为密文输出。该矩阵中字节的排列顺序为从上到下、从左至右依次排列,如下图所示:
现在假设明文分组P为"abcdefghijklmnop",则对应上面生成的状态矩阵图如下:
上图中,0x61为字符a的十六进制表示。可以看到,明文经过AES加密后,已经面目全非
4、密钥的基本结构
128位密钥也是用字节为单位的矩阵表示,矩阵的每一列被称为1个32位比特字。通过密钥编排函数该密钥矩阵
被扩展成一个44个字组成的序列 W[0],W[1],.… ,W[43],该序列的前4个元素 W[0],W[1],W[2],W[3] 是原
始密钥,用于加密运算中的初始密钥加(下面介绍);后面40个字分为10组,每组4个字(128比特)分别用于10
轮加密运算中的轮密钥加,如下图所示:
上图中,设 K = “abcdefghijklmnop”,则 K0 = a,K15 = p,W[0] = K0 K1 K2 K3 = “abcd”。
下图所示,其中的 W[0,3] 是指 W[0]、W[1]、W[2] 和 W[3] 串联组成的128位密钥。加密的第1轮到第9轮的轮
函数一样,包括4个操作:字节代换、行位移、列混合和轮密钥加。最后一轮迭代不执行列混合。另外,在第一轮
迭代之前,先将明文和原始密钥进行一次异或加密操作
上图也展示了AES解密过程,解密过程仍为10轮,每一轮的操作是加密操作的逆操作。由于AES的4个轮操作都
是可逆的,因此,解密操作的一轮就是顺序执行逆行移位、逆字节代换、轮密钥加和逆列混合。同加密操作类似,
最后一轮不执行逆列混合,在第1轮解密之前,要执行1次密钥加操作
下面分别介绍AES中一轮的4个操作阶段,这4个操作阶段使输入位得到充分的混淆
5、字节代换
5.1、字节代换操作
AES的字节代换其实就是一个简单的查表操作。AES定义了一个S盒和一个逆S盒。
AES的S盒:
状态矩阵中的元素按照下面的方式映射为一个新的字节:把该字节的高4位作为行值,低4位作为列值,取出S盒
或者逆S盒中对应的行的元素作为输出。例如,加密时,输出的字节S1为0x12,则查S盒的第0x01行和0x02列,得
到值0xc9,然后替换S1原有的0x12为0xc9。状态矩阵经字节代换后的图如下:
5.2、字节代换逆操作
逆字节代换也就是查逆S盒来变换,逆S盒如下:
6、行位移
6.1、行位移操作
行移位是一个简单的左循环移位操作。当密钥长度为128比特时,状态矩阵的第0行左移0字节,第1行左移1字
节,第2行左移2字节,第3行左移3字节,如下图所示:
6.2、行移位的逆变换
行移位的逆变换是将状态矩阵中的每一行执行相反的移位操作,例如AES-128中,状态矩阵的第0行右移0字
节,第1行右移1字节,第2行右移2字节,第3行右移3字节。
7、列混合
7.1、列混合操作
列混合变换是通过矩阵相乘来实现的,经行移位后的状态矩阵与固定的矩阵相乘,得到混淆后的状态矩阵,如
下图的公式所示:
态矩阵中的第 j 列 (0 ≤j≤3) 的列混合可以表示为下图所示:
其中,矩阵元素的乘法和加法都是定义在基于GF(28)上的二元运算,并不是通常意义上的乘法和加法。这里涉及到一些信息安全上的数学知识,不过不懂这些知识也行。其实这种二元运算的加法等价于两个字节的异或,乘法则复杂一点。对于一个8位的二进制数来说,乘法乘以(00000010)等价于左移1位(低位补0)后,再根据情况同(00011011)进行异或运算,设S1 = (a7 a6 a5 a4 a3 a2 a1 a0),刚0x02 * S1如下图所示:
也就是说,如果a7为1,则进行异或运算,否则不进行
类似地,乘以(00000100)可以拆分成两次乘以(00000010)的运算:
乘以(0000 0011)可以拆分成先分别乘以(0000 0001)和(0000 0010),再将两个乘积异或:
因此,我们只需要实现乘以2的函数,其他数值的乘法都可以通过组合来实现
下面举个具体的例子,输入的状态矩阵如下:
下面,进行列混合运算, 以第一列的运算为例:
其它列的计算就不列举了,列混合后生成的新状态矩阵如下:
7.2、列混合逆运算
逆向列混合变换可由下图的矩阵乘法定义:
8、轮密钥加
轮密钥加是将128位轮密钥 Ki 同状态矩阵中的数据进行逐位异或操作,如下图所示。其中,密钥 Ki 中每个字
W[4i],W[4i+1],W[4i+2],W[4i+3] 为32位比特字,包含4个字节,他们的生成算法下面再下面介绍。轮密钥加
过程可以看成是字逐位异或的结果,也可以看成字节级别或者位级别的操作。也就是说,可以看成S0 S1 S2 S3 组
成的32位字与 W[4i] 的异或运算
轮密钥加的逆运算同正向的轮密钥加运算完全一致,这是因为异或的逆操作是其自身。轮密钥加非常简单,但
却能够影响S数组中的每一位
9、密钥扩展
AES首先将初始密钥输入到一个 4 * 4 的状态矩阵中,如下图所示:
这个 4 * 4 矩阵的每一列的 4 个字节组成一个字,矩阵 4 列的4个字依次命名为 W[0]、W[1]、W[2]和W[3],它
们构成一个以字为单位的数组 W。例如,设密钥 K 为 “abcdefghijklmnop”,则 K0 = ‘a’,K1 = ‘b’,K2 = ‘c’,K3 =
‘d’,W[0] = “abcd”。接着,对 W 数组扩充40个新列,构成总共44列的扩展密钥数组。新列以如下的递归方式产
生:
1、如果 i 不是 4 的倍数,那么第 i 列由如下等式确定:
W[i] = W[i-4]⨁W[i-1]
2、如果 i 是 4 的倍数,那么第 i 列由如下等式确定:
W[i] = W[i-4]⨁T(W[i-1])
其中,T是一个有点复杂的函数。
函数 T 由 3 部分组成:字循环、字节代换和轮常量异或,这 3 部分的作用分别如下:
■ 字循环:将 1 个字中的4个字节循环左移 1 个字节。即将输入字[b0, b1, b2, b3]变换成[b1,b2,b3,b0]
■ 字节代换:对字循环的结果使用S盒进行字节代换
■ 轮常量异或:将前两步的结果同轮常量 Rcon[j] 进行异或,其中 j 表示轮数
轮常量Rcon[j]是一个字,其值见下表
j | Rcon[j] |
---|---|
1 | 01 00 00 00 |
2 | 02 00 00 00 |
3 | 04 00 00 00 |
4 | 08 00 00 00 |
5 | 10 00 00 00 |
6 | 20 00 00 00 |
7 | 40 00 00 00 |
8 | 80 00 00 00 |
9 | 1B 00 00 00 |
10 | 36 00 00 00 |
下面举个例子:
■ 设初始的128位密钥为:3C A1 0B 21 57 F0 19 16 90 2E 13 80 AC C1 07 BD
■ 那么4个初始值为:
W[0] = 3C A1 0B 21
W[1] = 57 F0 19 16
W[2] = 90 2E 13 80
W[3] = AC C1 07 BD
■ 下面求扩展的第1轮的子密钥(W[4],W[5],W[6],W[7])。 由于是4的倍数,所以:
W[4] = W[0] ⨁ T(W[3]), T(W[3])的计算步骤如下:
● 循环地将W[3]的元素移位:AC C1 07 BD 变成 C1 07 BD AC
● 将 C1 07 BD AC 作为S盒的输入,输出为78 C5 7A 91
● 将78 C5 7A 91与第一轮轮常量Rcon[1]进行异或运算,将得到79 C5 7A 91
● 因此,T(W[3]) = 79 C5 7A 91,故 W[4] = 3C A1 0B 21 ⨁ 79 C5 7A 91 = 45 64 71 B0
■ 其余的3个子密钥段的计算如下:
W[5] = W[1] ⨁ W[4] = 57 F0 19 16 ⨁ 45 64 71 B0 = 12 94 68 A6
W[6] = W[2] ⨁ W[5] =90 2E 13 80 ⨁ 12 94 68 A6 = 82 BA 7B 26
W[7] = W[3] ⨁ W[6] = AC C1 07 BD ⨁ 82 BA 7B 26 = 2E 7B 7C 9B
所以,第一轮的密钥为 45 64 71 B0 12 94 68 A6 82 BA 7B 26 2E 7B 7C 9B
10、分组加密5种加密模式
分组密码有五种工作体制:
1、电码本模式(Electronic Codebook Book (ECB))
2、密码分组链接模式(Cipher Block Chaining (CBC))
3、计算器模式(Counter (CTR))
4、密码反馈模式(Cipher FeedBack (CFB))
5、输出反馈模式(Output FeedBack (OFB))
在密码学中,分组密码操作模式是使用分组密码来提供诸如机密性或真实性的信息服务的算法。基于分组的对
称密码算法比如 DES/AES 算法只是描述如何根据秘钥对一段固定长度(分组块)的数据进行加密,对于比较长的
数据,分组密码工作模式描述了如何重复应用某种算法加密分组操作来安全地转换大于块的数据量。
简单说就是,AES算法描述怎么加密一个数据块,分组密码工作模式决定了如何重复加密比较长的多个数据块
10.1、ECB
ECB(Electronic Codebook, 电子密码本)模式是最简单的加密模式,明文消息被分成固定大小的块(分组),并
且每个块被单独加密。
每个块的加密和解密都是独立的,且使用相同的方法进行加密,所以可以进行并行计算,但是这种方法一旦有
一个块被破解,使用相同的方法可以解密所有的明文数据,安全性比较差。
适用于数据较少的情形,加密前需要把明文数据填充到块大小的整倍数
优点:
1、简单;
2、有利于并行计算;
3、误差不会被传送;
缺点:
1、不能隐藏明文的模式;
2、可能对明文进行主动攻击;
10.2、CBC
CBC(Cipher Block Chaining,密码块链)模式中每一个分组要先和前一个分组加密后的数据进行XOR异或操作,
然后再进行加密。
这样每个密文块依赖该块之前的所有明文块,为了保持每条消息都具有唯一性,第一个数据块进行加密之前需
要用初始化向量IV进行异或操作。
CBC模式是一种最常用的加密模式,它主要缺点是加密是连续的,不能并行处理,并且与ECB一样消息块必须填
充到块大小的整倍数
优点:
1、不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL、IPSec的标准。
缺点:
1、不利于并行计算;
2、误差传递;
3、需要初始化向量IV
10.3、CFB
CFB(Cipher Feedback,密码反馈)模式和CBC模式比较相似,前一个分组的密文加密后和当前分组的明文XOR
异或操作生成当前分组的密文。CFB模式的解密和CBC模式的加密在流程上其实是非常相似的
优点:
1、隐藏了明文模式;
2、分组密码转化为流模式;
3、可以及时加密传送小于分组的数据;
缺点:
1、不利于并行计算;
2、误差传送:一个明文单元损坏影响多个单元;
3、唯一的IV;
10.4、OFB
OFB(Output Feedback,输出反馈)模式将分组密码转换为同步流密码,也就是说可以根据明文长度先独立生成
相应长度的流密码。通过流程图可以看出,OFB和CFB非常相似,CFB是前一个分组的密文加密后XOR当前分组明
文,OFB是前一个分组与前一个明文块异或之前的流密码XOR当前分组明文。由于异或操作的对称性,OFB模式的
解密和加密完全一样的流程
优点:
1、隐藏了明文模式;
2、分组密码转化为流模式;
3、可以及时加密传送小于分组的数据;
缺点:
1、不利于并行计算;
2、对明文的主动攻击是可能的;
3、误差传送:一个明文单元损坏影响多个单元;
11、java代码
package cn.com.sinosafe.common.security;
import org.apache.commons.lang.StringUtils;
import org.apache.tomcat.util.codec.binary.Base64;
import sun.misc.BASE64Decoder;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;
/**
* @Author: lipeng13
* @Description:
* @Date: Created in 2022/4/25 16:07
* @Modified By:
*/
public class AESUtil {
/**
* 密钥 AES加解密要求key必须要128个比特位(这里需要长度为16,否则会报错)
*/
private static final String KEY = "xxl_job_12345678";
/**
* 算法
*/
private static final String ALGORITHMS = "AES/ECB/PKCS5Padding";
/**
* 静态常量
*/
private static final String AES = "AES";
public static void main(String[] args) {
System.out.println("加密密钥和解密密钥:" + KEY);
String content = "SBAZCSJYBAGDYQDF";
System.out.println("加密前:" + content);
String encrypt = aesEncrypt(content);
System.out.println("加密后:" + encrypt);
String decrypt = aesDecrypt(encrypt);
System.out.println("解密后:" + decrypt);
}
/**
* 将字符串【AES加密】为base 64 code
*
* @param content 待加密的内容
* @return 加密后的base 64 code
*/
public static String aesEncrypt(String content) {
try {
// 创建密码器
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
// 初始化为加密模式的密码器
Cipher cipher = Cipher.getInstance(ALGORITHMS);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(AESUtil.KEY.getBytes(), AESUtil.AES));
byte[] bytes = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
// 使用base64解码
return Base64.encodeBase64String(bytes);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 将base 64 code 【AES解密】为字符串
*
* @param encryptStr 待解密的base 64 code
* @return 解密后的String
*/
public static String aesDecrypt(String encryptStr) {
try {
if (StringUtils.isEmpty(encryptStr)) {
return null;
}
// 将字符串转为byte,返回解码后的byte[]
byte[] encryptBytes = new BASE64Decoder().decodeBuffer(encryptStr);
// 创建密码器
KeyGenerator kgen = KeyGenerator.getInstance(AESUtil.AES);
kgen.init(128);
// 初始化为解密模式的密码器
Cipher cipher = Cipher.getInstance(ALGORITHMS);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(AESUtil.KEY.getBytes(), AESUtil.AES));
byte[] decryptBytes = cipher.doFinal(encryptBytes);
return new String(decryptBytes);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}