加密算法
加密算法通常分为对称加密算法和非对称加密算法:
- 对称加密算法(symmetric-key cryptography):加密和解密时应用雷同的密钥。罕用的对称加密算法有 DES、AES。
- 非对称加密算法(asymmetric-key cryptography):加密和解密应用不同的密钥,例如公钥加密的内容只能用私钥解密,所以又称为公钥加密算法(public-key cryptography)。应用最宽泛的非对称加密算法是 RSA 算法。
两者有不同的应用场景,而且常常会一起搭配起来应用,例如 SSL/TLS 协定就联合了对称加密算法和非对称加密算法。
本文次要介绍最罕用的对称加密算法:AES。
AES
AES 全称 Advanced Encryption Standard,是一种对称加密算法。AES 的呈现次要是用来取代 DES 加密算法,因为 AES 的安全性绝对更高。
AES 应用十分宽泛,能够说只有上网,无论是应用手机 APP 还是 Web 利用,简直都离不开 AES 加密算法。因为目前大部分网站,包含手机 APP 后端接口,都曾经应用 HTTPS 协定,而 HTTPS 在数据传输阶段大部分都是应用 AES 对称加密算法。
在学习 AES 之前,首先要晓得以下规定:
- AES 是一种区块加密算法,加密时会将原始数据按大小拆分成一个个区块进行加密,区块大小固定为 128 比特(即 16 字节)
- AES 密钥长度能够是 128、192 或 256 比特(即 16、25 或 32 字节),密钥长度越长,安全性越高,而性能也就越低
AES 工作模式
AES 加密算法有多种工作模式(mode of operation),如:ECB、CBC、OFB、CFB、CTR、XTS、OCB、GCM。不同的模式参数和加密流程不同,然而外围依然是 AES 算法。
本文次要介绍 ECB、CBC、GCM 三种模式。
AES 填充形式
因为 AES 是一种区块加密算法,加密时会将原始数据按大小拆分成一个个 128 比特(即 16 字节)区块进行加密,如果须要加密的原始数据不是 16 字节的整数倍时,就须要对原始数据进行填充,使其达到 16 字节的整数倍。
罕用的填充形式有 PKCS5Padding、ISO10126Padding 等,另外如果能保障待加密的原始数据大小为 16 字节的整数倍,也能够抉择不填充,即 NoPadding。
Java 中的 AES
Java 中的 javax.crypto.Cipher
类提供加密和解密的性能。
创立一个 Cipher
:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
Cipher
类 getInstance
办法需传递一个加密算法的名称作为参数,用来创立对应的 Cipher
,其格局为 algorithm/mode/padding
,即 算法名称 / 工作模式 / 填充形式
,例如 AES/CBC/PKCS5Padding
。具体有哪些可选的加密形式,能够参考文档:
https://docs.oracle.com/javas…
ECB
ECB 全称为电子密码本(Electronic codebook),将待加密的数据拆分成块,并对每个块进行独立加密。
代码:
public static byte[] encryptECB(byte[] data, byte[] key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"));
byte[] result = cipher.doFinal(data);
return result;
}
public static byte[] decryptECB(byte[] data, byte[] key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"));
byte[] result = cipher.doFinal(data);
return result;
}
public static void main(String[] args) throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
String data = "Hello World"; // 待加密的明文
String key = "12345678abcdefgh"; // key 长度只能是 16、25 或 32 字节
byte[] ciphertext = encryptECB(data.getBytes(), key.getBytes());
System.out.println("ECB 模式加密后果(Base64):" + Base64.getEncoder().encodeToString(ciphertext));
byte[] plaintext = decryptECB(ciphertext, key.getBytes());
System.out.println("解密后果:" + new String(plaintext));
}
因为加密后的密文是二进制格局而非字符串,所以这里应用了 Base64 编码方式将其转换成字符串不便输入查看。输入:
ECB 模式加密后果(Base64):bB0gie8pCE2RBQoIAAIxeA==
解密后果:Hello World
须要留神,AES 密钥长度只能是 16、25 或 32 字节,如果不符合要求则会异样:
java.security.InvalidKeyException: Invalid AES key length
CBC 模式有一个致命的毛病,因为该模式对每个块进行独立加密,会导致同样的明文块被加密成雷同的密文块,相对来说并不是十分平安。下图就是一个很好的例子:
CBC
CBC 全称为明码分组链接(Cipher-block chaining),它的呈现解决 ECB 同样的明文块会被加密成雷同的密文块的问题。
CBC 引入了初始向量的概念(IV,Initialization Vector),第一个明文块先与 IV 进行异或后再加密,后续每个明文块先与前一个密文块进行异或后再加密。
代码:
public static byte[] encryptCBC(byte[] data, byte[] key, byte[] iv) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
byte[] result = cipher.doFinal(data);
return result;
}
public static byte[] decryptCBC(byte[] data, byte[] key, byte[] iv) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
byte[] result = cipher.doFinal(data);
return result;
}
public static void main(String[] args) throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException {
String data = "Hello World"; // 待加密的原文
String key = "12345678abcdefgh"; // key 长度只能是 16、25 或 32 字节
String iv = "iviviviviviviviv"; // CBC 模式须要用到初始向量参数
byte[] ciphertext = encryptCBC(data.getBytes(), key.getBytes(), iv.getBytes());
System.out.println("CBC 模式加密后果(Base64):" + Base64.getEncoder().encodeToString(ciphertext));
byte[] plaintext = decryptCBC(ciphertext, key.getBytes(), iv.getBytes());
System.out.println("解密后果:" + new String(plaintext));
}
输入:
CBC 模式加密后果(Base64):K7bSB51+KxfqaMjJOsPAQg==
解密后果:Hello World
因为 CBC 每个明文块加密都依赖前一个块的加密后果,所以其次要毛病在于加密过程是串行的,无奈被并行化。
GCM
GCM 的全称是 Galois/Counter Mode,它是一种认证加密(authenticated encryption)算法。它岂但提供了加密解密,还提供了数据完整性校验,避免篡改。
AES-GCM 模式是目前应用最宽泛的模式,能够尝试抓包看一下目前支流的 https 网站,其中大部分都是基于 GCM 模式。下图是应用抓包工具 Charles 查看浏览器拜访 https 网站所应用的加密算法:
能够看到浏览器个别反对 AES-GCM 和 AES-CBC 模式,最终服务器抉择应用 AES-GCM。
AES-GCM 认证加密须要用到以下参数:
- 待加密的明文
- 密钥
- 初始向量 IV
- additional authenticated data (AAD)
代码:
public static byte[] encryptGCM(byte[] data, byte[] key, byte[] iv, byte[] aad) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(128, iv));
cipher.updateAAD(aad);
byte[] result = cipher.doFinal(data);
return result;
}
public static byte[] decryptGCM(byte[] data, byte[] key, byte[] iv, byte[] aad) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(128, iv));
cipher.updateAAD(aad);
byte[] result = cipher.doFinal(data);
return result;
}
public static void main(String[] args) throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException {
String data = "Hello World"; // 待加密的原文
String key = "12345678abcdefgh"; // key 长度只能是 16、25 或 32 字节
String iv = "iviviviviviviviv";
String aad = "aad"; // AAD 长度无限度,可为空
byte[] ciphertext = encryptGCM(data.getBytes(), key.getBytes(), iv.getBytes(), aad.getBytes());
System.out.println("GCM 模式加密后果(Base64):" + Base64.getEncoder().encodeToString(ciphertext));
byte[] plaintext = decryptGCM(ciphertext, key.getBytes(), iv.getBytes(), aad.getBytes());
System.out.println("解密后果:" + new String(plaintext));
}
输入:
GCM 模式加密后果(Base64):1UxXmFpdUwMnpI7rh0XfmFqtdZSHTbNC/08g
解密后果:Hello World
AES-GCM 是流加密(Stream cipher)算法,所以对应的填充模式为 NoPadding,即无需填充。
参考文档
- 维基百科 -AES
- 维基百科 - 工作模式
- javax.crypto.Cipher
- GCM
关注我的公众号