文章目录
ECB
ECB(Electronic codebook),也就是电子密码本,会将加密的数据分组,分组大小和密码大小必须相同,然后对每个块进行分别加密。
ECB最大的问题在于相同明文加密之后的密文相同,这样会带来的问题是,密文可能会被根据特征破解。
举个例子,例如使用字母替换b代表a、c代表b,以此类推,最后z代表a,使用这种方式来加密文件,是不是很简单?但是为什么没人用呢?
因为它相同的明文加密会得到相同的密文。
这样,敌人就可以根据统计学中,日常用到最多的字母是e这个特征,然后查看密文中出现最多的字母,以此类推,然后反推分析密码表。
所以,重要数据,尽量不要使用ECB分组模式。
CBC
CBC(Cipher-block chaining),也就是密码块链接. 每个明文块先与前一个密文块进行异或后,再进行加密。每个密文块都依赖于它前面的所有明文块。第一个明文块会与初始向量异或。
CBC引入了一个可变的初始向量,可以得到不同密文,因此,不能从密文分析明文特征。
但是,因为后面分组依赖前面分组,所以没法并行执行。
PCBC(Propagating cipher-block chaining,PCBC)填充密码块链接模式,CBC的变种。和CBC的区别在于:每个明文块不但要与上一个密文异或,还要与上一个明文进行异或。
我们经常看到:DES加密算法, 要求原文长度必须是8byte的整数倍,AES加密算法, 要求原文长度必须是16byte的整数倍。
其实有个前提:是指在NoPadding无填充的情况下,使用ECB、CBC这种需要填充的分组模式。
CFB
CFB(Cipher feedback),即密文反馈模式。初始向量IV使用Key加密与明文异或得到密文,每个密文作为后一组的初始向量IV。
ECB和CBC模式,因为直接加密明文,所以明文分组必须和key大小一致,否则需要填充。
CFB因为不直接加密明文,所以分组大小不用和密钥key的大小一致,自然也不用填充。
和CFB非常类似的是OFB(Output feedback),即输出反馈模式,OFB的不同在于,后一组的初始向量IV使用的是前一组初始向量IV加密之后的数据,而不是使用的前一组的密文。
CTR
CTR(Counter),即计数器模式
CTR和CFB类似,不直接加密密文,所以不用填充。
另外,CTR使用一个函数来生成初始向量,后面分组不依赖前面的分组的结果,所以可以并行执行。
NoPadding
不填充,这意味着使用EBC\CBC这类需要填充的分组模式,那么数据必须和密钥key大小一致,否则会出错。
PKCS#5
填充数据为填充的字节数
xx xx xx xx 04 04 04 04
xx xx xx 05 05 05 05 05
PKCS5Padding限定填充大小是8Byte(64bit),AES最小块大小是16Byte(128bit),所以AES用不了PKCS5Padding。
PKCS#7
PKCS7Padding和PKCS5Padding基本一致,不同在于:块大小不是固定,而是区间:[1,255]
ZerosPadding
全部填充0,ZerosPadding最重要的问题在于处理数据本身包含0,怎样区分填充的0,还是输本身的0。
xx xx xx xx 00 00 00 00
00 00 00 00 00 00 00 00
ISO10126Padding
填充随机数,最后一个字节是填充的字节数
# 一共填充了4字节,所以最后一共字节填充04
xx xx xx xx 24 12 33 04
# 填充5字节,所以最后一共字节是05
xx xx xx AA 42 A6 22 05
国际标准化组织第10126号标准
ANSI X9.23
是ZeroPadding和ISO10126Padding结合体,最后一个字节是填充的字节数,填充使用0填充。
xx xx xx xx 00 00 00 04
xx xx xx 00 00 00 00 05
美国国家标准协会第 X.923 号标准
Java加密常见问题及其原因
我们以AES为例,说明一下Java中加密常出现的问题及其原因。
JCE中AES支持五中模式:
- ECB
- CBC
- PCBC
- CFB
- OFB
支持三种填充:
- NoPadding
- PKCS5Padding
- ISO10126Padding
不支持填充方式
java.security.NoSuchAlgorithmException: Cannot find any provider supporting AES/CFB/PKCS7Padding
@Test
public void aes() throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
String data = "hello word";
KeyGenerator keygen = KeyGenerator.getInstance("AES");
keygen.init(128);
SecretKey secretKey = keygen.generateKey();
byte[] key = secretKey.getEncoded();
secretKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CFB/PKCS7Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] cipherByte = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
System.out.println(java.util.Base64.getEncoder().encodeToString(cipherByte));
}
JCE是不支持PKCS7Padding填充、也不支持ZeroPadding,所以只要包含这2个都要出错。
如果要使用PKCS7Padding填充,可以用BouncyCastle。
@Test
public void bcAes() throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, BadPaddingException, IllegalBlockSizeException, InvalidKeyException {
String data = "hello word";
KeyGenerator keygen = KeyGenerator.getInstance("AES");
keygen.init(128);
SecretKey secretKey = keygen.generateKey();
Security.addProvider(new BouncyCastleProvider());
Key key = new SecretKeySpec(secretKey.getEncoded(), "AES" );
Cipher in = Cipher.getInstance("AES/CBC/PKCS7Padding" , "BC" );
in.init(Cipher.ENCRYPT_MODE, key);
byte [] cipherByte = in.doFinal(data.getBytes());
System.out.println(java.util.Base64.getEncoder().encodeToString(cipherByte));
}
初始向量与分组大小不一致
java.security.InvalidAlgorithmParameterException: Wrong IV length: must be 16 bytes long
@Test
public void aes() throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {
String data = "hello word";
KeyGenerator keygen = KeyGenerator.getInstance("AES");
keygen.init(128);
SecretKey secretKey = keygen.generateKey();
byte[] key = secretKey.getEncoded();
secretKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec("123456".getBytes());
cipher.init(Cipher.ENCRYPT_MODE, secretKey,iv);
byte[] cipherByte = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
System.out.println(java.util.Base64.getEncoder().encodeToString(cipherByte));
}
key大小是128位,16bytes,初始向量大小123456只有6bytes,所以出错,可以换成1234560123456789试一试。
NoPadding模式下待加密数据大小与key不一致
javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes
@Test
public void aes3() throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {
String data = "hello word";
KeyGenerator keygen = KeyGenerator.getInstance("AES");
keygen.init(128);
SecretKey secretKey = keygen.generateKey();
byte[] key = secretKey.getEncoded();
secretKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
IvParameterSpec iv = new IvParameterSpec("1234560123456789".getBytes());
cipher.init(Cipher.ENCRYPT_MODE, secretKey,iv);
byte[] cipherByte = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
System.out.println(java.util.Base64.getEncoder().encodeToString(cipherByte));
}
可以将data设置为hello word123456,刚好16字节试一试。
key的长度不满足要求
java.security.InvalidKeyException: Invalid AES key length: 6 bytes
@Test
public void aes() throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {
String data = "hello word";
SecretKeySpec secretKey = new SecretKeySpec("123456".getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec("1234560123456789".getBytes());
cipher.init(Cipher.ENCRYPT_MODE, secretKey,iv);
byte[] cipherByte = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
System.out.println(java.util.Base64.getEncoder().encodeToString(cipherByte));
}
JCE AES只支持16bytes(128bit)、24bytes(192bit)、32bytes(256bit)
123456只有6字节,不支持。