java:AES的加密模式和填充模式
1 前言
块加密,常用的加密模式有ECB、CBC。ECB,即electronic code book,将整个明文分成若干段相同小段,然后每小段进行加密,每段互不依赖,可以并行处理,同样的明文就会生成同样的密文;CBC,即cipher block chaining,密文分组链模式,密文分组间如同链条相互连接,先将明文切割为若干段,每一小段与上一段的密文段运算后(第一个块没有上个密文段,故而使用IV进行运算),再同秘钥进行加密,因为是串行处理,所以同样明文每次生成的密文不一样。
块加密中,常用还有填充模式,对于固定加密算法,每个块有固定大小,如AES的块大小为16个字节的整数倍,明文分块时,如果块大小不够,则需要使用固定数据进行填充。
AES的Cipher.getInstance调用时,使用AES即可,默认使用的分组模式就是ECB,填充模式为PKCS5Padding。如果需要使用CBC模式,则需要加入额外的Iv参数。
2 使用
package com.xiaoxu.crawler.utils;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.util.StringUtils;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* @author xiaoxu
* @date 2022-11-06 21:24
* crawlerJ:com.xiaoxu.crawler.utils.AESUtils
*/
public class AESUtils {
private static final String AES_ALGORITHM = "AES";
/* AES默认:分组模式:ECB,填充模式:PKCS5Padding */
private static final String AES_ECB_MODE = "AES/ECB/PKCS5Padding";
/* CBC加密模式,需要另外传入IvParameterSpec */
private static final String AES_CBC_MODE = "AES/CBC/PKCS5Padding";
private static final String UTF8 = StandardCharsets.UTF_8.name();
private static final List<Integer> AES_KEYSIZES = Arrays.asList(16, 24, 32);
/* AES分组模式 */
static enum AESGroupMode{
ECB("ECB", false, "ECB分组模式"),
CBC("CBC", true, "CBC分组模式");
/* 分组模式 */
private String code;
/* 是否需要IV向量 */
private boolean needIv;
/* 描述 */
private String desc;
private AESGroupMode(String code, boolean needIv, String desc){
this.code = code;
this.needIv = needIv;
this.desc = desc;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public boolean isNeedIv() {
return needIv;
}
public void setNeedIv(boolean needIv) {
this.needIv = needIv;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public static AESGroupMode getGroupModeByCode(String code){
for (AESGroupMode value : AESGroupMode.values()) {
if(value.getCode().equals(code)){
return value;
}
}
return null;
}
}
public static String newKey(String key){
if(!StringUtils.hasLength(key)){
ExcpUtils.throwExp("new key should not be empty");
}
boolean fl = false;
int len = key.getBytes(StandardCharsets.UTF_8).length;
for (int keySize : AES_KEYSIZES) {
if(len == keySize){
fl = true;
break;
}
}
String newKey = key;
if(!fl){
/* 32、24、16倒序排序 */
List<Integer> new_sizes = AES_KEYSIZES.stream().sorted(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
}).collect(Collectors.toList());
int length = 0;
for (int var0 = 0; var0 < new_sizes.size(); var0++) {
if(len > 0 && len < new_sizes.get(new_sizes.size()-1) && var0 == new_sizes.size()-1){
length = len;
}else if(new_sizes.get(var0) <= len){
length = new_sizes.get(var0);
}
if(new_sizes.get(var0) <= len || (len > 0 && len < new_sizes.get(new_sizes.size()-1)&& var0 == new_sizes.size()-1 ) ){
byte[] nKey = new byte[new_sizes.get(var0)];
for (int f = 0; f < new_sizes.get(var0); f++) {
/* 不足16位的末尾补0 */
nKey[f] = (byte)48;
}
System.arraycopy(key.getBytes(StandardCharsets.UTF_8),0,nKey,0,length);
newKey = new String(nKey, StandardCharsets.UTF_8);
break;
}
}
}
return newKey;
}
/* AES加密 String */
public static String encryptStrAES(String text, String key, String groupMode){
if(!StringUtils.hasLength(text)){
ExcpUtils.throwExp("encode text should not be null or empty.");
}
byte[] encodeBytes = encryptByteAES(text.getBytes(StandardCharsets.UTF_8), key, groupMode);
return Base64.encodeBase64String(encodeBytes);
}
/* AES解密 String*/
public static String decryptStrAES(String text, String key, String groupMode){
if(!StringUtils.hasLength(text)){
ExcpUtils.throwExp("decode text should not be null or empty.");
}
byte[] decodeBytes = decryptByteAES(Base64.decodeBase64(text.getBytes(StandardCharsets.UTF_8)), key, groupMode);
return new String(decodeBytes, StandardCharsets.UTF_8);
}
/* AES加密 originalBytes */
public static byte[] encryptByteAES(byte[] originalBytes, String key, String groupMode){
if(ArrayUtils.isEmpty(originalBytes)){
ExcpUtils.throwExp("encode originalBytes should not be empty.");
}
if(!StringUtils.hasLength(key)){
ExcpUtils.throwExp("key :" + key + ", encode key should not be null or empty.");
}
Cipher cipher = getAESCipher(key, Cipher.ENCRYPT_MODE, groupMode);
byte[] encodeBytes = null;
try {
encodeBytes = cipher.doFinal(originalBytes);
} catch (IllegalBlockSizeException | BadPaddingException e) {
ExcpUtils.throwExp(e.getClass().getName()+": encode byte fail. "+e.getMessage());
}
return encodeBytes;
}
/* AES解密 encryptedBytes */
public static byte[] decryptByteAES(byte[] encryptedBytes, String key, String groupMode){
if(ArrayUtils.isEmpty(encryptedBytes)){
ExcpUtils.throwExp("decode encryptedBytes should not be empty.");
}
if(!StringUtils.hasLength(key)){
ExcpUtils.throwExp("key :" + key + ", decode key should not be null or empty.");
}
Cipher cipher = getAESCipher(key, Cipher.DECRYPT_MODE, groupMode);
byte[] decodeBytes = null;
try {
decodeBytes = cipher.doFinal(encryptedBytes);
} catch (IllegalBlockSizeException | BadPaddingException e) {
ExcpUtils.throwExp(e.getClass().getName()+": decode byte fail. "+e.getMessage());
}
return decodeBytes;
}
public static Cipher getAESCipher(String key, int mode, String groupMode){
if(!StringUtils.hasLength(key)){
ExcpUtils.throwExp("key :" + key + ", should not be null or empty.");
}
Cipher cipher = null;
SecretKey secretKey;
AESGroupMode gMode = AESGroupMode.getGroupModeByCode(groupMode);
AssertUtils.assertNonNull(gMode, "error!gMode is null!");
AESGroupMode aesGroupMode = Optional.ofNullable(gMode).orElse(AESGroupMode.ECB);
try {
/* switch中的值为null时,将会抛出NPE(空指针异常) */
byte[] keyBytes = null;
switch (aesGroupMode){
case ECB:
/* 默认就是ECB分组模式,以及默认的填充模式:PKCS5Padding
故而 Cipher.getInstance使用"AES/ECB/PKCS5Padding",
* 和使用"AES"效果一致 */
cipher = Cipher.getInstance(AES_ECB_MODE);
keyBytes = key.getBytes(UTF8);
secretKey = new SecretKeySpec(keyBytes, AES_ALGORITHM);
cipher.init(mode, secretKey);
break;
case CBC:
cipher = Cipher.getInstance(AES_CBC_MODE);
keyBytes = key.getBytes(UTF8);
secretKey = new SecretKeySpec(keyBytes, AES_ALGORITHM);
/* IvParameterSpec的长度应该为16个字节,
否则报错:Wrong IV length: must be 16 bytes long */
IvParameterSpec ivParameterSpec = new IvParameterSpec(new byte[16]);
cipher.init(mode, secretKey, ivParameterSpec);
break;
default:
/* 抛出异常 */
ExcpUtils.throwExp("aesGroupMode not match, please check");
}
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
ExcpUtils.throwExp(e.getClass().getName()+": get cipher instance wrong. "+e.getMessage());
} catch (UnsupportedEncodingException u){
ExcpUtils.throwExp(u.getClass().getName()+": key transfer bytes fail. "+u.getMessage());
} catch (InvalidKeyException | InvalidAlgorithmParameterException i) {
ExcpUtils.throwExp(i.getClass().getName()+": key is invalid. "+i.getMessage());
}
return cipher;
}
public static void main(String[] args) {
String msg = "小徐123testAes!!!";
// String key = "0382f2bcbacfa8be";
// String key = "0382f2bcbacfa8b";
String key = "0382f2bcbacfa8b0382f2bcbacfa8b0382f2bcbacfa8b0382f2bcbacfa8b0382f2bcbacfa8b";
System.out.println("转换前的秘钥长度:" + key.getBytes(StandardCharsets.UTF_8).length);
System.out.println("转换后秘钥:"+newKey(key)+";长度:"+newKey(key).getBytes(StandardCharsets.UTF_8).length);
// AAz4gPXlduN+l1OX0BV9nWXqJqhXRS3ThRQXJsU0lWM=
System.out.println("AES秘钥长度只能为16、24、32:"+newKey(key).getBytes(StandardCharsets.UTF_8).length);
// String mode = "ECB";
String mode = "CBC";
String s = encryptStrAES(msg, newKey(key), mode);
System.out.println("加密后:"+s);
String s1 = decryptStrAES(s, newKey(key), mode);
System.out.println("解密后:"+s1);
}
}
使用CBC模式,相比于ECB模式,需要额外增加参数IvParameterSpec,且IvParameterSpec的参数字节数组大小,须为16字节,执行结果如下:
转换前的秘钥长度:75
转换后秘钥:0382f2bcbacfa8b0382f2bcbacfa8b03;长度:32
AES秘钥长度只能为16、24、32:32
加密后:U1SEYNpdwxiUbzTEpLgHv2yRr0wiOReAEiOhMfgrX1E=
解密后:小徐123testAes!!!
其余的工具类如下所示:
package com.xiaoxu.crawler.utils;
import com.xiaoxu.crawler.excp.CrawlerForJException;
import org.apache.commons.lang3.ArrayUtils;
/**
* @author xiaoxu
* @date 2022-11-06 15:50
* crawlerJ:com.xiaoxu.crawler.utils.AssertUtils
*/
public class AssertUtils {
/* 校验为真 */
public static void assertTrue(boolean res, String errorMsg){
handlerError(res, errorMsg);
}
/* 校验非空 */
public static <T> void assertNonNull(T obj, String errorMsg){
handlerError(null != obj, errorMsg);
}
/* 校验非null非empty字符串 */
public static <T> void assertNonEmpty(String str, String errorMsg){
handlerError(null != str && !str.isEmpty(), errorMsg);
}
/* 校验非空Array */
public static <T> void assertNonEmptyArray(T[] array, String errorMsg){
handlerError(!ArrayUtils.isEmpty(array), errorMsg);
}
/* 统一异常处理 */
private static void handlerError(boolean flag, String message){
if(!flag){
/* 使用公共异常处理 */
throw new CrawlerForJException(message);
}
}
}
package com.xiaoxu.crawler.utils;
import com.xiaoxu.crawler.excp.AccessParamException;
import com.xiaoxu.crawler.excp.CrawlerForJException;
import org.springframework.util.StringUtils;
/**
* @author xiaoxu
* @date 2022-11-06 16:04
* crawlerJ:com.xiaoxu.crawler.utils.ExcpUtils
*/
public class ExcpUtils {
/* 不为true则抛出异常 */
public static void throwExpIfFalse(boolean result,String msg){
if(StringUtils.hasLength(msg)&&!result){
throw new CrawlerForJException(msg);
}else if(!StringUtils.hasLength(msg)){
throw new AccessParamException(
String.format(
"调用throwExpIfFalse方法的msg不能为空:%s",msg));
}
}
/* 抛出异常的工具方法 */
public static void throwExp(String message){
if(StringUtils.hasLength(message)){
throw new CrawlerForJException(message);
}else{
throw new AccessParamException(
String.format("方法%s的参数不能为空:%s",
ExcpUtils.class.getSimpleName()
+Thread.currentThread().getStackTrace()[1].getMethodName(),
message));
}
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/192104.html