java:AES的加密模式和填充模式

块加密,常用的加密模式有ECB、CBC。ECB,即electronic code book,将整个明文分成若干段相同小段,然后每小段进行加密,每段互不依赖,可以并行处理,同样的明文就会生成同样的密文;CBC,即cipher block chaining,密文分组链模式,密文分组间如同链条相互连接,先将明文切割为若干段,每一小段与上一段的密文段运算后(第一个块没有上个密文段,故而使用IV进行运算),再同秘钥进行加密,因为是串行处理,所以同样明文每次生成的密文不一样。

块加密中,常用还有填充模式,对于固定加密算法,每个块有固定大小,如AES的块大小为16个字节的整数倍,明文分块时,如果块大小不够,则需要使用固定数据进行填充。

AES的Cipher.getInstance调用时,使用AES即可,默认使用的分组模式就是ECB,填充模式为PKCS5Padding。如果需要使用CBC模式,则需要加入额外的Iv参数。

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秘钥长度只能为162432: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));
                    块加密,常用的加密模式有ECB、CBC。ECB,即electronic code book,将整个明文分成若干段相同小段,然后每小段进行加密,每段互不依赖,可以并行处理,同样的明文就会生成同样的密文;CBC,即cipher block chaining,密文分组链模式,密文分组间如同链条相互连接,先将明文切割为若干段,每一小段与上一段的密文段运算后(第一个块没有上个密文段,故而使用IV进行运算),再同秘钥进行加密,因为是串行处理,所以同样明文每次生成的密文不一样。
				
package com.tbridge.bcgroup.workorder.utils; import org.apache.commons.codec.binary.Base64; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; * @author Administrator public class AES { // 加密 public static String
一种高级加密标准(英语:Advanced Encryption Standard,缩写:AES)在密码学中又称Rijndael加密法。 美国联邦政府采用的一种区块加密标准。 这个标准用来替代原先的DES。 是对称密钥加密中最流行的算法之一。 学习AES算法首先了解三个点:密钥、填充模式。 实现加密和解密的基础,对明文的加密和解密需要同一个密钥。AES128,AES192,AES256,实际上就是指AES算法对不
ref: https://www.cnblogs.com/3hhh/p/12701209.html https://blog.csdn.net/u013871100/article/details/80100992 三种填充模式的区别(PKCS7Padding/PKCS5Padding/ZeroPadding) 某些加密算法要求明文需要按一定长度对齐,叫做块大小(BlockSize),比如16字节,那么对于一段任意的数据,加密前需要对最后一个块填充到16 字节,解密后需要删除掉填充的数据。 ZeroPa
前一久,在对接支付通道时,遇到上游使用AES加密方式,对方要求加密时使用CBC模式,zeropadding填充,偏移量为0000*4(即16个0),输出十六进制,字符集使用UTF-8。 本以为也没什么问题,可到实际开发时却发现Java虽然支持AES的CBC模式,但填充方式却没有zeropadding模式。通过查看文档,先梳理一下加密算法相关的知识。 JDK1.8支持的加密算法: Cipher......
import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.GCMParameterSpec; public class AESCCMExample { public static void main(String[] args) throws Exception { byte[] key = hexStringToByteArray("00112233445566778899AABBCCDDEEFF"); byte[] nonce = hexStringToByteArray("112233445566778899AABBCC"); byte[] plaintext = "Hello World".getBytes("UTF-8"); // create a cipher object and initialize it for encryption Cipher cipher = Cipher.getInstance("AES/CCM/NoPadding", "BC"); SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); GCMParameterSpec paramSpec = new GCMParameterSpec(128, nonce); cipher.init(Cipher.ENCRYPT_MODE, keySpec, paramSpec); // encrypt the plaintext byte[] ciphertext = cipher.doFinal(plaintext); // initialize the cipher for decryption cipher.init(Cipher.DECRYPT_MODE, keySpec, paramSpec); // decrypt the ciphertext and print the result byte[] decryptedText = cipher.doFinal(ciphertext); System.out.println(new String(decryptedText, "UTF-8")); public static byte[] hexStringToByteArray(String s) { int len = s.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i+1), 16)); return data; 在这个示例中,我们使用了Bouncy Castle作为我们的JCA提供者,可以在代码中看到`Cipher.getInstance("AES/CCM/NoPadding", "BC")`。我们还需要提供AES加密所需的密钥和CCM模式所需的nonce值。然后,我们使用Cipher对象进行加密和解密,并将结果打印到控制台上。 需要注意的是,在实际使用中,需要根据具体的加密需求对参数进行更改,例如CCM模式需要设置nonce长度为7-13字节,并且需要根据加密算法的要求调整密钥长度等参数。