前言知识:
1.AES(Advanced Encryption Standard)高级加密标准,作为分组密码(把明文分成一组一组的,每组长度相等,每次加密一组数据,直到加密完整个明文)。
2.在AES标准规范中,分组长度只能是128位,也就是说,每个分组为16个字节(每个字节8位)8的倍数。密钥的长度可以使用128位、192位或256位,分别对应的就是AES128
,AES192
,AES256,
密钥的长度不同,推荐加密轮数也不同,从安全性来看,AES256
安全性最高。从性能看,AES128
性能最高。本质原因就是它们的加密处理轮数不同
3.AES属于对称加密算法,是因为加解密使用同一个秘钥,
4.AES
支持三种长度的密钥: 128位,192位,256位,一般有至少4种模式,即ECB、CBC、CFB、OFB等
5.AES
算法在对明文加密的时候,并不是把整个明文一股脑的加密成一整段密文,而是把明文拆分成一个个独立的明文块,每一个明文块长度128bit
。这些明文块经过
AES
加密器复杂处理,生成一个个独立的密文块,这些密文块拼接在一起,就是最终的AES
加密的结果。但这里涉及到一个问题,假如一段明文长度是
196bit
,如果按每128bit
一个明文块来拆分的话,第二个明文块只有64bit
,不足128bit
。这时候怎么办呢?就需要对明文块进行填充(Padding) 。
6.几种典型的填充方式:
NoPadding
: 不做任何填充,但是要求明文必须是16字节的整数倍。PKCS5Padding
(Java默认): 如果明文块少于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 }ISO10126Padding
:如果明文块少于16个字节(128bit
),在明文块末尾补足相应数量的字节,最后一个字符值等于缺少的字符数,其他字符填充随机数。比如明文:{1,2,3,4,5,a,b,c,d,e},缺少6个字节,则可能补全为{1,2,3,4,5,a,b,c,d,e,5,c,3,G,$,6}PKCS7Padding
原理与PKCS5Padding
相似,区别是PKCS5Padding
的blocksize
为8字节,而PKCS7Padding
的blocksize
可以为1到255字节需要注意的是,如果在
AES
加密的时候使用了某一种填充方式,解密的时候也必须采用同样的填充方式,ZeroPadding
(C++默认,java没有次填充方式):java操作的话,明文不足16位则手动补零,然后调用NoPadding
AES四种工作模式原理:
1、ECB模式:
ECB (电子密码本)模式是最简单的块密码加密模式,加密前根据数据块大小(如AES为128位,每组大小跟加密秘钥长度相同)分成若干块,之后将每块使用相同的密钥单独通过块加密器密器。这种加密模式的优点就是简单,不需要初始化向量(IV) ,每个数据块独立进行加/解密,利于并行计算,加/解密效率很高。但这种模式中,所有数据都采用相同密钥进行加/解密,也没有经过任何逻辑运算,相同明文得到相同的密文,所以可能导致“选择明文攻击”的发生。(最早采用和最简单的模式)
优点: 1.简单; 2.有利于并行计算; 3.误差不会被扩散;
缺点: 1.不能隐藏明文的模式; 2.可能对明文进行主动攻击; 因此,此模式适于加密小消息
2、CBC模式: 参考
CBC (密码分组链接)模式是先将明文切分成若干小块,然后每个小块与初始块或者上一段的密文段进行逻辑异或运算后,再用密钥进行加密。第一个明文块与一个叫初始化向量的数据块进行逻辑异或运算。这样就有效的解决了ECB模式所暴露出来的问题,即使两个明文块相同,加密后得到的密文块也不相同。但是缺点也相当明显,如加密过程复杂,效率低等。
优点: 不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL、IPSec的标准。
缺点: 1.不利于并行计算; 2.误差传递; 3.需要初始化向量IV
3、CFB模式:
与ECB和CBC模式只能够加密块数据不同,CFB模式能够将密文转化成为流密文。这种加密模式中,由于加密流程和解密流程中被块加密器加密的数据是前块的密文,因此即使本块明文数据的长度不是数据块大小的整数倍也是不需要填充的,这保证了数据长度在加密前后是相同的。
优点: 1.隐藏了明文模式; 2.分组密码转化为流模式; 3.可以及时加密传送小于分组的数据;
缺点: 1.不利于并行计算; 2.误差传送:一个明文单元损坏影响多个单元; 3.唯一的IV;
4、OFB模式:
不再直接加密明文块,其加密过程是先使用块加密器生成密钥流,然后再将密钥流和明文流进行逻辑异或运算得到密文流。
优点: 1.隐藏了明文模式; 2.分组密码转化为流模式; 3.可以及时加密传送小于分组的数据;
缺点: 1.不利于并行计算; 2.对明文的主动攻击是可能的; 3.误差传送:一个明文单元损坏影响多个单元;
AES的算法原理
加密算法的一般设计准则
混淆 (Confusion) 最大限度地复杂化密文、明文与密钥之间的关系,通常用非线性变换算法达到最大化的混淆。
扩散 (Diffusion) 明文或密钥每变动一位将最大化地影响密文中的位数,通常采用线性变换算法达到最大化的扩散。
对Rijndael算法来说解密过程就是加密过程的逆向过程。下图为AES加解密的流程,从图中可以看出:1)解密算法的每一步分别对应加密算法的逆操作,2)加解密所有操作的顺序正好是相反的。正是由于这几点(再加上加密算法与解密算法每步的操作互逆)保证了算法的正确性。具体参考1 参考2
正文(C中AES_cbc_encrypt加密对应java中的解密)
//C中的加密方式
AES_cbc_encrypt((const unsigned char*)in.data(),
(unsigned char*)out.data(),
len,
&aes,
(unsigned char*)ivec.data(),
AES_ENCRYPT);
AES_cbc_encrypt:这是C++中AES中的CBC加密方式,可以对任意长度的输入数据进行加密,若输入数据不为16字节整数倍时,该函数内部会使用ZeroPadding自动对输入数据,进行填充,再进行加密。然后我查了一下,java中没有ZeroPadding,都是手动实现补零然后调用NoPadding的,我的具体实现:
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.nio.charset.StandardCharsets;
/**
* @ClassName AESCBCUtil
* @Description TODO
* @Author wanghaha
* @Date 2023/3/1
**/
public class AESCBCUtil {
/*
* @Description: 满足pc的加密方式 生成128位
* @Author: wanghaha
* @Date: 2023/3/1
* @param: data
* @param: iv
* @return: java.lang.String
**/
public static String encrypt128(String data, String iv) {
byte[] key = new byte[16];
for (int i = 0; i < 16; i++) {
key[i] = (byte) (29 + i * 3);
}
try {
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
byte[] dataBytes = data.getBytes();
byte[] plaintext = new byte[128];
//填充
System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
//设置偏移量参数
IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encryped = cipher.doFinal(plaintext);
return DatatypeConverter.printBase64Binary(encryped);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/* 加密 正常长度
* @Description:
* @Author: wanghaha
* @Date: 2023/3/1
* @param: null
* @return: null
**/
public static String encrypt(String data, String iv) {
byte[] key = new byte[16];
for (int i = 0; i < 16; i++) {
key[i] = (byte) (29 + i * 3);
}
try {
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
byte[] dataBytes = data.getBytes();
int blockSize = cipher.getBlockSize();
int length = dataBytes.length;
//计算需填充长度
if (length % blockSize != 0) {
length = length + (blockSize - (length % blockSize));
}
byte[] plaintext = new byte[length];
//填充
System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
//设置偏移量参数
IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encryped = cipher.doFinal(plaintext);
return DatatypeConverter.printBase64Binary(encryped);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
//解密
public static String desEncrypt(String data, String iv) {
byte[] key = new byte[16];
for (int i = 0; i < 16; i++) {
key[i] = (byte) (29 + i * 3);
}
try {
byte[] encryp = DatatypeConverter.parseBase64Binary(data);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] original = cipher.doFinal(encryp);
//因为pc端使用加密的是zeroPadding: 后补0的Nopadding方式 且 都是字符unsigned限制的 char正数 可以通过判断第一个0的位置判断字符长度
int end=0;
while(end < original.length && original[end] != 0){
end++;
}
return new String(original,0, end);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//测试
public static void main(String[] args) {
String data = "232342342342342";
String iv = "2334022720230227";
String encrypt= encrypt(data ,iv);
String desencrypt = desEncrypt(encrypt,iv);
System.out.println("长加密后:"+encrypt);
System.out.println("长解密后:"+desencrypt);
}
}
遇到的bug解决方法:
bug1:在做AES解密的时候,碰到了"Given final block not properly padded. Such issues can arise if a bad key is used during decryption"报错,意思就是加密和加密所所对应的秘钥不同
bug2:在使用java的Cipher类进行AES加密时,报错:IllegalBlockSizeException: Input length not multiple of 16 bytes
报错的代码 Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding")
填充方式如果是Nopadding,那么加密的明文就得是8的倍数,或者是密钥必须是16位的
待加密内容的长度必须是16的倍数,如果不是16的倍数
总结:Java虽然支持AES的CBC模式,但是填充方式不支持ZeroPadding。
原理:ZeroPadding就是用'\0'将加密内容填充至BlockSize(CBC一般是16)的整数倍,再用NoPadding方式填充即可。