加密算法通常分为对称加密算法和非对称加密算法:
两者有不同的使用场景,而且经常会一起搭配起来使用,例如 SSL/TLS 协议就结合了对称加密算法和非对称加密算法。
本文主要介绍最常用的对称加密算法:AES。
AES 全称 Advanced Encryption Standard,是一种对称加密算法。AES 的出现主要是用来取代 DES 加密算法,因为 AES 的安全性相对更高。
AES 使用非常广泛,可以说只要上网,无论是使用手机 APP 还是 Web 应用,几乎都离不开 AES 加密算法。因为目前大部分网站,包括手机 APP 后端接口,都已经使用 HTTPS 协议,而 HTTPS 在数据传输阶段大部分都是使用 AES 对称加密算法。
在学习 AES 之前,首先要知道以下规则:
AES加密算法有多种工作模式(mode of operation),如:ECB、CBC、OFB、CFB、CTR、XTS、OCB、GCM。不同的模式参数和加密流程不同,但是核心仍然是 AES 算法。
本文主要介绍 ECB、CBC、GCM 三种模式。
由于 AES 是一种区块加密算法,加密时会将原始数据按大小拆分成一个个 128 比特(即 16 字节)区块进行加密,如果需要加密的原始数据不是 16 字节的整数倍时,就需要对原始数据进行填充,使其达到 16 字节的整数倍。
常用的填充方式有 PKCS5Padding、ISO10126Padding 等,另外如果能保证待加密的原始数据大小为 16 字节的整数倍,也可以选择不填充,即 NoPadding。
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 全称为电子密码本(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 全称为密码分组链接(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 的全称是 Galois/Counter Mode,它是一种认证加密(authenticated encryption)算法。它不但提供了加密解密,还提供了数据完整性校验,防止篡改。
AES-GCM 模式是目前使用最广泛的模式,可以尝试抓包看一下目前主流的 https 网站,其中大部分都是基于 GCM 模式。下图是使用抓包工具 Charles 查看浏览器访问 https 网站所使用的加密算法:
可以看到浏览器一般支持 AES-GCM 和 AES-CBC 模式,最终服务器选择使用 AES-GCM。
AES-GCM 认证加密需要用到以下参数:
代码:
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,即无需填充。
在ie下设置 css 样式 style="cursor:hand;" 可以正常显示 但是在firefox下就不行...
作者 / Krish Vitaldevara,Google Play 信任与安全产品管理总监 多年来,向数十...
Dreamweaver中如何使用Flash影片 1、首先需要我们准备的是一个Flash文件,其次最...
CSS3实现酷炫的3D旋转透视 3D动画效果现在越来越普及,已经被广泛的应用到了各个...
最近在做项目时,发现CSS3中关于动画的技术,自己很少运用在项目中,平时一些列...
在讲CSS优先级之前,我们得要了解什么是CSS,CSS是用来做什么的。 首先,我们对C...
行高line-height实现单行文本垂直居中 以前一直认为单行文本垂直居中要将高度和...
一、作用 离线浏览 - 根据文件规则把资源缓存在本地,脱机依然能够访问资源,联...
背景 京东购物小程序作为京东小程序业务流量的主要入口,承载着许多的活动和页面...
打开软件,我们按快捷键ctrl+n,建立一个新的文件。 点击常用,选择布局。 点击...