Java中的AES加密解密

Java中的AES加密解密

AES介绍

什么是AES

高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。2006年,高级加密标准已然成为对称密钥加密中最流行的算法之一。

该算法为比利时密码学家Joan Daemen和Vincent Rijmen所设计,结合两位作者的名字,以Rijndael为名投稿高级加密标准的甄选流程。(Rijndael的发音近于"Rhine doll")

AES一般有五种工作体制:1.电码本模式(Electronic Codebook Book (ECB));2.密码分组链接模式(Cipher Block Chaining (CBC));3.计算器模式(Counter (CTR));4.密码反馈模式(Cipher FeedBack (CFB));5.输出反馈模式(Output FeedBack (OFB))。

AES加密位数

AES通常有128、192、256位(bit位)加密,主要区别在于秘钥的长度,分别对应16bytes、24bytes、32bytes。

在线加密和解密工具:https://encode-decode.com/encryption-functions/

Java中的AES

Java中一般把密码通过KeyGenerator以及SecureRandom(一般用hash、SHA256等)把密码长度按照指定的加密位数据改造成对应的字节长度,并不是直接用给定的密码加密或者解密,这样改造后的密码是以初始的密码为种子计算而来,解密的时候也需要先进行相应处理。

这样做的好处就是密码长度没有什么限制,实际密码是根据给定的keySize位数计算,直接得到对应的位数的秘钥。

注意:由于Java使用了hash改造密码,因此要和其他平台兼容的时候不能使用KeyGenerator这个操作,只能直接使用对应长度的秘钥

JDK默认并不支持AES 256,另外还有些加密模式也不支持(如:AES/ECB/PKCS7Padding),建议引入bouncycastle加密库(有些项目可能已经通过第三方库间接引入了)

 <dependency>
      <groupId>org.bouncycastle</groupId>
      <artifactId>bcprov-jdk15on</artifactId>
      <version>1.60</version>
 </dependency>

Java的AES加密解密工具

AES的ECB和CBC模式都有,参考代码如下:

public class SimpleAESUtils {
    private static final Logger logger = LoggerFactory.getLogger(SimpleAESUtils.class);
    private static final String AES = "AES";
    private static final String AES_MODE = "AES/ECB/PKCS7Padding"; // AES电子密码本
    private static final String AES_MODE_CBC = "AES/CBC/PKCS7Padding"; // CBC模式
    static {
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }
    /**
     * 加密
     *
     * @param content  内容
     * @param password 加密密码,长度对应位数,16-128、24-192、32-256,长度位数不对将加密失败
     * @return
     */
    public static byte[] encrypt(String content, byte[] password) {
        try {
            SecretKeySpec key = new SecretKeySpec(password, AES);
            Cipher cipher = Cipher.getInstance(AES_MODE);// 创建密码器
            byte[] byteContent = content.getBytes(StandardCharsets.UTF_8);
            cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化
            return cipher.doFinal(byteContent); // 加密
        } catch (Exception e) {
            logger.error("AES加密失败", e);
        }
        return new byte[0];
    }
    /**
     * 解密
     *
     * @param encryptContent 已加密内容根据实际的内容格式转换成对应的byte数组
     * @param password       加密密码,长度对应位数,16-128、24-192、32-256,长度位数不对将解密失败
     * @return
     */
    public static byte[] decrypt(byte[] encryptContent, byte[] password) {
        try {
            SecretKeySpec key = new SecretKeySpec(password, AES);
            Cipher cipher = Cipher.getInstance(AES_MODE);// 创建密码器
            cipher.init(Cipher.DECRYPT_MODE, key);// 初始化
            return cipher.doFinal(encryptContent); // 加密
        } catch (Exception e) {
            logger.error("AES解密失败", e);
        }
        return new byte[0];
    }
    /**
     * 加密
     *
     * @param content  内容
     * @param password 密码
     * @param keySize  位数
     * @return
     */
    public static byte[] encrypt(String content, String password, int keySize) {
        try {
            byte[] encodedPassword = generatePassword(password, keySize);
            return encrypt(content, encodedPassword);
        } catch (Exception e) {
            logger.error("AES加密失败", e);
        }
        return new byte[0];
    }
    /**
     * 解密
     *
     * @param encryptContent 待解密内容
     * @param password       密码
     * @param keySize        秘钥位数
     * @return
     */
    public static byte[] decrypt(byte[] encryptContent, String password, int keySize) {
        try {
            byte[] encodedPassword = generatePassword(password, keySize);
            return decrypt(encryptContent, encodedPassword);
        } catch (Exception e) {
            logger.error("AES解密失败", e);
        }
        return new byte[0];
    }
    /**
     * 密码长度通过hash改造成对应的字节长度
     *
     * @param password
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static byte[] generatePassword(String password, int keySize) throws NoSuchAlgorithmException {
        KeyGenerator kgen = KeyGenerator.getInstance("AES"); // 秘钥生成器
        SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); // hash算法
        secureRandom.setSeed(password.getBytes());
        kgen.init(keySize, secureRandom);
        SecretKey secretKey = kgen.generateKey();
        return secretKey.getEncoded();
    }
    /**
     * 加密
     *
     * @param content  内容
     * @param password 加密密码,长度对应位数,16-128、24-192、32-256,长度位数不对将加密失败
     * @param iv       向量值
     * @return
     */
    public static byte[] encryptCBC(String content, byte[] password, byte[] iv) {
        try {
            SecretKeySpec key = new SecretKeySpec(password, AES);
            Cipher cipher = Cipher.getInstance(AES_MODE_CBC);// 创建密码器
            byte[] byteContent = content.getBytes(StandardCharsets.UTF_8);
            cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));// 初始化
            return cipher.doFinal(byteContent); // 加密
        } catch (Exception e) {
            logger.error("AES加密失败", e);
        }
        return new byte[0];
    }
    /**
     * 解密
     *
     * @param encryptContent 已加密内容根据实际的内容格式转换成对应的byte数组
     * @param password       加密密码,长度对应位数,16-128、24-192、32-256,长度位数不对将解密失败
     * @param iv             iv 向量值
     * @return
     */
    public static byte[] decryptCBC(byte[] encryptContent, byte[] password, byte[] iv) {
        try {
            SecretKeySpec key = new SecretKeySpec(password, AES);
            Cipher cipher = Cipher.getInstance(AES_MODE_CBC);// 创建密码器
            cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));// 初始化
            return cipher.doFinal(encryptContent); // 加密
        } catch (Exception e) {
            logger.error("AES解密失败", e);
        }
        return new byte[0];
    }
}

ECB模式测试

public static void main(String... args) throws Exception {
    String content = "123";
    String password = "123456";
    int keySize = 256;
    // 指定keySize来加密解密,密码位数没有什么限制
    byte[] encrypt = encrypt(content, password, keySize);
    byte[] decrypt = decrypt(encrypt, password, keySize);
    System.out.println(new String(decrypt, StandardCharsets.UTF_8).equals(content));
    // 这里使用的是Base64格式的密码,decode之后数组的长度对应响应的加密位数
    String password256Base64 = "a7SDfrdDKRBe5FaN2n3GftLKKtkfZS5GsdEjfdjsyBE=";
    byte[] encrypt1 = encrypt(content, Base64.getDecoder().decode(password256Base64));
    byte[] decrypt1 = decrypt(encrypt1, Base64.getDecoder().decode(password256Base64));
    System.out.println(new String(decrypt1, StandardCharsets.UTF_8).equals(content));
}

CBC模式测试

CBC模式除了密码之外还需要一个向量IV值,IV的长度是16字节:

public static void main(String... args) throws Exception {
    String cbcContent = "123";
    String cbcPassword = "a7SDfrdDKRBe5FaN2n3GftLKKtkfZS5GsdEjfdjsyBE=";
    String cbcIv = "1234567812345678";
    byte[] encrypt2 = encryptCBC(cbcContent, Base64.getDecoder().decode(cbcPassword), cbcIv.getBytes());
    byte[] decrypt2 = decryptCBC(encrypt2, Base64.getDecoder().decode(cbcPassword), cbcIv.getBytes());
    System.out.println(new String(decrypt2, StandardCharsets.UTF_8).equals(cbcContent));
}

常见错误

AES要求加密秘钥的位数是128/192/256(对应bytes长度:16/24/32),如果秘钥长度不是这几个值就会报错。

org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher$InvalidKeyOrParametersException: Key length not 128/192/256 bits.
    at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineInit(Unknown Source)
    at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineInit(Unknown Source)
    at java.base/javax.crypto.Cipher.implInit(Cipher.java:839)
    at java.base/javax.crypto.Cipher.chooseProvider(Cipher.java:901)
    at java.base/javax.crypto.Cipher.init(Cipher.java:1286)
    at java.base/javax.crypto.Cipher.init(Cipher.java:1223)
    at com.citsgbt.mobile.gateway.config.security.SimpleAESUtils.encrypt(SimpleAESUtils.java:93)
    at com.citsgbt.mobile.gateway.config.security.SimpleAESUtils.main(SimpleAESUtils.java:182)
Caused by: java.lang.IllegalArgumentException: Key length not 128/192/256 bits.
    at org.bouncycastle.crypto.engines.AESEngine.generateWorkingKey(Unknown Source)
    at org.bouncycastle.crypto.engines.AESEngine.init(Unknown Source)
    at org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher.init(Unknown Source)
    at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher$BufferedGenericBlockCipher.init(Unknown Source)
    ... 8 common frames omitted

JDK中如果没有相关的AES算法模式,会有如下错误:

java.security.NoSuchAlgorithmException: Cannot find any provider supporting AES/ECB/PKCS7Padding
    at java.base/javax.crypto.Cipher.getInstance(Cipher.java:565)
    at com.citsgbt.mobile.gateway.config.security.SimpleAESUtils.encrypt(SimpleAESUtils.java:91)
    at com.citsgbt.mobile.gateway.config.security.SimpleAESUtils.encrypt(SimpleAESUtils.java:131)
    at com.citsgbt.mobile.gateway.config.security.SimpleAESUtils.main(SimpleAESUtils.java:177)

需要引入第三方的加密库,并在代码中添加进去:

if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
    Security.addProvider(new BouncyCastleProvider());
}
上一篇
下一篇