AES加密算法

AES(Advanced Encryption Standard)是一种对称加密算法(也叫共享密钥),对称加密算法的意思是加密和解密都是用同一个密钥(密钥和秘钥是同义词),通常来说,对称加密算法效率要优于非对称加密算法,它用来代替DES(Data Encryption Standard,56位密钥)。AES有三个关键点:密钥、填充、模式。

  • 密钥
  • 密钥分为128位(16字节)、192位(24字节)、256位(32字节),位数越多,解密和加密的运算量越大,相应的越安全,可以折中使用192位兼顾效率和安全。
  • 填充
  • 填充,在加密时,会将数据按照128位(16字节)一组分为多个明文块,然后对明文块分别加密;当然了,加密时与解密时的填充必须一致,否则无法解密。
  • NoPadding
  • 不填充,但要求明文必须是128位的整数倍;如果使用NoPadding,程序要对明文预处理进行填充,以达到16字节整数倍的要求。
  • PKCS5Padding(默认)
  • 如果明文块少于128位,则补足相应数量的字符,且字符的值等于缺少的字节数。比如明文:{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字节,则补足相应数量的字符,且最后一个字符的值等于缺少的字节数,其他字符填充随机数;比如明文:{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}
  • 模式
  • 模式,上边提到过,加密时,会将数据分为一组一组的明文。
  • ECB
  • ECB(电码本模式 Electronic Codebook Book 默认)是各组分别加密然后拼接起来,各组之间没有关系,可以并行计算;
  • CBC
  • CBC(密码分组链接模式 Cipher Block Chaining,默认),第二组依赖第一组的结果,第三组依赖第二组的结果,只有等待前一个组加密完成后,再进行下一组加密,以此类推,这种就没法并行计算。与ECB不同的事,CBC有初始向量,初始向量先于明文进行运算,结果再与密钥进行运算。

注意,咱们通常所说的“密钥”并不是真正的密钥,而是产生密钥的“种子”。

举例

好了,我们用java实验一下AES算法,AES算法在javax.crypto包下

ECB模式

Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
String key = "aaaaaaaaaaaaaaaa";
 SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8),"AES");
 cipher.init(Cipher.ENCRYPT_MODE,secretKeySpec);
 String content = "你好";
 byte[] encryptBytes = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));

 cipher.init(Cipher.DECRYPT_MODE,secretKeySpec);
 byte[] plaintextBytes = cipher.doFinal(encryptBytes);
 String plaintext = new String(plaintextBytes, StandardCharsets.UTF_8);
 log.info("解密后:{}",plaintext);

CBC模式

CBC模式必须使用初始向量

String seed = "aaaaaaaaaaaaaaaa";
//CBC有偏移量
IvParameterSpec ivspec = new IvParameterSpec(seed.getBytes(StandardCharsets.UTF_8));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(seed.getBytes(StandardCharsets.UTF_8),"AES");
cipher.init(Cipher.ENCRYPT_MODE,secretKeySpec,ivspec);
String content = "你";
byte[] encryptBytes = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
byte[] encryptedBase64 = Base64.getEncoder().encode(encryptBytes);
log.info("加密后:{}",new String(encryptedBase64, StandardCharsets.UTF_8));
byte[] decode = Base64.getDecoder().decode(encryptedBase64);

cipher.init(Cipher.DECRYPT_MODE,secretKeySpec,ivspec);
byte[] plaintextBytes = cipher.doFinal(decode);
String plaintext = new String(plaintextBytes, StandardCharsets.UTF_8);
log.info("解密后:{}",plaintext);

不填充

以上两个例子都使用了填充,如果使用不填充,那么就需要对明文预处理,确保明文是16字节的整数倍,通常做法是将明文转换为字节数组,将字节数组补为16的倍数,补齐的值都是0,0在ASCII中对应的是没有字符,所以在解码时也不需要特殊处理。比如

Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
        int blockSize = cipher.getBlockSize();
        byte[] dataBytes;
		try {
			dataBytes = data.getBytes("UTF-8");
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException(e);
		}
        int plaintextLength = dataBytes.length;
        //数据长度必须是16的倍数
        if (plaintextLength % blockSize != 0) {
            plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
        }
        byte[] plaintext = new byte[plaintextLength];
        //字节数组补齐为16的倍数,补上的位置都是0,0在ASCII中不对应字符,所以在解码时不需要特殊处理;
        System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);

AES对称加密算法 java.security.InvalidKeyException: Illegal key size or default parameters_开发语言

常见错误

java.security.InvalidKeyException: Illegal key size or default parameters

默认的jdk提供的AES算法只支持128位,如果超过128位就会抛出这个异常。
根据Stack Overflow所说,JDK8 Update 161版本开始,不限制长度,如果遇到这个问题,就升级JDK吧。

参考

程序员小灰讲AES