AES的java模块包_AES 工具包

CreatedAt: 20200813

JDK Version: Oracle JDK 1.8.0_202

package com.mrathena.toolkit;

import com.mrathena.exception.ServiceException;

import javax.crypto.Cipher;

import javax.crypto.KeyGenerator;

import javax.crypto.SecretKey;

import javax.crypto.spec.IvParameterSpec;

import javax.crypto.spec.SecretKeySpec;

import java.nio.charset.Charset;

import java.nio.charset.StandardCharsets;

import java.security.SecureRandom;

import java.security.spec.AlgorithmParameterSpec;

import java.util.Base64;

import java.util.UUID;

/**

* AES 加密工具

*

* 密钥加密/密钥加密

* 密钥转换/密钥生成

*

* *******************************************

* 字符串格式的密钥在没有特殊说明时都为BASE64编码格式 *

* 字符串格式的密文在没有特殊说明时都为BASE64编码格式 *

* *******************************************

*

* Java中AES加密默认使用AES/ECB/PKCS5Padding, 即 Cipher.getInstance("AES") 等效于 Cipher.getInstance("AES/ECB/PKCS5Padding")

*

* AES密钥长度128/192/256,其中192与256需要配置无政策限制权限文件

* 填充模式最常用的两种PKCS5Padding和PKCS7Padding,其中后者只有BC独有。

*

* CBC加密模式,该模式需要一个初始向量

* 服务端和我们客户端必须使用一样的密钥和初始向量IV。

* 服务端和我们客户端必须使用一样的加密模式。

* 服务端和我们客户端必须使用一样的Padding模式。

*

* 要理解AES的加密流程,会涉及到AES加密的五个关键词,分别是:分组密码体制、Padding、密钥、初始向量IV和四种加密模式(不确定是否有更多)

* 分组密码体制:所谓分组密码体制就是指将明文切成一段一段的来加密,然后再把一段一段的密文拼起来形成最终密文的加密方式。AES采用分组密码体制,即AES加密会首先把明文切成一段一段的,而且每段数据的长度要求必须是128位16个字节,如果最后一段不够16个字节了,就需要用Padding来把这段数据填满16个字节,然后分别对每段数据进行加密,最后再把每段加密数据拼起来形成最终的密文。

* Padding:Padding就是用来把不满16个字节的分组数据填满16个字节用的,它有三种模式PKCS5、PKCS7和NOPADDING。PKCS5是指分组数据缺少几个字节,就在数据的末尾填充几个字节的几,比如缺少5个字节,就在末尾填充5个字节的5。PKCS7是指分组数据缺少几个字节,就在数据的末尾填充几个字节的0,比如缺少7个字节,就在末尾填充7个字节的0。NoPadding是指不需要填充,也就是说数据的发送方肯定会保证最后一段数据也正好是16个字节。那如果在PKCS5模式下,最后一段数据的内容刚好就是16个16怎么办?那解密端就不知道这一段数据到底是有效数据还是填充数据了,因此对于这种情况,PKCS5模式会自动帮我们在最后一段数据后再添加16个字节的数据,而且填充数据也是16个16,这样解密段就能知道谁是有效数据谁是填充数据了。PKCS7最后一段数据的内容是16个0,也是同样的道理。解密端需要使用和加密端同样的Padding模式,才能准确的识别有效数据和填充数据。我们开发通常采用PKCS7 Padding模式。(好像Java默认是PKCS#5Padding)

* 初始向量IV:初始向量IV的作用是使加密更加安全可靠,我们使用AES加密时需要主动提供初始向量,而且只需要提供一个初始向量就够了,后面每段数据的加密向量都是前面一段的密文。初始向量IV的长度规定为128位16个字节,初始向量的来源为随机生成。

* 密钥:AES要求密钥的长度可以是128位16个字节、192位或者256位,位数越高,加密强度自然越大,但是加密的效率自然会低一些,因此要做好衡量。我们开发通常采用128位16个字节的密钥,我们使用AES加密时需要主动提供密钥,而且只需要提供一个密钥就够了,每段数据加密使用的都是这一个密钥,密钥来源为随机生成。

* 四种加密模式:AES一共有四种加密模式,分别是ECB(电子密码本模式)、CBC(密码分组链接模式)、CFB、OFB,我们一般使用的是CBC模式。四种模式中除了ECB相对不安全之外,其它三种模式的区别并没有那么大,

*

* opmode(操作模式)是必须参数,可选值是ENCRYPT_MODE、DECRYPT_MODE、WRAP_MODE和UNWRAP_MODE。

* Key类型参数如果不是非对称加密,对应的类型是SecretKey,如果是非对称加密,可以是PublicKey或者PrivateKey。

* SecureRandom是随机源,因为有些算法需要每次加密结果都不相同,这个时候需要依赖系统或者传入的随机源,一些要求每次加解密结果相同的算法如AES不能使用此参数。

*

* Java Cryptography Architecture

* Standard Algorithm Name Documentation for JDK 8

* Java密码体系结构 JDK 8的标准算法名称文档

* https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html

* https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyPairGenerator

* https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyFactory

* https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#algspec

* https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Cipher

* https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Signature

* 参考

* AES加解密算法的模式介绍

* https://blog.csdn.net/searchsun/article/details/2516191

* JDK安全模块JCE核心Cipher使用详解

* https://blog.csdn.net/zcmain/article/details/90640797

* SecureRandom是随机源,因为有些算法需要每次加密结果都不相同,这个时候需要依赖系统或者传入的随机源,一些要求每次加解密结果相同的算法如AES不能使用此参数

*/

public final class AESKit {

public static void main(String[] args) {

String data = "你好aaa";

String s = generateKeyStr();

String cipher = encryptToStr(s, data);

System.out.println(cipher);

System.out.println(decryptToStr(s, cipher));

}

private AESKit() {}

/**

* 加密算法

*/

private static final String ALGORITHM = "AES";

/**

* 单例 KeyGenerator

*/

private static volatile KeyGenerator instance;

/**

* 默认密钥长度(位),AES密钥长度可以为128/192/256,其中192和256需要配置无政策限制权限文件

*/

private static final int DEFAULT_KEY_SIZE = 128;

/**

* 默认TRANSFORMATION(转换模式)

*/

private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";

/**

* 算法参数透明定义(向量)

*/

private static final AlgorithmParameterSpec IV_PARAMETER_SPEC = new IvParameterSpec(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});

/**

* UTF-8编码

*/

private static final Charset UTF8 = StandardCharsets.UTF_8;

/**

* 单例获取 KeyGenerator

* 默认通过随机源生成128位长度的密钥,如需192/256位长度密钥则需要修改

*/

private static KeyGenerator getKeyGenerator() {

try {

if (null == instance) {

synchronized (AESKit.class) {

if (null == instance) {

KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);

keyGenerator.init(DEFAULT_KEY_SIZE, new SecureRandom(UUID.randomUUID().toString().getBytes()));

instance = keyGenerator;

}

}

}

return instance;

} catch (Throwable cause) {

throw new ServiceException(cause);

}

}

/**

* 生成密钥

*/

public static SecretKey generateKey() {

return getKeyGenerator().generateKey();

}

/**

* 生成密钥

*/

public static String generateKeyStr() {

return toKeyStr(generateKey());

}

/**

* 字符串密钥转标准密钥

*/

public static SecretKey toSecretKey(String secretKeyStr) {

try {

return new SecretKeySpec(decode(secretKeyStr), ALGORITHM);

} catch (Throwable cause) {

throw new ServiceException(cause);

}

}

/**

* 标准密钥转字符串密钥

*/

public static String toKeyStr(SecretKey key) {

return encode(key.getEncoded());

}

/**

* 加密

*/

public static byte[] encrypt(SecretKey secretKey, byte[] data) {

try {

Cipher cipher = Cipher.getInstance(TRANSFORMATION);

cipher.init(Cipher.ENCRYPT_MODE, secretKey, IV_PARAMETER_SPEC);

return cipher.doFinal(data);

} catch (Throwable cause) {

throw new ServiceException(cause);

}

}

/**

* 加密

*/

public static byte[] encrypt(String secretKeyStr, byte[] data) {

return encrypt(toSecretKey(secretKeyStr), data);

}

/**

* 加密

*/

public static byte[] encrypt(SecretKey secretKey, String data) {

return encrypt(secretKey, data.getBytes(UTF8));

}

/**

* 加密

*/

public static byte[] encrypt(String secretKeyStr, String data) {

return encrypt(toSecretKey(secretKeyStr), data.getBytes(UTF8));

}

/**

* 加密

*/

public static String encryptToStr(SecretKey secretKey, byte[] data) {

return encode(encrypt(secretKey, data));

}

/**

* 加密

*/

public static String encryptToStr(String secretKeyStr, byte[] data) {

return encode(encrypt(toSecretKey(secretKeyStr), data));

}

/**

* 加密

*/

public static String encryptToStr(SecretKey secretKey, String data) {

return encode(encrypt(secretKey, data.getBytes(UTF8)));

}

/**

* 加密

*/

public static String encryptToStr(String secretKeyStr, String data) {

return encode(encrypt(toSecretKey(secretKeyStr), data.getBytes(UTF8)));

}

/**

* 解密

*/

public static byte[] decrypt(SecretKey secretKey, byte[] data) {

try {

Cipher cipher = Cipher.getInstance(TRANSFORMATION);

cipher.init(Cipher.DECRYPT_MODE, secretKey, IV_PARAMETER_SPEC);

return cipher.doFinal(data);

} catch (Throwable cause) {

throw new ServiceException(cause);

}

}

/**

* 解密

*/

public static byte[] decrypt(String secretKeyStr, byte[] data) {

return decrypt(toSecretKey(secretKeyStr), data);

}

/**

* 解密

*/

public static byte[] decrypt(SecretKey secretKey, String data) {

return decrypt(secretKey, decode(data));

}

/**

* 解密

*/

public static byte[] decrypt(String secretKeyStr, String data) {

return decrypt(toSecretKey(secretKeyStr), decode(data));

}

/**

* 解密

*/

public static String decryptToStr(SecretKey secretKey, byte[] data) {

return new String(decrypt(secretKey, data), UTF8);

}

/**

* 解密

*/

public static String decryptToStr(String secretKeyStr, byte[] data) {

return new String(decrypt(toSecretKey(secretKeyStr), data), UTF8);

}

/**

* 解密

*/

public static String decryptToStr(SecretKey secretKey, String data) {

return new String(decrypt(secretKey, decode(data)), UTF8);

}

/**

* 解密

*/

public static String decryptToStr(String secretKeyStr, String data) {

return new String(decrypt(toSecretKey(secretKeyStr), decode(data)), UTF8);

}

/**

* 编码

*/

private static String encode(byte[] data) {

return Base64.getEncoder().encodeToString(data);

}

/**

* 解码

*/

private static byte[] decode(String data) {

return Base64.getDecoder().decode(data);

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值