AES对称加密算法加解密实践指南

1、什么是对称加密?

使用相同的密钥(Key)进行数据的加密和解密,安全性依赖于密钥的保密性。

常见对称加密算法:AES(高级加密标准)、DES(数据加密标准)、3DES(三重DES)、RC4等。

2、JAVA加解密常用类

2.1  SecretKeySpec类

        SecretKeySpec 类提供了一种简便的方法,可以直接通过已知密钥(Key)创建 SecretKey,而不需要通过KeyGenerator去创建随机的密钥 和 创建SecretKey 。

2.2  AlgorithmParameters类

        AlgorithmParameters 类提供了获取、设置和管理特定算法相关参数的方式。比如,CBC工作模式下的AES加密算法,需要设置一个初始化向量IV,就可以用这个类设置初始化向量。

2.3  Cipher类

        Cipher 类提供加密和解密的功能,支持多种加密模式(AES、SM4等等)和 填充方式(PKCS5、PKCS7等等)。

3、实际使用

3.1  导入maven依赖

为什么要导入这个依赖,后面会介绍

不想导入这个依赖也可以,加密类有一个相同名的成员变量,用这行代码替代它,但是不推荐。因为PKCS#5适用于64位块大小的加密算法(3DES、DES、RC4),而PKCS#7更通用,适用于任何块大小。

private static final String DEFAULT_ALG_STRING = "AES/CBC/PKCS5Padding";
<!--添加Bouncy Castle 库的依赖-->
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.70</version>
</dependency>

3.2  定义加解密工具类

使用CBC工作模式,需要一个iv作为初始化向量,用于增强使用AES加密算法的安全性。

同一条数据,密钥相同,iv不同的话,加密数据也会不一样,因此,加解密时必须使用相同的key和iv,否则会解密失败。

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidParameterSpecException;
import java.util.Base64;

/**
 * 使用AES对称加密算法的CBC模式进行加解密
 */
public class AESCBCUtil {
    //使用AES算法的 CBC模式 和 PKCS7填充
    private static final String DEFAULT_ALG_STRING = "AES/CBC/PKCS7Padding";

    static {
        //向Java的Security系统添加一个新的安全提供者(Provider),即 BouncyCastle提供者
        //BouncyCastle是一个广泛使用的开源加密库,它提供了大量的加密算法实现,包括一些不在Java标准版中的算法(比如,国密算法SM4等等)
        Security.addProvider(new BouncyCastleProvider());
    }


    /**
     * 初始化Cipher
     *
     * @param encrypt 判断是加密还是解密操作,true表示进行加密
     * @param key     密钥
     * @param iv      初始化向量(CBC模式是必须的)
     * @return
     */
    private static Cipher init(boolean encrypt, String key, String iv) {
        AlgorithmParameters params;
        try {
            params = AlgorithmParameters.getInstance("AES");
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException(e);
        }
        try {
            //设置iv参数
            params.init(new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8)));
        } catch (InvalidParameterSpecException e) {
            throw new IllegalStateException(e);
        }

        //我们已经从外部导入了密钥,因此不需要使用KeyGenerator生成随机的密钥
        //使用 SecretKeySpec,直接根据字节数组生成一个密钥对象
        SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
        Cipher cipher;
        try {
            //创建Cipher实例,并指定加密算法(AES)、模式(CSC)和填充方式(PKCS7)
            cipher = Cipher.getInstance(DEFAULT_ALG_STRING);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("无法获取AES算法实例", e);
        } catch (NoSuchPaddingException e) {
            throw new IllegalStateException("无法获取算法Padding:" + DEFAULT_ALG_STRING, e);
        }
        int mode;
        if (encrypt) {
            //指定操作模式为【加密】
            mode = Cipher.ENCRYPT_MODE;
        } else {
            //指定操作模式为【解密】
            mode = Cipher.DECRYPT_MODE;
        }
        try {
            //根据mode的不同,就能判断是加密操作,还是解密操作
            cipher.init(mode, secretKeySpec, params);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new IllegalStateException(e);
        }
        return cipher;
    }

    /**
     * @param data 明文数据
     * @param key  密钥
     * @param iv
     * @return
     */
    public static String encrypt(String data, String key, String iv) {
        if (data == null) {
            return null;
        }
        //初始化一个加密的Cipher
        Cipher cipher = init(true, key, iv);

        byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
        byte[] encryptBytes = null;
        try {
            //执行实际的加密操作
            encryptBytes = cipher.doFinal(bytes);
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            throw new IllegalStateException(e);
        }
        //使用Base64对字节数组进行加密,获得一个加密数组
        byte[] encode = Base64.getEncoder().encode(encryptBytes);

        return new String(encode, StandardCharsets.UTF_8);
    }

    /**
     * @param encryptData 加密数据
     * @param key         使用加密用到的密钥
     * @param iv          使用加密用到的iv
     * @return
     */
    public static String decrypt(String encryptData, String key, String iv) {
        //初始化一个解密的Cipher
        Cipher cipher = init(false, key, iv);
        if (encryptData == null) {
            return null;
        }
        //通过Base64,先将加密串解密(因为加密数据是通过Base64加密生成的)
        byte[] bytes = Base64.getDecoder().decode(encryptData);
        byte[] decryptBytes = null;
        try {
            //执行实际的解密操作
            decryptBytes = cipher.doFinal(bytes);
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            throw new IllegalStateException(e);
        }
        return new String(decryptBytes, StandardCharsets.UTF_8);
    }

}

3.3 创建启动类

public class Demo1 {
    public static void main(String[] args) {
        String hi = "hello world!";

        //AES算法要求的密钥长度有三种
        //128位-->密钥长度16字节
        //192位-->密钥长度24字节
        //256位-->密钥长度32字节

        //密钥
        String key16= "UMR@202503041014";
        //初始化向量iv,固定为16字节
        String iv = "9876543211234567";
        String encryptData = AESCBCUtil.encrypt(hi, key16, iv);
        System.out.println("加密信息:" + encryptData);

        System.out.println("解密信息:" + AESCBCUtil.decrypt(encryptData, key16, iv));
    }
}

控制台打印结果: 

4、为什么要添加Bouncy Castle 库的依赖?

Bouncy Castle 是一个开源加密库,因为JDK8对 PKCS#7 格式不支持,只支持PKCS#5,因此只有导入第三方的加密库,并注册成为JAVA的安全提供者才行。

如果把加密类的静态代码块屏蔽

就会出现这个问题的原因

5、AES128、AES192、AES256的区别

 AES加密算法的块大小固定是128位(即16字节),而数字128、192、256指的是密钥(key)的长度,分别对应16字节、24字节、32字节。通常选择AES128就已经有足够的安全性了。

另外,如果是CBC模式,所需的iv和块大小相同。比如,AES的块大小固定是128位,那iv也固定为128位(即16字节),不会随key的长度变化而变化。

如果密钥(key)的字节长度不满足,在加解密时,则会提示以下错误信息

比特即“bit”,还有一种称呼叫作“位”,1字节(byte) = 8bit

但1字符并不一定占用1字节,取决于字符编码。

ASCII编码方式下,每个字符占用 1 字节,包括英文字母、数字、标点符号及一些控制字符。

UTF-8编码方式下,字符长度不固定。ASCII 范围内的字符仍占用1字节,中文占用3字节。

GBK编码方式下,中文字符占2字节

作者:_UMR_

物联沃分享整理
物联沃-IOTWORD物联网 » AES对称加密算法加解密实践指南

发表回复