AES(高级加密标准)是一种广泛使用的对称密钥加密算法。

分组加密

分组加密(块加密)— Block Cyper:将明文分组成块,一次加密一个明文分组块。

从国密算法中可以知道,分组加密是对128比特的明文输入和128比特的加密密钥进行处理,生成128比特的密文输出,即分组加密算法只能加密固定长度的明文分组块

但实际情况往往是明文的长度会远超过密码分组的长度,因此需要对分组加密算法进行迭代,才能将明文全部加密,采用的迭代方法就是分组加密算法的加密工作模式。

对称算法一般分为流加密(按照单个bit位或byte字节进行运算)和块加密(根据特定长度分块进行运算)两种类型。

对称加解密中,当需要分块处理数据,数据长度不符合密码规范定义的分组数据包要求时,我们需要按一定的方法填充分组数据包长度,使其满足一定的规则要求。

常见的对称算法基本都属于块加密算法,它们的分块长度也不同:

8字节:DES算法、3DES算法

16字节:AES算法、SM1算法、SSF33算法、SM4算法、SM6算法

1. NoPadding

不填充(原文必须是8个字节的整数倍)

DES加密算法时,要求原文数据长度必须是8个字节的整数倍

AES加密算法时,要求原文数据长度必须是16个字节的整数倍

2. PKCS5Padding

分组数据包的大小为8个字节的倍数,不够就填充补足。

一种基于口令的加密标准(PKCS#5)中定义的填充模式。若数据长度L,对称算法的分块长度B,则需填充B-(L%B)个字节的B-(L%B)。

如使用SM1加密9个字节的数据,此时L=9,B=16,则需填充7个字节的0x07;若L=16,则需填充16个字节的0x10。

填充前:0x12 0x34 0x56 0x78 0x90 0xab 0xcd 0xef 0x01

填充后:0x12 0x34 0x56 0x78 0x90 0xab 0xcd 0xef 0x01 0x07 0x07 0x07 0x07 0x07 0x07 0x07(填充7个0x07)

3. Tips

默认情况下,加密模式和填充模式为:ECB/PKCS5Padding

CBC模式时,在初始化Cipher对象时,需要增加参数,初始化向量IV:IvParameterSpec Iv = new IV:IvParameterSpec(key.getBytes());

一、AES加密模式

1. ECB(电子密码本)模式

ECB模式是将明文分成固定长度的组块,然后对每个明文分组块使用相同的密钥进行独立加密或解密,且该加密的结果将直接作为密文分组。

ECB模式是最简单的分组加密算法的一种工作模式。

当面对大量数据时,ECB模式的加密速度会非常慢。此外,如果攻击者能够解密先前的数据块,那么他们就能够破解后续的数据块。

2. CBC(串行密钥传输)模式

CBC是一种比ECB更加安全的加密模式。

CBC模式是先将明文分成若干个组块,然后每个明文分组与前一密文分组进行异或 XOR 运算,然后再进行加密。因此,每个密文分组都依赖于它前面的所有明文分组。

由于密文分组像链条一样相互连接在一起,因此称为密文分组链接模式。

从加密过程可知,第 00 块明文分组没有上一块密文分组来进行异或,于是引入一个初始化向量 IV(Initialization Vector)IV(Initialization Vector) 来充当第一个干扰项,保证每条消息的唯一性。

但是,CBC模式的速度较慢,因为需要处理大量的中间结果。

3. CTR(计数器)模式

CTR模式与CBC模式非常相似,但是它使用了不同的密钥分配方法。在CTR模式中,每个数据块都由一个随机数生成器产生,该随机数生成器用于确定数据的索引位置。这使得CTR模式非常适合于流式数据传输,例如视频和音频流媒体。CTR模式也非常快,因为它只需要处理每个数据块的中间结果,而不需要像CBC模式那样处理整个数据块。

4. CFB(密文反馈模式)

CFB模式中密码算法的输入是前一个密文分组,再将该密文分组送到密码算法的输入端产生自同步的密钥流。其解密过程几乎就是颠倒的CBC的加密过程。

由于是将密文分组反馈到密码算法中,因此称为密文反馈模式。

二、填充算法

填充算法用于填充输入数据以匹配AES模式的要求。下面是一些常用的填充算法及其优缺点:

1. NoPadding

NoPadding是一种不使用填充算法的加密模式。这意味着输入数据将被直接编码为字节序列,而不会受到填充的影响。虽然NoPadding模式非常安全,但它会导致填充算法无法正确地处理数据。

2. ZerosPadding

ZerosPadding是一种简单的填充算法,它将输入数据的最后一个字节替换为0。这种填充方式适用于所有AES加密模式,包括ECB、CBC和CTR模式。缺点是填充后的字节数量可能超过原始输入数据的大小。

3. PKCS#5 padding

PKCS#5 padding是一种常见的填充算法,用于填充数据以符合AES模式的要求。这种填充方式使用一个固定长度的填充字节序列,以确保输入数据被完全填充。PKCS#5 padding的优点是它可以适应各种输入数据的大小,但缺点是填充过程可能很慢。

4. PKCS#7 padding

PKCS#7 padding也是一种填充算法,它使用一个固定长度的填充字节序列来填充数据。这种填充方式的优点是可以适应各种输入数据的大小,并且填充过程相对较快。但是,它与PKCS#5 padding一样,可能需要较长的填充时间。

三、工具类代码

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
 
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.PrintWriter;
import java.io.StringWriter;
 
/**
 * @Author: Duys
 * @date 2022/4/19 10:19
 * @描述:
 */
@Slf4j
public class AES128Utils {
 
    /**
     * AES_KEY 加密解密用的密钥Key,可以用26个字母和数字组成的16位,此处使用AES-128-CBC加密模式。
     * AES_VECTOR 向量, 普通aes加密解密需要为16位。
     */
    public static final String AES_KEY = "LX%TB*19!@#HPAZW";
    public static final String AES_VECTOR = "123f5678901234ad";
 
 
    /**
     * 加密
     */
    public static String encrypt(String sSrc, String key, String vector) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        byte[] raw = key.getBytes();
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        IvParameterSpec iv = new IvParameterSpec(vector.getBytes());// 使用CBC模式,需要一个向量iv,可增加加密算法的强度
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
        byte[] encrypted = cipher.doFinal(sSrc.getBytes("utf-8"));
        return Base64.encodeBase64String(encrypted);// 此处使用BASE64做转码。
    }
 
    /**
     * 解密
     */
    public static String decrypt(String sSrc, String key, String ivs) {
        try {
            byte[] raw = key.getBytes("ASCII");
            SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            IvParameterSpec iv = new IvParameterSpec(ivs.getBytes());
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
 
            byte[] encrypted1 = Base64.decodeBase64(sSrc);
            byte[] original = cipher.doFinal(encrypted1);
            String originalString = new String(original, "utf-8");
            return originalString;
        } catch (Exception ex) {
            StringWriter sw = new StringWriter();
            PrintWriter printWriter = new PrintWriter(sw);
            ex.printStackTrace(printWriter);
            log.error("解密报错 >>> {}", sw, ex);
            return null;
        }
    }
 
 
    public static void main(String[] args) throws Exception {
        String str = "{\n" +
                "    \"age\": 26,\n" +
                "    \"fullAge\": \"26岁\",\n" +
                "    \"ageUnit\": \"岁\",\n" +
                "    \"areaCode\": \"1001\",\n" +
                "    \"areaName\": \"区政务服务中心\",\n" +
                "    \"checkTime\": \"2023-03-03 10:26:31\",\n" +
                "    \"deptCode\": 1,\n" +
                "    \"eqpCode\": \"-1\",\n" +
                "    \"gatherTime\": \"2023-02-07 11:54:51\",\n" +
                "    \"lczd\": \"临床诊断\",\n" +
                "    \"lisInfoId\": \"1000000072\",\n" +
                "    \"mobile\": \"15614132322\",\n" +
                "    \"name\": \"张三\",\n" +
                "    \"orderTime\": \"2023-02-07 11:54:42\",\n" +
                "    \"patTypeCode\": 1,\n" +
                "    \"patTypeName\": \"门诊\",\n" +
                "    \"receiveTime\": \"2023-02-07 11:49:19\",\n" +
                "    \"reportId\": \"1000000072\",\n" +
                "    \"sex\": \"男\",\n" +
                "    \"ssid\": \"130481199712121111\",\n" +
                "    \"statusId\": 373,\n" +
                "    \"testNum\": \"3\",\n" +
                "    \"testTime\": \"2023-02-07 11:55:19\",\n" +
                "    \"testTypeCode\": 41\n" +
                "  }";
 
        String encryption = null;
        String decrypt = null;
        try {
            encryption = encrypt(str, AES_KEY, AES_VECTOR);//普通加密
            decrypt = decrypt(encryption, AES_KEY, AES_VECTOR);//普通解密
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("加密:" + encryption);
        System.out.println("解密:" + decrypt);
    }
 
}

文章作者: 刘同学
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 刘同学的小站
后端 算法 Java
喜欢就支持一下吧