【Java】全网最详细的对称加密AES详解

前言

  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方法执行测试结果:
在这里插入图片描述

  • 如果您需要使用PKCS7Padding填充进行加密,可以在pom中引入如下依赖
<!-- 包含了基础的密码学功能,比如对称加密(如 AES)、非对称加密(如 RSA)、散列算法(如 SHA-256)以及数字签名等。它是实现加密、数字签名等基本密码学应用的基础 -->
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15on</artifactId>
            <version>1.70</version> <!-- 检查最新的版本 -->
        </dependency>
        <!-- 包含了与 PKIXPublic 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;

@Component
public class SecurityProviderInitializer implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        // 添加 Bouncy Castle 为首选的安全提供者
        Security.addProvider(new BouncyCastleProvider());
    }
}

有关非对称加密RSA大家可以看我另一篇: 全网最详细的非对称加密RSA详解

为了帮助更多像你一样的读者,我将持续在专栏中分享技术干货和实用技巧。如果你觉得这篇文章对你有帮助,可以考虑关注我的专栏,谢谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值