加密分组与填充及其常见问题

本文详细介绍了加密模式如ECB、CBC、CFB、CTR的特点和问题,以及填充方式如NoPadding、PKCS#5、PKCS#7、ZerosPadding、ISO10126Padding的差异。特别强调了Java加密过程中常见的错误,如不支持的填充方式、初始向量与分组大小不一致、数据长度与密钥不匹配等问题,并给出了相应的解决示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ECB

ECB(Electronic codebook),也就是电子密码本,会将加密的数据分组,分组大小和密码大小必须相同,然后对每个块进行分别加密。

ECB最大的问题在于相同明文加密之后的密文相同,这样会带来的问题是,密文可能会被根据特征破解。

举个例子,例如使用字母替换b代表a、c代表b,以此类推,最后z代表a,使用这种方式来加密文件,是不是很简单?但是为什么没人用呢?

因为它相同的明文加密会得到相同的密文。

这样,敌人就可以根据统计学中,日常用到最多的字母是e这个特征,然后查看密文中出现最多的字母,以此类推,然后反推分析密码表。

所以,重要数据,尽量不要使用ECB分组模式。
ECB分组

CBC

CBC(Cipher-block chaining),也就是密码块链接. 每个明文块先与前一个密文块进行异或后,再进行加密。每个密文块都依赖于它前面的所有明文块。第一个明文块会与初始向量异或。

CBC引入了一个可变的初始向量,可以得到不同密文,因此,不能从密文分析明文特征。

但是,因为后面分组依赖前面分组,所以没法并行执行。
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

和CFB非常类似的是OFB(Output feedback),即输出反馈模式,OFB的不同在于,后一组的初始向量IV使用的是前一组初始向量IV加密之后的数据,而不是使用的前一组的密文。

CTR

CTR(Counter),即计数器模式

CTR和CFB类似,不直接加密密文,所以不用填充。

另外,CTR使用一个函数来生成初始向量,后面分组不依赖前面的分组的结果,所以可以并行执行。
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]

RFC5652-PKCS#7

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支持五中模式:

  1. ECB
  2. CBC
  3. PCBC
  4. CFB
  5. OFB

支持三种填充:

  1. NoPadding
  2. PKCS5Padding
  3. 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字节,不支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值