1.简述
高级加密标准(Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。
注:使用jdk自带的jce.jar包实现
加密标准:
AES为分组密码,分组密码也就是把明文分成一组一组的,每组长度相等,每次加密一组数据,直到加密完整个明文。在AES标准规范中,分组长度只能是128位,也就是说,每个分组为16个字节(每个字节8位)。密钥的长度可以使用128位、192位、256位。密钥的长度不同,加密轮数也不同。
AES |
密钥长度(32位bit) |
分组长度(32位bit) |
加密轮数 |
JDK |
AES-128 |
4(16个字符) |
4 |
10 |
支持 |
AES-192 |
6(24个字符) |
4 |
12 |
不支持 |
AES-256 |
8(32个字符) |
4 |
14 |
不支持 |
2.AES加密模式
JCE支持模式:
- 电码本模式(Electronic Codebook Book,缩写:ECB):最基本的加密模式,是将整个明文分成若干段相同的小段,然后对每一小段进行加密。
- 密码分组链接模式(Cipher Block Chaining,缩写:CBC):先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再与密钥进行加密。
- 计算器模式(Counter,缩写:CTR):计算器模式不常见,在CTR模式中, 有一个自增的算子,这个算子用密钥加密之后的输出和明文异或的结果得到密文,相当于一次一密。这种加密方式简单快速,安全可靠,而且可以并行加密,但是在计算器不能维持很长的情况下,密钥只能使用一次。
- 密码反馈模式(Cipher FeedBack ,缩写:CFB):能够将块密文转换为流密文。
- 输出反馈模式(Output FeedBack,缩写:OFB):先用块加密器生成密钥流,然后再将密钥流与明文流异或得到密文流,解密是先用块加密器生成密钥流,再将密钥流与密文流异或得到明文,由于异或操作的对称性所以加密和解密的流程是完全一样的。
JCE5种模式优缺点:
1. ECB:
优点:简单、可并行计算、误差不传递。
缺点:不能隐藏明文的模式、可能对明文进行主动攻击。
2. CBC:
优点:不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL、IPSec的标准。
缺点:需要初始化向量IV、不利于并行计算、误差传递。
3. CTR:
优点:同明文不同密、每个块单独运算,适合并行运算。
缺点:主动攻击(改明文,后续内容不影响,只要误差不传递该缺点就存在)。
4. CFB:
优点:隐藏了明文模式、分组密码转化为流模式、可以及时加密传送小于分组的数据。
缺点:不利于并行计算、误差传送:一个明文单元损坏影响多个单元。
5. OFB:
优点:同明文不同密文,分组密钥转换为流密码。
缺点:串行运算不利并行、误差传送:一个明文单元损坏影响多个单元。
3.AES填充方式
JCE中AES支持三种填充:NoPadding,PKCS5Padding,ISO10126Padding。
注:不带模式和填充来获取AES算法的时候,其默认使用ECB/PKCS5Padding。
填充 |
16字节加密后数据长度 |
不满16字节加密后长度 |
NoPadding |
16 |
不支持 |
PKCS5Padding |
32 |
支持 |
ISO10126Padding |
32 |
支持 |
4.AES加密实现
(1)NoPadding补码方式实现
import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; /**测试类 */ public class Test{ private static final String AESKEY = "WEBTEST123456789";//密钥 public static void main(String[] args) { try { String enString = "asd#测试#13#";//有了#号区分,这样做是为了方便过滤不是16的倍数时填充的0 System.out.println("加密前的字串是:" + enString); enString = AesUtils.Encrypt(enString, AESKEY); System.out.println("加密后的字串是:" + enString); enString = AesUtils.Decrypt(enString, AESKEY); System.out.println("解密后的字串是:" + enString); } catch (Exception e) { e.printStackTrace(); } } } /**AES加密工具类 */ class AesUtils { // 加密 public static String Encrypt(String sSrc, String sKey) throws Exception { if (sKey == null) { System.out.print("Key为空null"); return null; } // 判断Key是否为16位 if (sKey.length() != 16) { System.out.print("Key长度不是16位"); return null; } //注意这一步,不是16的倍数,则后面填充0,不填充的话会报错 while (sSrc.getBytes().length % 16 > 0) { sSrc += "0"; } byte[] raw = sKey.getBytes(); SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); //注意,不是CBC模式,去除IvParameterSpec IvParameterSpec iv = new IvParameterSpec("1234567812345678".getBytes()); cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv); byte[] encrypted = cipher.doFinal(sSrc.getBytes()); return byte2hex(encrypted); } // 解密 public static String Decrypt(String sSrc, String sKey) throws Exception { try { // 判断Key是否正确 if (sKey == null) { System.out.print("Key为空null"); return null; } // 判断Key是否为16位 if (sKey.length() != 16) { System.out.print("Key长度不是16位"); return null; } byte[] raw = sKey.getBytes("ASCII"); SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); IvParameterSpec iv = new IvParameterSpec("1234567812345678".getBytes()); cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv); byte[] encrypted1 = hex2byte(sSrc); try { byte[] original = cipher.doFinal(encrypted1); String originalString = new String(original); return originalString; } catch (Exception e) { System.out.println(e.toString()); return null; } } catch (Exception ex) { System.out.println(ex.toString()); return null; } } /**自定义二次加密,将二进制转为16进制 */ public static String byte2hex(byte[] b) { String hs = ""; String stmp = ""; for (int n = 0; n < b.length; n++) { stmp = (java.lang.Integer.toHexString(b[n] & 0XFF)); if (stmp.length() == 1) { hs = hs + "0" + stmp; } else { hs = hs + stmp; } } return hs.toUpperCase(); } /**二次加密内容解密,将16进制转换为二进制 */ public static byte[] hex2byte(String strhex) { if (strhex == null) { return null; } int l = strhex.length(); if (l % 2 == 1) { return null; } byte[] b = new byte[l / 2]; for (int i = 0; i != l / 2; i++) { b[i] = (byte) Integer.parseInt(strhex.substring(i * 2, i * 2 + 2), 16); } return b; } }
注:使用NoPadding补码方式,需要加密、解密的数据是16字节的倍数,否则会报错。
(2)其他补码方式实现
/**测试类 */ public class Test{ private static final String AESKEY = "WEBTEST123456789";//密钥 public static void main(String[] args) { try { String enString = "asd#测试#13#";//有了#号区分,这样做是为了方便过滤不是16的倍数时填充的0 System.out.println("加密前的字串是:" + enString); enString = AesUtils.Encrypt(enString, AESKEY); System.out.println("加密后的字串是:" + enString); enString = AesUtils.Decrypt(enString, AESKEY); System.out.println("解密后的字串是:" + enString); } catch (Exception e) { e.printStackTrace(); } } } /**AES加密工具类 */ class AesUtils { // 加密 public static String Encrypt(String sSrc, String sKey) throws Exception { if (sKey == null) { System.out.print("Key为空null"); return null; } // 判断Key是否为16位 if (sKey.length() != 16) { System.out.print("Key长度不是16位"); return null; } byte[] raw = sKey.getBytes(); SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); //注意,不是CBC模式,去除IvParameterSpec IvParameterSpec iv = new IvParameterSpec("1234567812345678".getBytes()); cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv); byte[] encrypted = cipher.doFinal(sSrc.getBytes()); return byte2hex(encrypted); } // 解密 public static String Decrypt(String sSrc, String sKey) throws Exception { try { // 判断Key是否正确 if (sKey == null) { System.out.print("Key为空null"); return null; } // 判断Key是否为16位 if (sKey.length() != 16) { System.out.print("Key长度不是16位"); return null; } byte[] raw = sKey.getBytes("ASCII"); SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); IvParameterSpec iv = new IvParameterSpec("1234567812345678".getBytes()); cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv); byte[] encrypted1 = hex2byte(sSrc); try { byte[] original = cipher.doFinal(encrypted1); String originalString = new String(original); return originalString; } catch (Exception e) { System.out.println(e.toString()); return null; } } catch (Exception ex) { System.out.println(ex.toString()); return null; } } /**自定义二次加密,将二进制转为16进制 */ public static String byte2hex(byte[] b) { String hs = ""; String stmp = ""; for (int n = 0; n < b.length; n++) { stmp = (java.lang.Integer.toHexString(b[n] & 0XFF)); if (stmp.length() == 1) { hs = hs + "0" + stmp; } else { hs = hs + stmp; } } return hs.toUpperCase(); } /**二次加密内容解密,将16进制转换为二进制 */ public static byte[] hex2byte(String strhex) { if (strhex == null) { return null; } int l = strhex.length(); if (l % 2 == 1) { return null; } byte[] b = new byte[l / 2]; for (int i = 0; i != l / 2; i++) { b[i] = (byte) Integer.parseInt(strhex.substring(i * 2, i * 2 + 2), 16); } return b; } }
注:使用PKCS5Padding、ISO10126Padding补码方式,不是16字节的倍数也能进行加密、解密。