AES加密
AES加密的五种模式
- ECB(Electronic Codebook Book) 电码本模式
将明文分成若干段相同的小段, 不足补齐, 然后依次对每一小段进行加密后输出密文. ECB模式的弱点在于, 相同的明文会产生相同的密文, 容易遭到字典攻击, 安全性不够高 - CBC(Cipher Block Chaining) 密码分组链接模式
先明文切分成若干小段, 每一小段与初始块或者上一段的密文段进行异或运算后, 再与密钥进行加密, 这样做的目的是增强破解难度. 相对于ECB模式来说, CBC模式较安全, 同时CBC适合于传输长度较长的报文 - CTR(Counter) 计算器模式
在CTR模式中有一个自增的算子, 这个算子用密钥加密之后的输出和明文异或的结果得到密文, 相当于一次一密. 这种加密方式简单快速安全可靠, 而且可以并行加密. 但是在计算器不能维持很长的情况下, 密钥只能使用一次 - CFB(Cipher FeedBack) 密码反馈模式
- OFB(Output FeedBack) 输出反馈模式
AES ECB和CBC在代码实现中的区别
ECB模式的加解密, 只需要一个passcode参数
public static String aesDecrypt(String hexString, String passcode) { try { byte[] raw = passcode.getBytes(StandardCharsets.UTF_8); SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, skeySpec); byte[] encrypted = CodecUtil.hexStringToBytes(hexString); byte[] original = cipher.doFinal(encrypted); return new String(original, StandardCharsets.UTF_8); } catch (Exception e) { LOG.error(e.getMessage(), e); return null; } } public static String aesEncrypt(String plainText, String passcode) { try { byte[] raw = passcode.getBytes(StandardCharsets.UTF_8); SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, skeySpec); byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8)); return CodecUtil.hexBytesToString(encrypted); } catch (Exception e) { LOG.error(e.getMessage(), e); return null; } }
CBC模式则需要增加两个参数, salt 和 iv (initialization vector)
/* Derive the key, given password and salt. */ SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); KeySpec spec = new PBEKeySpec(password, salt, 65536, 256); SecretKey tmp = factory.generateSecret(spec); SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
其中65536是循环次数, 256是位数. 在CBC模式下, 对应每个文本会生成一个随机的初始化向量, 所以即使文本相同, 产生的密文也不同, 每次加密输出的结果包含两部分: 密文和初始化向量.
/* Encrypt the message. */ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, secret); AlgorithmParameters params = cipher.getParameters(); byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV(); byte[] ciphertext = cipher.doFinal("Hello, World!".getBytes("UTF-8"));
保存密文和初始化向量后, 解密时密钥的生成还是一样的, 使用相同的口令 + salt + 循环次数, 但是cipher需要用和密文一起保存的初始化向量来初始化.
/* Decrypt the message, given derived key and initialization vector. */ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv)); String plaintext = new String(cipher.doFinal(ciphertext), "UTF-8"); System.out.println(plaintext);
如果需要简化输入, 可以将口令同时用于生成初始化向量.
AES加密时的 java.security.InvalidKeyException: Illegal key size 异常
程序代码
// 设置加密模式为AES的CBC模式 Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES"); IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16); cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); // 加密 byte[] encrypted = cipher.doFinal(unencrypted); ...
当执行到
cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
时, 如果密钥大于128, 会抛出java.security.InvalidKeyException: Illegal key size 异常. 因为密钥长度是受限制的, java运行时环境读到的是受限的policy文件. 文件位于${java_home}/jre/lib/security, 这种限制是因为美国对软件出口的控制.
处理办法: 在官方网站下载JCE无限制权限策略文件
JDK7的下载地址: http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html
JDK8的下载地址: http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html
下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt
如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件
如果安装了JDK,还要将两个jar文件也放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件