前言
AES(Advanced Encryption Standard,高级加密标准)是一种对称加密算法,用于加密电子数据。它是由比利时密码学家Joan Daemen和Vincent Rijmen设计的,并于2001年由美国国家标准与技术研究院(NIST)采纳为官方标准。
一、AES加密原理
AES算法基于替换和置换操作,它支持128位、192位和256位的密钥长度,并且对于每种密钥长度,都有一个固定的加密轮数(10轮、12轮、14轮)。
初始状态:将明文分成128位的数据块,并将其放入一个4×4字节的矩阵中。
密钥扩展:从原始密钥派生出一系列子密钥,用于每一轮加密。
加密过程:包括多个相同的轮次,每轮包括四个步骤:字节替代、行移位、列混合和密钥加法。
最终轮:除了不进行列混合外,其余步骤与之前的轮相同。
1、其中不同的密钥长度解释如下:
AES加密算法支持128位、192位和256位的密钥长度,它们的主要区别在于安全性和性能:
安全性:
128位密钥:提供非常高的安全性,对于大多数应用来说已经足够安全。
192位密钥:比128位更安全一些,但在实际应用中并不常见。
256位密钥:提供最高的安全性,理论上是最难破解的。
性能:
128位密钥:由于密钥较短,加密和解密速度较快。
192位密钥:加密和解密速度略慢于128位密钥。
256位密钥:加密和解密速度最慢,但通常这种性能差异在现代硬件上是可以接受的。
加密轮数:
128位密钥:加密轮数为10轮。
192位密钥:加密轮数为12轮。
256位密钥:加密轮数为14轮。
密钥生成:
密钥长度越长,生成密钥所需的计算资源越多。
2、如何选择密钥长度
选择哪种密钥长度主要取决于您的安全需求和性能要求:
一般应用:对于大多数普通应用,128位密钥已经足够安全,同时提供了较好的性能。
高安全性需求:如果您处理的是极其敏感的数据,或者需要满足某些合规性要求,那么256位密钥可能是更好的选择。
性能敏感型应用:如果您的应用对性能极为敏感,比如需要处理大量的数据流,那么可能需要权衡安全性和性能,考虑使用128位密钥。
3、加密模式
AES加密支持多种工作模式,每种模式都有其特定的应用场景和优势。以下是AES加密的一些常见模式
1、电码本模式(ECB, Electronic Codebook)
描述:ECB是最简单的模式,它将明文分割成块,并独立地对每个块进行加密。每个块使用相同的密钥进行加密。
优点:实现简单,速度快。
缺点:相同的明文块总是产生相同的密文块,容易暴露模式。
适用场景:适用于小数据量加密,如加密单个密码或小文件。
2、密码分组链接模式(CBC, Cipher Block Chaining)
描述:CBC模式中,每个明文块在加密前与前一个密文块进行异或运算。第一个明文块与一个初始化向量(IV)进行异或。
优点:提高了安全性,相同的明文块在不同的位置会产生不同的密文块。
缺点:加密和解密过程依赖于块间的顺序,无法并行处理。
适用场景:适用于需要较高安全性的场景,如文件加密、数据库加密等。
3、计数器模式(CTR, Counter)
描述:CTR模式使用一个计数器和一个密钥生成伪随机序列,该序列与明文进行异或运算得到密文。计数器可以预先设置,使得加密过程可以并行化。
优点:可以并行处理,适合高速加密。
缺点:需要确保计数器的唯一性。
适用场景:适用于需要高速加密的应用,如网络通信、视频流传输等。
4、输出反馈模式(OFB, Output Feedback)
描述:OFB模式类似于CTR模式,但使用了密文的反馈机制。它使用密钥和一个初始向量生成伪随机序列,然后与明文进行异或运算。
优点:可以并行处理,适用于流式加密。
缺点:错误传播问题,即密文中的错误会影响后续的解密结果。
适用场景:适用于实时传输数据的加密,如音频或视频流。
5、密码反馈模式(CFB, Cipher Feedback)
描述:CFB模式使用密文的反馈机制,将密文的一部分作为输入来生成下一个密文块。
优点:可以并行处理,适用于流式加密。
缺点:错误传播问题,即密文中的错误会影响后续的解密结果。
适用场景:适用于实时传输数据的加密,如网络通信。
选择哪种模式取决于你的具体需求:
如果你关心的是加密速度并且数据块之间没有相关性,可以选择 ECB。
如果你需要更高的安全性,并且数据块之间存在相关性,可以选择 CBC。
如果你需要并行处理数据,可以选择 CTR。
如果你需要处理连续的数据流,并且希望减少错误传播的影响,可以选择 OFB 或 CFB。
在大多数情况下,CBC 和 CTR 是最常用的模式。如果你需要在性能和安全性之间取得平衡,CTR 模式通常是一个不错的选择,因为它既可以并行处理又提供了良好的安全性。
4、填充模式
填充(Padding)模式用于处理不足一个完整分组大小的数据块。填充模式确保所有的数据块都是固定长度的,这样就可以正确地进行加密和解密。以下是几种常见的填充模式
1、PKCS5Padding
描述:PKCS5Padding 是一种常用的填充算法,适用于所有AES加密模式,包括ECB、CBC和CTR模式。在这种模式下,最后一个数据块会被填充到16字节的倍数。填充的字节数等于16减去最后一个数据块的长度(模16)。填充字节的值等于需要添加的字节数。
示例:如果最后一个数据块长度为12字节,则填充4个值为4的字节。
2、PKCS7Padding
描述:PKCS7Padding 与PKCS5Padding非常相似,但适用于任何分组大小(最大为255字节)。填充的字节数等于分组大小减去最后一个数据块的长度(模分组大小)。填充字节的值等于需要添加的字节数。
示例:如果最后一个数据块长度为10字节,分组大小为16字节,则填充6个值为6的字节。
3、ISO10126Padding
描述:ISO10126Padding 也是一种填充算法,它在最后一个数据块的末尾填充一个随机字节序列,然后在序列的末尾添加一个字节,其值等于填充的总字节数。
示例:如果最后一个数据块长度为10字节,则先填充5个随机字节,最后添加一个值为6的字节。
4、ZeroPadding
描述:ZeroPadding 用零字节填充最后一个数据块,直到达到分组大小。
示例:如果最后一个数据块长度为10字节,则填充6个零字节。
5、NoPadding
描述:不填充,如CTR模式,不需要填充,因为它们可以处理任意长度的数据。
示例:在CTR模式下,不需要对数据进行填充。
选择填充模式时,通常会考虑以下几个因素:
安全性:PKCS5Padding 和 PKCS7Padding提供了更好的安全性,因为它们确保了填充字节的值是唯一的,并且可以通过检查填充字节来验证数据的完整性。
兼容性:PKCS5Padding 和 PKCS7Padding 是最常用的标准,因此与其他系统或库的兼容性最好。
性能:ZeroPadding 可能更快,因为它只需要填充零字节,但这可能会降低安全性。注意:jdk自带的加密模块不支持PKCS7Padding 填充,如果需要PKCS7Padding 需要引入第三方支持*
二、使用案例
下面例子将使用CBC模式和PKCS5Padding填充进行AES加密和解密,大家有需要可以直接复制到自己的项目当中使用
import javax.crypto.Cipher;import javax.crypto.KeyGenerator;import javax.crypto.SecretKey;import javax.crypto.spec.IvParameterSpec;import javax.crypto.spec.SecretKeySpec;import java.io.UnsupportedEncodingException;import java.security.NoSuchAlgorithmException;import java.security.SecureRandom;import java.util.Base64;/** * AES加密解密工具类(目前AES比DES和DES3更安全,速度更快,对称加密一般采用AES) */public class AESUtil { private static final String KEY_ALGORITHM="AES"; /** * * 参数按"加密算法/模式/填充模式" 。 * * (1)加密算法有:AES * * (2) 模式有CBC(有向量模式)和ECB(无向量模式)等,使用CBC模式需要定义一个IvParameterSpec对象,使用CBC模式更加安全,一般建议使用CBC模式 * * (3) 填充模式:NoPadding、PKCS5Padding、PKCS7Padding等 */ private static final String CIPHER_ALGORITHM="AES/CBC/PKCS5Padding"; /** * 生成AES密钥 * @param keySize * @return * @throws NoSuchAlgorithmException * @throws UnsupportedEncodingException */ public static String generateKey(int keySize) throws NoSuchAlgorithmException, UnsupportedEncodingException { KeyGenerator keyGenerator=KeyGenerator.getInstance(KEY_ALGORITHM); keyGenerator.init(keySize,new SecureRandom()); SecretKey secretKey=keyGenerator.generateKey(); return new String(Base64.getEncoder().encode(secretKey.getEncoded()),"UTF-8"); } /** * 使用AES加密数据 * @param data 加密数据 * @param key 密钥 * @param iv 初始化随机向量 * @return * @throws Exception */ public static String encrypt(String data, String key,String iv)throws Exception{ //密钥base64解码 byte[] decode = Base64.getDecoder().decode(key); SecretKeySpec secretKeySpec=new SecretKeySpec(decode,KEY_ALGORITHM); byte[] ivBytes = Base64.getDecoder().decode(iv); IvParameterSpec ivSpec = new IvParameterSpec(ivBytes); //根据参数获取加密实例 Cipher cipher=Cipher.getInstance(CIPHER_ALGORITHM); //初始化对象为加密模式 cipher.init(Cipher.ENCRYPT_MODE,secretKeySpec,ivSpec); //加密后的密文进行base64编码 return Base64.getEncoder().encodeToString(cipher.doFinal(data.getBytes())); } /** * 使用AES解密密文 * @param encrypt 加密后的密文 * @param key 密钥 * @param iv 初始化随机向量 * @return * @throws Exception */ public static String decrypt(String encrypt,String key,String iv)throws Exception{ //密钥base64解码 byte[] decode = Base64.getDecoder().decode(key); SecretKeySpec secretKeySpec=new SecretKeySpec(decode,KEY_ALGORITHM); Cipher cipher=Cipher.getInstance(CIPHER_ALGORITHM); byte[] ivBytes = Base64.getDecoder().decode(iv); IvParameterSpec ivSpec = new IvParameterSpec(ivBytes); //初始化对象为解密模式 cipher.init(Cipher.DECRYPT_MODE,secretKeySpec,ivSpec); byte[] dateBytes = Base64.getDecoder().decode(encrypt); //解密并转换为字符串 return new String(cipher.doFinal(dateBytes)); } public static void main(String[] args) throws Exception { // 生成一个 16 字节的随机 IV byte[] ivBytes = new byte[16]; SecureRandom secureRandom = new SecureRandom(); secureRandom.nextBytes(ivBytes); String iv = new String(Base64.getEncoder().encode(ivBytes), "UTF-8"); System.out.println("iv:"+iv); String key = AESUtil.generateKey(128); String plainText ="Hello,world!"; System.out.println("加密前的数据:" + plainText); String encrypt = AESUtil.encrypt(plainText,key,iv); System.out.println("加密后的密文:" + new String(encrypt)); String decryptedText = AESUtil.decrypt(encrypt,key,iv); System.out.println("解密后的数据:" + decryptedText); }}
main方法执行测试结果:
<!-- 包含了基础的密码学功能,比如对称加密(如 AES)、非对称加密(如 RSA)、散列算法(如 SHA-256)以及数字签名等。它是实现加密、数字签名等基本密码学应用的基础 --> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.70</version> <!-- 检查最新的版本 --> </dependency> <!-- 包含了与 PKIX(Public Key Infrastructure X.509)相关的一系列功能,主要包括证书管理、证书验证、证书撤销列表(CRL)等功能。它提供了更高级的密码学服务,比如证书链验证、证书签发等--> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpkix-jdk15on</artifactId> <version>1.70</version> <!-- 检查最新的版本 --> </dependency>
并且增加以下类启动后执行Security.addProvider(new BouncyCastleProvider());来添加安全提供者,然后只需要将上例中的PKCS5Padding改成PKCS7Padding即可 import org.bouncycastle.jce.provider.BouncyCastleProvider;import org.springframework.boot.CommandLineRunner;import org.springframework.stereotype.Component;@Componentpublic class SecurityProviderInitializer implements CommandLineRunner { @Override public void run(String... args) throws Exception { // 添加 Bouncy Castle 为首选的安全提供者 Security.addProvider(new BouncyCastleProvider()); }}
有关非对称加密RSA大家可以看我另一篇: 全网最详细的非对称加密RSA详解
为了帮助更多像你一样的读者,我将持续在专栏中分享技术干货和实用技巧。如果你觉得这篇文章对你有帮助,可以考虑关注我的专栏,谢谢。