Java 中的加密算法: AES

加密算法
加密算法通常分为对称加密算法和非对称加密算法:

对称加密算法(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、24 或 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),将待加密的数据拆分成块,并对每个块进行独立加密。

ECB 加密

ECB 解密
代码:

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、24 或 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、24 或 32 字节,如果不符合要求则会异常:

java.security.InvalidKeyException: Invalid AES key length
ECB 模式有一个致命的缺点,由于该模式对每个块进行独立加密,会导致同样的明文块被加密成相同的密文块,相对来说并不是非常安全。下图就是一个很好的例子:

在这里插入图片描述

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、24 或 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、24 或 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,即无需填充。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java可以通过javax.crypto包实现AES加密算法,具体实现步骤如下: 1. 获取Cipher对象 ```java Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); ``` 其AES表示使用AES算法,CBC表示使用CBC模式,PKCS5Padding表示使用PKCS#5填充方式。 2. 初始化Cipher对象 ```java SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); IvParameterSpec ivSpec = new IvParameterSpec(ivBytes); cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); ``` 其,keyBytes表示AES加密密钥的字节数组,ivBytes表示初始化向量的字节数组,ENCRYPT_MODE表示加密模式。 3. 执行加密操作 ```java byte[] encryptedBytes = cipher.doFinal(plainText.getBytes("UTF-8")); ``` 其,plainText表示需要加密的明文字符串。 完整的加密代码如下: ```java import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; public class AesUtil { public static String encrypt(String plainText, String key, String iv) throws Exception { byte[] keyBytes = key.getBytes("UTF-8"); byte[] ivBytes = iv.getBytes("UTF-8"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); IvParameterSpec ivSpec = new IvParameterSpec(ivBytes); cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); byte[] encryptedBytes = cipher.doFinal(plainText.getBytes("UTF-8")); return Base64.getEncoder().encodeToString(encryptedBytes); } public static void main(String[] args) throws Exception { String plainText = "Hello, world!"; String key = "1234567890123456"; String iv = "1234567890123456"; String encryptedText = encrypt(plainText, key, iv); System.out.println("encryptedText: " + encryptedText); } } ``` 其,key和iv都是16字节(128位)的字符串。由于AES算法的安全性非常高,因此在使用时需要注意密钥和向量的安全性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值