山雨欲来

V1

2022/02/09阅读:80主题:橙心

AES加密原理

AES加密原理

简介

密码学中的高级加密标准(全称:Advanced Encryption Standard, AES),又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准,也是DES算法的替代者,当今最流行的对称加密算法之一。对称加密就是加密和解密数据时,需要使用相同的密钥。

要学习AES算法,首先要搞清楚AES的基本概念:密钥、填充模式、加密模式等等这些概念。

场景

假设有一方发送想接收方发送消息,如果对内容没有做任何加密,发送方想接收方发送了一个明文消息"小明在吗?"。这段消息可能会被中间人窥探到,以至于暴露了通信双方的内容。因此我们不能传送明文,必须进行加密。双方通过使用对称加密的方式传输密文。

AES的基本结构

AES为分组密码,分组密码也就是把明文分成一组一组的,每组长度相等,每次加密一组数据,直到将整个明文加密完成,之后在将一块块密文块拼接起来,形成密文。

在AES标准规范中,分组长度只能是128位,也就是说,每个分组为16个字节(每个字节8位)。

密钥长度可以使用128位、192位或256位。密钥的长度不同,推荐加密轮数也不同。

AES 密钥长度(32位比特字) 分组长度(32位比特字) 加密轮数
AES-128 4 4 10
AES-192 6 4 11
AES-256 8 4 14

填充模式

填充模式是做什么的?我们在一些AES加密解密网站中总是能够看到填充模式,在我们进行加解密时,必须选择一个填充模式。

这是因为如果明文不是128位(16字节)的则需要进行填充,需要将明文补充到16个字节整数倍的长度。在我们进行加解密时需要采用同样的填充方式,否则无法解密成功。填充模式有:No Padding、PKCS5 Padding、PKCS7 Padding、ISO10126 Padding、Ansix923 Padding、Zero Padding等等。

No Padding

不需要填充,但是明文必须是16个字节的整数倍。如果不是,那么就需要使用者自己去实现填充。除了这种模式外,其他的填充模式如果已经是16个字节的数据的话,会在填充一个16字节的数据。

PKCS5 Padding

在明文的末尾进行填充,填充的数据时当前和16个字节相差的数量。

这里明文长度明显不足16个字节,那么就需要进行填充。明文长度为14,则填充字节数为216进制为02
明文:70 61 73 73 77 6f 72 64 54 65 78 74 43 61
填充后:70 61 73 73 77 6f 72 64 54 65 78 74 43 61 02 02

PKCS7 Padding

在明文的末尾进行填充,填充的数据时当前和16个字节相差的数量。

这里明文长度为14,则填充字节数为216进制为02
明文:70 61 73 73 77 6f 72 64 54 65 78 74 43 61
填充后:70 61 73 73 77 6f 72 64 54 65 78 74 43 61 02 02

ISO10126 Padding

在明文的末尾进行填充,当前和16个字节相差的数量填写在最后,其余字节填充随机数。

当前和16个字节相差的数量写在最后,所以填充后倒数第二字节填充随机数,最后一个字节是与16个字节相差的数量。
明文:70 61 73 73 77 6f 72 64 54 65 78 74 43 61
填充后:70 61 73 73 77 6f 72 64 54 65 78 74 43 61 01 02

Ansix923 Padding

最后一个字节填充字节序列的长度,其余字节均填充数字0。

明文:70 61 73 73 77 6f 72 64 54 65 78 74 43 61
填充后:70 61 73 73 77 6f 72 64 54 65 78 74 43 61 00 02

Zero padding

  • 后面补0,缺多少就补多少个0
明文:70 61 73 73 77 6f 72 64 54 65 78 74 43 61
填充后:明文:70 61 73 73 77 6f 72 64 54 65 78 74 43 61 00 00

除了No padding这种填充模式,其余的填充模式,如果已经是16个字节的数据,会在填充一个16字节的数据.

明文:70 61 73 73 77 6f 72 64 54 65 78 74 43 61 73 65
填充后:{70 61 73 73 77 6f 72 64 54 65 78 74 43 61 73 65 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00}

加密模式

ECB(Electronic Codebook Book 电码本)模式

使用ECB模式加密时,相同的明文分组会被转换为相同的密文分组,我们可以将其裂解为是一个巨大的"明文分组->密文分组"的对应表,因此ECB模式也称为电码本模式。

上文中说道:"AES为分组密码,分组密码也就是把明文分成一组一组的,每组长度相等,每次加密一组数据,直到加密完整个明文"。就是将数据分成不同的组(块)进行加密。加密完成后,在将加密后的数据拼接起来。

优点
  • 简单、速度快、可并行;
缺点
  • 如果明文块相同,则生成的密文块也相同,这样会导致安全性降低;
  • ECB模式容易受到攻击,攻击者无需破译密码就能操纵明文;

CBC(Cipher Block Chaining 密码分组链接)模式

为了解决ECB模式的密文块相同的缺点,CBC的模式引入了一个初始向量(iv)的概念,该向量必须是一个与密钥长度相等的数据,在第一次加密前,会使用初始化向量与第一块数据做异或运算,生成的新数据再进行加密,加密第二块之前,会拿第一块的密文数据与第二块明文进行异或运算后再进行加密,以此类推,解密时也就是在解密后,再进行异或运算,生成最终的明文。

在使用CBC模式是应该注意以下几点:

  • 向量必须是一个与密钥长度相等的数据。
  • 由于在加密前和解密后都会做异或运算,因此我们的明文可以不用补全,不是16个字节的倍数也可以,CBC中会自动用0补全进行异或运算。
  • 在解密时,是在解密后才会再做异或运算,保证数据解密成功。
  • 由于自动进行了补全,所以解密出的数据也会在后面补全0,因此获取到数据时,需要将末尾的0去除,或者根据源数据长度来截取解密后的数据
优点
  • 每次加密密钥不同,提高了安全性;
缺点
  • 加密无法并行运算,但是解密可以并行,必须在前一个块加密完成后,才能加密后块,并且也需要填充0在后面,所以并不适合流数据(不适合的原因可能是,需要满足128位的数据之后才能进行加密,这样后面才不会有0的补全)
  • 如果前一个数据加密错误,那么后续的数据都是错的了
  • 两端需要同时约定初始向量iv

CTR(Counter 计算器)模式

CTR模式是一种通过将逐次累加的计数器进行加密来生成密钥流的流密码。

CTR模式中,每个分组对应一个逐次累加的计数器,并通过对计数器进行加密来生成密钥流。也就是说,最终的密文分组时通过将计数器加密而得到的比特序列,与明文分组进行XOR而得到的。

每次加密时都会生成一个不同的值作为计数器的初始值。当分组长度为128bit时,计数器的初始值可能如下图所示:

优点
  • CTR模式的加密和解密使用了完全相同的模式,因此在程序上实现上比较容易。
  • CTR模式中可以以任意顺序对分组进行加密和解密,因为在加密和解密时需要用到的“计数器”的值可以由nonce和分组序号直接计算出来。
  • 能够以任意顺序处理分组,意味着能够实现并行计算。在支持并行计算的系统中,CTR模式的速度是非常快的。
  • 假设CTR模式的密文分组中有一个比特被反转了,则解密后明文分组中仅有与之对应的比特会被反转,这一错误不会放大。
  • CTR模式中,如果对密钥流的一个分组进行加密后碰巧和加密前是相同的,那么这个分组之后的密钥流就会变成同一值的不断反复,这个在CTR中不存在这一问题。

OFB(Output FeedBack 输出反馈)模式

该模式与CFB类似,OFB模式不是通过密码算法对明文直接加密的,而是通过将"明文分组"和"密码算法的输出"进行XOR异或来产生"密文分组"的。它是将iv或者上一个iv加密后的数据加密,生成的key与明文做异或运算。解密时采用的是同样的方法,利用了异或运算的对称性来进行加解密,除了这一点,其余与CFB一致。

OFB模式中,密码算法的输入则是密码算法前一个输出,也就是将输出反馈给密码算法,因此就有了"输出反馈模式"这个名字。

优点

OFB模式中,XOR所需要的比特序列(密钥流)可以事先通过密码算法生成,和明文分组无关。只要提前准备好所需的密钥流,则在实际从明文生成密文的过程中,就完全不需要动用密码算法了,只要将明文与密钥流进行XOR就可以了。和AES等密码算法相比,XOR运算是非常快的。这就意味着只要提前准备好密钥流就可以快速完成加密。换个角度来看,生成密钥流的操作和进行XOR运算的操作是可以并行的。

CFB(Cipher FeedBack 密码反馈)模式

这个模式类似于CBC模式,可以将块密码变为自同步的流密码;工作过程非常类似。CFB的解密过程几乎就是颠倒的CBC的加密过程。

在生成第一个密文分组时,由于不存在前一个输出的数据,因此需要使用初始化向量(IV)来代替。所以我们每次加密时,都需要生成一个不同的随机比特序列用作初始化向量。

首先需要使用一个与块(密钥)大小相同的移位寄存器,并用IV将寄存器初始化,然后,将寄存器内容使用块(密钥)密码加密,然后将结果的最高x位与明文的x位进行异或,以产生密文的x位。下一步将生成的x位密文移入寄存器中,并对下面的x位明文重复这一过程。解密过程与加密过程相似,以IV开始,对寄存器加密,将结果的高x位与密文异或,产生x位明文,再将密文的下面x位移入寄存器。

CFB模式中,密码算法的输入是前一个密文分组,也就是将密文分组反馈到密码算法中,因此有了“密文反馈算法”这个名字。

优点
  • 与CBC类似,解密过程可以并行化。
缺点
  • 明文的改变会影响接下来的所有的密文,因此加密过程不能并行化。

AES的加解密原理

上面我们说道,AES的密钥支持三种长度:AES128、AES192、AES256。密钥的长度决定了AES加密的轮数。并且不同阶段的Round有不同的处理步骤。我们可以分为初始轮普通轮最终轮

初始轮它只做一个操作:就是轮密钥加(AddRoundKey)

普通轮有四个操作步骤:①字节代换(SubBytes)、②行移位(ShiftRows)、③列混淆(MixColumns)、④加轮密钥(AddRoundKey)

最终轮有三个操作步骤:①字节代换(SubBytes)、②行移位(ShiftRows)、③轮密钥加(AddRoundKey)。注意在最终轮中,没有列混淆这个操作。

AES整体结构图如下:

下面我们来看看每个操作是做了些什么?

AES加密

初始轮轮密钥加

初始轮密钥加就是将明文与密钥之间进行异或操作。如下图操作:

w[0,3]可以看成这样,该序列的前4个元素W[0],W[1],W[2],W[3]是原始密钥,用于加密运算中的初始密钥加。后面40个字分为10组,每组4个字(128比特)分别用于10轮加密运算中的轮密钥加。

密钥扩展

上图中我们可以看到有一个长度为44的二维数组,这个数组是通过密钥编排函数将密钥矩阵扩展成一个44个字节组成的序列W[0],W[1], … ,W[43],该序列的前4个元素W[0],W[1],W[2],W[3]是原始密钥,用于加密运算中的初始密钥加。后面40个字分为10组,每组4个字(128比特)分别用于10轮加密运算中的轮密钥加。

这个44矩阵的每一列的4个字节组成一个字,矩阵4列的4个字依次命名为W[0]、W[1]、W[2]和W[3],它们构成一个以字为单位的数组W。例如,设密钥K为"abcdefghijklmnop",则K0 = ‘a’,K1 = ‘b’, K2 = ‘c’,K3 = ‘d’,W[0] = “abcd”、W[1]="efgh"。接着我们需要对W数组扩充剩下的40个新列,构成总共44列的密钥扩展数组。计算方式如下:

  • 如果i不是4的倍数,那么第i列由如下等式确定:W[i] = W[i-4] xor W[i-1];
  • 如果i是4的倍数,那么第i列由如下等式确定:W[i] = W[i-4] xor T(W[i-1]); 我们可以看到当i是4的倍数,出现了一个T函数。这个T函数由3部分组成:字循环、字节代换、轮常量异或。

轮常量Rcon[j]是一个字,其值见下表。

下面我们来举个案例: 随意假设一个128位的初始密钥为3C A1 0B 21 57 F0 19 16 90 2E 13 80 AC C1 07 BD(顺序为从上到下,从左至右)。这个图中我们要求W[4],我们发现4是4的倍数,所以使用这个等式:W[i] = W[i-4] xor T(W[i-1]),也就是W[4] = W[0] xor T(W[3])。 W[3]的值是我们的原始密钥,所以将W[3]代入T函数中。

  1. 首先先是字循环,字循环就是将1个字中的4个字节循环左移1个字节。即{AC,C1,07,BD}变成了{C1,07,BD,AC} 。
  2. 字节代换,对字循环的结果使用S盒进行字节代换。将{C1,07,BD,AC} 作为S盒的输入,输出为{78,C5,7A,91}。
  3. 将字节代换的出来的结果与第一轮轮常量Rcon[1]进行异或计算,将得到{79,C5,7A,91}。之后再与W[0]进行异或运算,W[4] = {3C,A1,0B,21} xor {79,C5,7A,91} = {45,64,71,B0}。 以上就计算出了W[4],后面W[5]、W[6]、W[7]都是如此,计算完W[4]、W[5]、W[6]、W[7]之后,第一轮要使用的子密钥矩阵就已经计算完成了。

字节代换

AES的字节代换就是一个简单的查表操作。AES定义了一个S盒和一个逆S盒。S盒用于加密;逆S盒用于解密。

状态矩阵中的元素按照下面的方式映射为一个新的字节:把该字节的高4位作为行值,低4位作为列值,取出S盒或者逆S盒中对应的行的元素作为输出。例如,加密时,输出的字节S1为0x12,则查S盒的第0x01行和0x02列,得到值0xc9,然后替换S1原有的0x12为0xc9。状态矩阵经字节代换后的图如下:

行移位

行移位是一个简单的左循环移位操作。状态矩阵:第0行左移0字节,第1行左移1字节,第2行左移2字节,第三行左移3字节。如下图所示:

如果解密的话,做右移位操作,状态矩阵:第0行右移0字节,第1行右移1字节,第2行右移2字节,第3行右移3字节。

列混合

列混合变换是通过矩阵相乘来实现的,经行移位后的状态矩阵与固定的矩阵相乘,得到混淆后的状态矩阵。

下图的这个正矩阵是给定的。

状态矩阵中的第j列(0 <= j <=3)的列混合可以表示为下图所示:

其中,矩阵元素的乘法和加法都是定义在基于GF(2^8)上的二元运算,并不是通常意义上的乘法和加法。这种二元运算的加法等价于两个字节的异或,乘法则复杂一点。对于一个8位的二进制数来说,使用域上的乘法乘以(00000010)等价于左移1位(低位补0)后,再根据情况(二进制的最高位是否为0)同(00011011)进行异或运算。

我们设一个8位二进制数(a7a6a5a4a3a2a1a0),如:S1=(10011001),如0x02 * S1如下图所示:

  • 如果a7为1,则先进行乘法运算再与00011011进行异或运算,否则不进行;

案例: 我们先获取到行移位计算后的结果,根据给定的正矩阵开始进行计算。根据下图公式代入即可:

代入式子案例如下:
(0x02 * 0xD4) xor (0x03 * 0xBF) xor (0x01 * 0x5D) xor (0x01 * 0x30)
= (10110011) xor (1101 1010) xor (0101 1101) xor (0011 0000)
0000 0100


0x02:0000 0010
0xD4:1101 0100      0xD4 16进制换成二进制,最高位为1,所以乘法算完后,还需要再与00011011进行异或运算

文中提到"对于一个8位的二进制数来说,使用域上的乘法乘以(00000010)等价于左移1位(低位补0)",所以0xD4 乘以0x02 得 1010 1000。之后再与00011011进行异或运算,得出10110011


0x03:0000 0011
0x02:0000 0010
0x01:0000 0001

0xBF:1011 1111
0x020x01进行异或可以得到0x03,所以我们可以这么写:(0x02 xor 0x01) * 0xBF
(0x02 * 0xBF) xor 0xBF

首先先计算乘法
0xBF1011 1111 -> 0111 1110
之后与0001 1011进行异或
0111 1110 xor 0001 1011 = 0110 0101
之后0110 0101 与 0xBF进行异或,得出 1101 1010

最终结果为0x04

列混合解密

同样用以上的方式进行解密。只不过正矩阵被换成了逆变换的矩阵,也是给定的一个矩阵。

轮密钥加

通过密钥编排函数该密钥矩阵被扩展成一个44个字节组成的序列W[0],W[1], … ,W[43],该序列的前4个元素W[0],W[1],W[2],W[3]是原始密钥用于加密运算中的初始密钥加。后面40个字节分为10组,每组4个字节(128比特)分别用于10轮加密运算中的轮密钥加。

在上文密钥扩展中,我们通过案例已经介绍了密钥矩阵如何被扩展成一个44个字节组成的序列。在我们进行第一轮加密时,我们就需要计算出第一轮要使用的轮密钥加,也就是W[4]、W[5]、W[6]、W[7]。

首先我们需要拿到列混合之后的状态矩阵,然后根据此状态矩阵同128位轮密钥进行逐位异或操作。

案例: 假设我们经过列混合之后,拿到的结果是:

第一轮子密钥矩阵结果是:

第一轮的轮密钥加结果是通过列混合结果与子密钥矩阵结果相互之间进行异或得到的。 比如:0x04 xor 0xAO = 0xA4

轮密钥加的逆运算同正向的轮密钥加运算完全一致,这是因为异或的逆操作是其自身。轮密钥加非常简单,但却能够影响S数组中的每一位。

AES解密

在文章开始的图中,有AES解密的流程图,可以对应那个流程图来进行解密。下面介绍的是另一种等价的解密模式,流程图如下图所示。这种等价的解密模式使得解密过程各个变换的使用顺序同加密过程的顺序一致,只是用逆变换取代原来的变换。

AES算法实现(Java版)

给定矩阵常量

package com.hbh.AESTest.constant;

public interface AESConstants {


    /**
     * 正矩阵  用于列混合
     */

    short[][] CX = {
            {0x020x030x010x01},
            {0x010x020x030x01},
            {0x010x010x020x03},
            {0x030x010x010x02},
    };
    
    // 逆变换矩阵  用于列混合
    short[][] INVERSE_CX = {
            {0x0E0x0B0x0D0x09},
            {0x090x0E0x0B0x0D},
            {0x0D0x090x0E0x0B},
            {0x0B0x0D0x090x0E},
    };


    short[] LEFT_SHIFT_TABLE = {1230};

    short[] INVERSE_LEFT_SHIFT_TABLE = {1230};

    // 轮常量数组
    short[][] R_CON = {
            {0x000x000x000x00},
            {0x010x000x000x00},
            {0x020x000x000x00},
            {0x040x000x000x00},
            {0x080x000x000x00},
            {0x100x000x000x00},
            {0x200x000x000x00},
            {0x400x000x000x00},
            {0x800x000x000x00},
            {0x1b0x000x000x00},
            {0x360x000x000x00},
    };

    // 加密操作行移位变换中决定字间字节循环左移规则的矩阵
    short[][] SHIFTING_TABLE = {
            {0123},
            {12 ,30},
            {2301},
            {3012},
    };

    // 解密操作行移位变换中决定字间字节循环左移规则的矩阵
    short[][] INVERSE_SHIFTING_TABLE = {
            {0321},
            {10 ,32},
            {2103},
            {3210},
    };

    /**
     * s盒
     */

    short[][] SUBSTITUTE_BOX = {
            {0x630x7c0x770x7b0xf20x6b0x6f0xc50x300x010x670x2b0xfe0xd70xab0x76},
            {0xca0x820xc90x7d0xfa0x590x470xf00xad0xd40xa20xaf0x9c0xa40x720xc0},
            {0xb70xfd0x930x260x360x3f0xf70xcc0x340xa50xe50xf10x710xd80x310x15},
            {0x040xc70x230xc30x180x960x050x9a0x070x120x800xe20xeb0x270xb20x75},
            {0x090x830x2c0x1a0x1b0x6e0x5a0xa00x520x3b0xd60xb30x290xe30x2f0x84},
            {0x530xd10x000xed0x200xfc0xb10x5b0x6a0xcb0xbe0x390x4a0x4c0x580xcf},
            {0xd00xef0xaa0xfb0x430x4d0x330x850x450xf90x020x7f0x500x3c0x9f0xa8},
            {0x510xa30x400x8f0x920x9d0x380xf50xbc0xb60xda0x210x100xff0xf30xd2},
            {0xcd0x0c0x130xec0x5f0x970x440x170xc40xa70x7e0x3d0x640x5d0x190x73},
            {0x600x810x4f0xdc0x220x2a0x900x880x460xee0xb80x140xde0x5e0x0b0xdb},
            {0xe00x320x3a0x0a0x490x060x240x5c0xc20xd30xac0x620x910x950xe40x79},
            {0xe70xc80x370x6d0x8d0xd50x4e0xa90x6c0x560xf40xea0x650x7a0xae0x08},
            {0xba0x780x250x2e0x1c0xa60xb40xc60xe80xdd0x740x1f0x4b0xbd0x8b0x8a},
            {0x700x3e0xb50x660x480x030xf60x0e0x610x350x570xb90x860xc10x1d0x9e},
            {0xe10xf80x980x110x690xd90x8e0x940x9b0x1e0x870xe90xce0x550x280xdf},
            {0x8c0xa10x890x0d0xbf0xe60x420x680x410x990x2d0x0f0xb00x540xbb0x16},
    };

    /**
     * 逆s盒
     */

    short[][] INVERSE_SUBSTITUTE_BOX = {
            {0x520x090x6a0xd50x300x360xa50x380xbf0x400xa30x9e0x810xf30xd70xfb},
            {0x7c0xe30x390x820x9b0x2f0xff0x870x340x8e0x430x440xc40xde0xe90xcb},
            {0x540x7b0x940x320xa60xc20x230x3d0xee0x4c0x950x0b0x420xfa0xc30x4e},
            {0x080x2e0xa10x660x280xd90x240xb20x760x5b0xa20x490x6d0x8b0xd10x25},
            {0x720xf80xf60x640x860x680x980x160xd40xa40x5c0xcc0x5d0x650xb60x92},
            {0x6c0x700x480x500xfd0xed0xb90xda0x5e0x150x460x570xa70x8d0x9d0x84},
            {0x900xd80xab0x000x8c0xbc0xd30x0a0xf70xe40x580x050xb80xb30x450x06},
            {0xd00x2c0x1e0x8f0xca0x3f0x0f0x020xc10xaf0xbd0x030x010x130x8a0x6b},
            {0x3a0x910x110x410x4f0x670xdc0xea0x970xf20xcf0xce0xf00xb40xe60x73},
            {0x960xac0x740x220xe70xad0x350x850xe20xf90x370xe80x1c0x750xdf0x6e},
            {0x470xf10x1a0x710x1d0x290xc50x890x6f0xb70x620x0e0xaa0x180xbe0x1b},
            {0xfc0x560x3e0x4b0xc60xd20x790x200x9a0xdb0xc00xfe0x780xcd0x5a0xf4},
            {0x1f0xdd0xa80x330x880x070xc70x310xb10x120x100x590x270x800xec0x5f},
            {0x600x510x7f0xa90x190xb50x4a0x0d0x2d0xe50x7a0x9f0x930xc90x9c0xef},
            {0xa00xe00x3b0x4d0xae0x2a0xf50xb00xc80xeb0xbb0x3c0x830x530x990x61},
            {0x170x2b0x040x7e0xba0x770xd60x260xe10x690x140x630x550x210x0c0x7d},
    };
}

加密解密核心逻辑

package com.hbh.AESTest.core;

import com.hbh.AESTest.constant.AESConstants;
import com.hbh.AESTest.operation.*;


/**
 * @program: java_basis
 * @description:
 * @author: hbh
 * @create: 2022-02-05 16:10
 */

public class AESCore {
    /**
     *
     * @param initialPTState 明文或密文的状态数组
     * @param roundKeys 加、解密要用到的轮密钥数组
     * @param substituteTable 加、解密要用到的S盒
     * @param mixColumnTable 列混合中用来取代即约多项式的数组
     * @param shiftingTable 行变换中用来决定字间左移的位数的数组
     * @return
     */

    public static short[][] coreEncrypt(short[][] initialPTState, short[][][] roundKeys, short[][] substituteTable, short[][] mixColumnTable, short[][] shiftingTable){
        // 初始轮密钥加,异或操作
        short[][] state = InitialUtils.xor(roundKeys[0],initialPTState);

        // 处理前九轮变换
        for (int i = 0; i < 9; i++) {
            // 将状态数组的字节替换为S盒中相应位置的字节
            state = RoundKeyExtensionUtils.substituteState(state, substituteTable);

            // 行移位变换
            state = ShiftRow.shiftRows(state, shiftingTable);
            // 列混合变换
            state = MixColumn.mixColumns(state, mixColumnTable);
            // 轮密钥加变换
            state = InitialUtils.xor(roundKeys[i + 1], state);
        }

        // 处理最后一轮
        state = RoundKeyExtensionUtils.substituteState(state, substituteTable);
        state = ShiftRow.shiftRows(state, shiftingTable);
        state = InitialUtils.xor(roundKeys[roundKeys.length - 1], state);
        return state;
    }





    /**
     * 解密逻辑:通过将可逆操作抽取成可逆矩阵, 复用加密核心函数
     * @param encryptedTextState initial encrypted text state
     * @param keyState initial key state
     * @return decrypted state
     */

    public static short[][] coreDecrypt(short[][] encryptedTextState, short[][] keyState) {
        // obtain raw round keys
        short[][] rawRoundKeys = RoundKeyExtensionUtils.generateRoundKeys(keyState);

        // make it easier to obtain a whole block of round key in a round transformation
        short[][][] roundKeys = transfer(rawRoundKeys); // 将密钥扩展搞成3维数组

        // 对中间9个密钥进行逆列混合变换
        for (int i = 1; i < roundKeys.length - 1; i++) {
            roundKeys[i] = MixColumn.mixColumns(roundKeys[i], AESConstants.INVERSE_CX);
        }

        short[][][] inverseRoundKeys = inverseRoundKeys(roundKeys);
        return coreEncrypt(encryptedTextState, inverseRoundKeys, AESConstants.
                INVERSE_SUBSTITUTE_BOX, AESConstants.INVERSE_CX, AESConstants.INVERSE_SHIFTING_TABLE);
    }

    /**
     * [解密] 将解密扩展密钥数组逆转,方便复用核心加密操作,
     * @param roundKeys 解密扩展密钥数组
     * @return 逆转了的解密扩展密钥数组
     */

    private static short[][][] inverseRoundKeys(short[][][] roundKeys) {
        short[][][] result = new short[roundKeys.length][4][4];
        int length = roundKeys.length;
        for (int i = 0; i < roundKeys.length; i++) {
            result[i] = roundKeys[length - 1 - i];
        }
        return result;
    }


    private static short[][][] transfer(short[][] origin) {
        short[][][] result = new short[origin.length / 4][4][4];
        for (int i = 0; i < origin.length / 4; i++) {
            short[][] temp = new short[4][4];
            System.arraycopy(origin, i * 4, temp, 04);
            result[i] = temp;
        }
        return result;
    }

}

初始变换

package com.hbh.AESTest.operation;

/**
 * @program: java_basis
 * @description: 初始变换操作工具类
 * @author: hbh
 * @create: 2022-02-05 13:57
 */

public class InitialUtils {

    /**
     *
     * @param first  明文
     * @param second 密钥
     * @return
     */

    public static short[][] xor(short[][] first, short[][] second){
        short[][] res = new short[first.length][4];

        for (int i = 0; i < first.length; i++) {
            for (int j = 0; j < second.length; j++) {
                res[i][j] = (short) (first[i][j] ^ second[i][j]);
            }
        }
        return res;
    }
}

行移位

package com.hbh.AESTest.operation;

/**
 * @program: java_basis
 * @description: 行位移变换:属于置换,属于线性变换,本质在于把数据打乱重排,起扩散作用
 * @author: hbh
 * @create: 2022-02-05 16:00
 */

public class ShiftRow {

    /**
     * 行移位变换,对状态的行进行循环左移
     * @param state
     * @param shiftingTable
     * @return
     */

    public static short[][] shiftRows(short[][] state, short[][] shiftingTable){
        short[][] result = new short[state.length][4];
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < state.length; j++) {
                result[j][i] = state[shiftingTable[j][i]][i];
            }
        }
        return result;
    }
}

密钥扩展

package com.hbh.AESTest.operation;

import com.hbh.AESTest.constant.AESConstants;


/**
 * @program: java_basis
 * @description: 密钥扩展
 * @author: hbh
 * @create: 2022-02-05 14:22
 */

public class RoundKeyExtensionUtils {

    /**
     * 密钥扩展
     * @param originalKey  原始密钥
     * @return  返回密钥扩展
     */

    public static short[][] generateRoundKeys(short[][] originalKey){
        short[][] roundKeys = new short[44][4];

        int keyWordCount = originalKey.length;
        System.arraycopy(originalKey,0,roundKeys,0,keyWordCount); // 这里使用原始密钥
        for (int i = keyWordCount; i < keyWordCount * 11; i++) {
            short[] temp = roundKeys[i-1];
            if(i % keyWordCount == 0){
                // 如果i是4的倍数,这里会进行一个T函数
                /**
                 * T函数操作包含如下:
                 * 1. 字循环
                 * 2. 字节代换
                 * 3. 轮常量异或(给定)
                 */

                temp = xor(substituteWord(leftShift(temp)), AESConstants.R_CON[i / keyWordCount]);
            }
            // 如果i不是4的倍数,直接进行异或
            roundKeys[i] = xor(roundKeys[i - keyWordCount], temp);
        }
        return roundKeys;
    }

    /**
     * 字循环 : 以字节为单位循环左移1个单位
     * @param aWord
     * @return
     */

    public static short[] leftShift(short[] aWord) {
        short[] result = new short[4];
        for (int i = 0; i < 4; i++) {
            result[i] = aWord[AESConstants.LEFT_SHIFT_TABLE[i]];
        }
        return result;
    }

    /**
     * 字节代换   对字中每个字节进行字节替代
     * @param aWord
     * @return
     */

    public static short[] substituteWord(short[] aWord) {
        for (int i = 0; i < 4; i++) {
            aWord[i] = substituteByte(aWord[i],AESConstants.SUBSTITUTE_BOX);
        }
        return aWord;
    }

    /**
     * 字节替代:取一个字的高四位和低四位分别作为S盒的行号和列号,通过行列号取S盒中的字节替代原字节
     * @param originalByte
     * @param substituteTable
     * @return
     */

    public static short substituteByte(short originalByte, short[][] substituteTable) {
        int low4Bits = originalByte & 0x000f;
        int high4Bits = (originalByte >> 4) & 0x000f;
        return substituteTable[high4Bits][low4Bits];
    }

    /**
     * 两个字间的异或操作
     * @param first
     * @param second
     * @return
     */

    public static short[] xor(short[] first, short[] second) {
        short[] result = new short[4];
        for (short i = 0; i < 4; i++) {
            result[i] = (short) (first[i] ^ second[i]);
        }
        return result;
    }

    /**
     * 状态替代:对状态中的每个字进行字替代
     */

    public static short[][] substituteState(short[][] state, short[][] substituteTable) {
        for (int i = 0; i < state.length; i++) {
            for (int j = 0; j < 4 ; j++) {
                state[i][j] = substituteByte(state[i][j], substituteTable);
            }
        }
        return state;
    }
}

列混合

package com.hbh.AESTest.operation;

/**
 * @program: java_basis
 * @description: 列混合变换
 * @author: hbh
 * @create: 2022-02-05 16:05
 */

public class MixColumn {

    /**
     * 列混合变换
     *
     * @param state 状态数组
     * @param table 多项式等价矩阵
     * @return 列混合变换后的新状态
     */

    public static short[][] mixColumns(short[][] state, short[][] table) {
        short[][] result = new short[state.length][4];
        for (int i = 0; i < state.length; i++) {
            result[i] = matrixMultiply(state[i], table);
        }
        return result;
    }


    private static short[] matrixMultiply(short[] aWord, short[][] table) {
        short[] result = new short[4];
        for (int i = 0; i < 4; i++) {
            result[i] = wordMultiply(table[i], aWord);
        }
        return result;
    }


    private static short wordMultiply(short[] firstWord, short[] secondWord) {
        short result = 0;
        for (int i = 0; i < 4; i++) {
            result ^= multiply(firstWord[i], secondWord[i]);
        }
        return result;
    }


    private static short multiply(short a, short b) {
        short temp = 0;
        while (b != 0) {
            if ((b & 0x01) == 1) {
                temp ^= a;
            }
            a <<= 1;
            if ((a & 0x100) > 0) {
                a ^= 0x1b;
            }
            b >>= 1;
        }
        return (short) (temp & 0xff);

    }
}

package com.hbh.AESTest.serivce;

public interface CipherService {

    /**
     * 加密
     * @param plainText 需要加密的明文
     * @param key 密钥
     * @return
     */

    String encrypt(String plainText, String key);


    /**
     * 解密
     * @param encryptedText  需要解密的密文
     * @param key
     * @return
     */

    String decrypt(String encryptedText, String key);
}

对外接口

package com.hbh.AESTest.serivce;

import com.hbh.AESTest.constant.AESConstants;
import com.hbh.AESTest.core.AESCore;
import com.hbh.AESTest.operation.InitialUtils;
import com.hbh.AESTest.operation.MixColumn;
import com.hbh.AESTest.operation.ShiftRow;
import com.hbh.AESTest.util.ArrayUtil;
import com.hbh.AESTest.operation.RoundKeyExtensionUtils;
import com.hbh.AESTest.util.Base64Util;
import org.junit.Test;

import java.util.Arrays;

/**
 * @program: java_basis
 * @description:
 * @author: hbh
 * @create: 2022-02-05 16:34
 */

public class AESCipherServiceImpl implements CipherService{

    public static void main(String[] args) {
        String plaintext = "passwordTextCase", key = "simpleKeyCase123";
        CipherService aesService = new AESCipherServiceImpl();

        String encryptedText = aesService.encrypt(plaintext, key);

        String decrypt = aesService.decrypt(encryptedText, key);

        ArrayUtil.printInfo("原文:", plaintext, false);
        ArrayUtil.printInfo("密钥:", key, false);
        ArrayUtil.printInfo("密文:", encryptedText, false);
        ArrayUtil.printInfo("解密后:", decrypt, false);

    }
    
    @Override
    public String encrypt(String plainText, String key) {

        System.out.println("#####################  encryption  #####################");
        ArrayUtil.printInfo("明文:", plainText, false);
        ArrayUtil.printInfo("密钥:", key, false);
        short[][] initialPTState = transfer(ArrayUtil.transferToShorts(plainText));
        ArrayUtil.printInfo("16进制明文:", getStateHex(initialPTState), false);
        short[][] initialKeyState = transfer(ArrayUtil.transferToShorts(key));
        ArrayUtil.printInfo("16进制密钥:", getStateHex(initialKeyState), true);


        short[][] rawRoundKeys = RoundKeyExtensionUtils.generateRoundKeys(initialKeyState);// 生成密钥扩展

        System.out.println("密钥扩展:");
        printRoundKeys(rawRoundKeys);

        short[][][] roundKeys = transfer(rawRoundKeys); // 将密钥扩展整成 三维数组,每一轮更能快速的获取到这一轮要使用的轮密钥加
        /**
         * initialPTState:
         * roundKeys:密钥扩展,轮密钥加需要使用
         * AESConstants.SUBSTITUTE_BOX:S盒
         * AESConstants.CX:列混合给定的固定矩阵
         * AESConstants.SHIFTING_TABLE:密钥扩展中字循环左移操作需要用到的矩阵
         */

        short[][] finalState = AESCore.coreEncrypt(initialPTState, roundKeys, AESConstants.SUBSTITUTE_BOX,
                AESConstants.CX, AESConstants.SHIFTING_TABLE);
        return Base64Util.encode(transfer2Bytes(finalState));
    }



    @Override
    public String decrypt(String encryptedText, String key) {
        System.out.println("#####################  decryption  #####################");
        short[][] initialTextState = transfer(Base64Util.decodeToShorts(encryptedText));
        short[][] initialKeyState = transfer(ArrayUtil.transferToShorts(key));

        short[][] decryptState = AESCore.coreDecrypt(initialTextState, initialKeyState);
        return getOrigin(decryptState);
    }


    /**
     * 打印轮密钥数组
     * @param 
     */

    private void printRoundKeys(short[][] roundKeys) {
        for (int i = 0, keyOrder = 1; i < roundKeys.length; i += 4, keyOrder++) {
            String infoKValue = getStateHex(new short[][]{
                    roundKeys[i], roundKeys[i + 1],
                    roundKeys[i + 2], roundKeys[i + 3]
            });
            ArrayUtil.printInfo("[RoundKey " + keyOrder + "]", infoKValue, false);
        }
        System.out.println();
    }

    /**
     * 状态数组的十六进制串
     * @param
     * @return
     */

    private String getStateHex(short[][] state) {
        StringBuilder builder = new StringBuilder();
        for (short[] aWord : state) {
            builder.append(toHexString(aWord));
        }
        return builder.toString();
    }


    /**
     * 将一个字以字节为单位转换成十六进制形式字符串
     * @param
     * @return
     */

    private String toHexString(short[] aWord) {
        StringBuilder builder = new StringBuilder();
        for (short aByte : aWord) {
            builder.append(toHexString(aByte));
        }
        return builder.toString();
    }


    /**
     * 获取一个字节的十六进制串
     * @param value
     * @return
     */

    private String toHexString(short value) {
        String hexString = Integer.toHexString(value);
        if (hexString.toCharArray().length == 1) {
            hexString = "0" + hexString;
        }
        return hexString;
    }

    /**
     * 将二维数组转成三维数组,方便在轮变换中通过轮下标获取 Nk x 4 的轮密钥矩阵
     * @param origin 二维轮密钥数组
     * @return 三维轮密钥数组
     */

    private short[][][] transfer(short[][] origin) {
        short[][][] result = new short[origin.length / 4][4][4];
        for (int i = 0; i < origin.length / 4; i++) {
            short[][] temp = new short[4][4];
            System.arraycopy(origin, i * 4, temp, 04);
            result[i] = temp;
        }
        return result;
    }

    /**
     * 将矩阵转成Nb x 4状态矩阵,用在获取明文和密钥的初始状态数组
     * @param origin
     * @return
     */

    private short[][] transfer(short[] origin) {
        short[][] result = new short[origin.length / 4][4];
        for (int i = 0; i < result.length; i++) {
            System.arraycopy(origin, i * 4, result[i], 04);
        }
//        int limit = 0;
//        // 0,0  1,0,  2,0  3,0
//        for (int i = 0; i < result.length; i++) {
//            for(int j = 0; j < result.length; j++){
//                result[j][i] = origin[limit];
//                limit ++;
//            }
//        }
        return result;
    }
    /**
     * 将加密后得到的状态数组转成字节数组,便于进行Base64编码
     * @param finalState 加密后得到的状态数组
     * @return 状态数组对应的字节数组
     */

    private byte[] transfer2Bytes(short[][] finalState) {
        byte[] result = new byte[finalState.length * 4];
        for (int i = 0;i < finalState.length; i++) {
            for (int j = 0; j < 4; j++) {
                result[i * 4 + j] = (byte) (finalState[i][j] & 0xff);
            }
        }
        return result;
    }


    /**
     * 将最终解密的short数组还原为字符串
     * @param decryptState
     * @return 
     */

    private static String getOrigin(short[][] decryptState) {
        StringBuilder builder = new StringBuilder();
        for (short[] shorts : decryptState) {
            for (short s : shorts) {
                builder.append(String.valueOf((char) s));
            }
        }
        return builder.toString();
    }
}

输出转化数组工具类

package com.hbh.AESTest.util;

import java.io.*;
import java.math.BigInteger;

/**
 * @program: java_basis
 * @description:
 * @author: hbh
 * @create: 2022-02-05 16:39
 */

public class ArrayUtil {
    /**
     * 将字符串转为一维short数组
     * transfer a string to short array
     * @param string target string to be process
     * @return a short array
     */

    public static short[] transferToShorts(String string) {
        byte[] bytes = string.getBytes();
        int length = bytes.length;
        short[] shorts = new short[length];
        for (int i = 0; i < length; i++) {
            shorts[i] = bytes[i];
        }
        return shorts;
    }

    /**
     * print information for tracing the whole process
     *
     * @param key info type
     * @param value info
     * @param nextRow flag deciding whether going to next row
     */

    public static String printInfo(String key, String value, boolean nextRow) {
        String message = String.format("%-30s%-70s", key, value);
        if (nextRow) {
            message += "\n";
        }
        System.out.println(message);
        return message;
    }

    /**
     * concatenate two byte arrays
     * @param before the first array in concat
     * @param after the second array in concat
     * @return concat
     */

    public static byte[] concat(byte[] before, byte[] after) {
        byte[] result = new byte[before.length + after.length];
        System.arraycopy(before, 0, result, 0, before.length);
        System.arraycopy(after, 0, result, before.length, after.length);
        return result;
    }

    /**
     * transfer int value into byte array
     * @param intValue int value
     * @return byte array
     */

    public static byte[] int2Bytes(int intValue) {
        byte[] byteArray = null;
        try {
            ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
            DataOutputStream dataOut = new DataOutputStream(byteOut);
            dataOut.writeByte(intValue);
            byteArray = byteOut.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return byteArray;
    }

    /**
     * segment one-dimension array into an 8x8 array
     * transfer every byte unit into int
     * transfer int into ascii type  and print it
     * @param chars char array to be processed
     */

    public static String segmentAndPrintChars(char[] chars) {
        char[][] chars1 = ArrayUtil.segmentDimension(chars, chars.length / 8 ,8);
        StringBuilder builder = new StringBuilder();
        for (char[] aChars1 : chars1) {
            builder.append((char) Integer.parseInt(String.valueOf(aChars1), 2));
        }
        return builder.toString();
    }

    /**
     * transfer byte array into char array
     * @param bytes byte array to be transferred
     * @return bits in chars format
     */

    public static char[] bytesToChars(byte[] bytes) {
        StringBuilder builder = new StringBuilder();
        for (byte aByte : bytes) {
            String aByteStr = Integer.toBinaryString(
                    (aByte & 0xff) + 0x0100).substring(1);
            builder.append(aByteStr);
        }
        return builder.toString().toCharArray();
    }

    public static short[] byteToShorts(byte[] bytes) {
        short[] result = new short[bytes.length];
        for (int i = 0; i < bytes.length; i++) {
            result[i] = (short) (bytes[i] & 0xff);
        }
        return result;
    }

    /**
     * disrupt the order of an array by reindexing it
     * @param chars original array to be disrupted
     * @param newIndexes index array the disruption is according to
     * @return disrupted sub array of chars
     */

    public static char[] disruptArray(char[] chars, short[] newIndexes) {
        char[] resultChars = new char[newIndexes.length];
        for (int i = 0; i < newIndexes.length; i++) {
            resultChars[i] = chars[newIndexes[i] - 1];
        }
        return resultChars;
    }

    /**
     * byte array printing
     * @param bytes byte array
     */

    public static void printByteArray(byte[] bytes){
        int length = bytes.length;
        StringBuilder builder = new StringBuilder();
        for (int i = 1; i <= length; i++) {
            String symbol = (i % 8 == 0) ? " " : "";
            builder.append((int) bytes[i - 1]).append(symbol);
        }
        System.out.println(builder.toString());
    }

    /**
     * print bits in chars format and what it stands for
     * @param prefix info
     * @param chars bits in chars format to be printed
     */

    public static String printBitChars(String prefix, char[] chars) {
        int length = chars.length;
        StringBuilder builder = new StringBuilder();
        for (int i = 1; i <= length; i++) {
            String symbol = (i % 8 == 0 || i == length)
                    ? " " : "";
            builder.append(chars[i - 1]).append(symbol);
        }
        String message = String.format("%-30s%-30s", prefix, builder.toString());
        System.out.println(message);
        return message;
    }

    /**
     * char array printing
     * @param chars char array to be printed
     */

    public static void printArray(char[] chars) {
        int length = chars.length;
        for (int i = 1; i <= length; i++) {
            String symbol = (i % 8 == 0 || i == length)
                    ? "\n" : " ";
            System.out.print(chars[i - 1] + symbol);
        }
    }

    /**
     * left shifting based on String operation
     * @param src bits to be shifted in chars format
     * @param length shifting length
     * @return shifted bit sequences in chars format
     */

    public static char[] leftShift(char[] src, int length) {
        String byteString = String.valueOf(src);
        return (byteString.substring(length) +
                byteString.substring(0, length)).toCharArray();
    }

    /**
     * xor operation
     * @param aChars operating bits in chars format
     * @param bChars operating bits in chars format
     * @return result of operation, formatting in 48 or 64 bits
     */

    public static char[] xor(char[] aChars, char[] bChars, int bits) {
        BigInteger a = new BigInteger(String.valueOf(aChars), 2);
        BigInteger b = new BigInteger(String.valueOf(bChars), 2);

        BigInteger xorResult = a.xor(b);

        String xorStr = xorResult.toString(2);
        StringBuilder builder = new StringBuilder();

        int sub = bits - xorStr.length();
        if (sub > 0) {
            for (int i = 0; i < sub; i++) {
                builder.append("0");
            }
            xorStr = builder.toString() + xorStr;
        }
        return xorStr.toCharArray();
    }

    /**
     * segment one-dimension array into a two-dimension array
     * which holds data in rows x columns format
     * @param chars one-dimension array
     * @param rows rows of result
     * @param columns columns of result
     * @return a two-dimension array
     *          which holds data in rows x columns format
     */

    public static char[][] segmentDimension(char[] chars, int rows, int columns) {
        char[][] result = new char[rows][columns];
        for (int i = 0; i < result.length; i++) {
            System.arraycopy(chars, i * columns, result[i], 0, result[i].length);
        }
        return result;
    }

    /**
     * concatenate two arrays after left shifting in loop
     * @param before the first array in concat
     * @param after  the second array in concat
     * @return concat
     */

    public static char[] concat(char[] before, char[] after) {
        return (String.valueOf(before) + String.valueOf(after))
                .toCharArray();
    }

    /**
     * 获取文件字节数组
     * @param file 文件
     * @return 字节数组
     */

    public static byte[] getBytes(File file){
        byte[] buffer = null;
        FileInputStream fis = null;
        ByteArrayOutputStream bos = null;
        try {
            fis = new FileInputStream(file);
            bos = new ByteArrayOutputStream(1000);
            byte[] b = new byte[1000];
            int n;
            while ((n = fis.read(b)) != -1) {
                bos.write(b, 0, n);
            }
            buffer = bos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (fis != null) {
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if (bos != null) {
                    try {
                        bos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return buffer;
    }
}

Base64工具类

package com.hbh.AESTest.util;

import java.util.Base64;

/**
 * @program: java_basis
 * @description: 通过Base64编码规则将不可见字符转化为可见字符
 * @author: hbh
 * @create: 2022-02-05 16:55
 */


public class Base64Util {

    /**
     * 对字节数组进行编码 (AES)
     * encode byte array
     * @param bytes byte array
     * @return encoded string
     */

    public static String encode(byte[] bytes) {
        return Base64.getEncoder().encodeToString(bytes);
    }

    /**
     * 将已经经过Base64编码的字符串解码成short数组 (AES)
     * decode a encoded string to short array
     * @param encodedText encoded text
     * @return short array
     */

    public static short[] decodeToShorts(String encodedText) {
        return ArrayUtil.byteToShorts(Base64.getDecoder().decode(encodedText));
    }

    /**
     * encode encrypted text bits by Base64 for processing unprintable (DES)
     * and invisible character
     * @param chars encrypted text bits
     * @return encoded text
     */

    public static String encode(char[] chars) {
        char[][] segmentedChars = ArrayUtil.segmentDimension(chars, chars.length / 88);
        byte[] bytes = new byte[0];
        for (char[] segmentedChar : segmentedChars) {
            bytes = ArrayUtil.concat(bytes, ArrayUtil.int2Bytes(
                    Integer.parseInt(String.valueOf(segmentedChar), 2)));
        }
        return Base64.getEncoder().encodeToString(bytes);
    }

    /**
     * decode decoded encrypted text bits in chars format (DES)
     * @param encodedText encoded text
     * @return encrypted text bits in chars format
     */

    public static char[] decodeToChars(String encodedText) {
        return ArrayUtil.bytesToChars(Base64.getDecoder().decode(encodedText));
    }

    /**
     * 将未处理的字符串用Base64编码 (RC4)
     * @param rawString 未处理的字符串
     * @return 编码后的字符串
     */

    public static String encode(String rawString) {
        char[] chars = rawString.toCharArray();
        byte[] bytes = new byte[0];
        for (char c : chars) {
            bytes = ArrayUtil.concat(bytes, ArrayUtil.int2Bytes(c));
        }
        return Base64.getEncoder().encodeToString(bytes);
    }

    /**
     * 将已用Base64编码的字符串解码 (RC4)
     * @param encodedText 已用Base64编码的字符串
     * @return 解码结果
     */

    public static String decode(String encodedText) {
        StringBuilder builder = new StringBuilder();
        byte[] bytes = Base64.getDecoder().decode(encodedText);
        for (byte b : bytes) {
            builder.append((char) (b & 0xff));
        }
        return builder.toString();
    }
}

参考文章

  • https://blog.csdn.net/chengqiuming/article/details/82390910
  • https://www.cnblogs.com/yanzi-meng/p/9640578.html
  • https://blog.csdn.net/chengqiuming/article/details/82355772
  • https://blog.csdn.net/qq_36949163/article/details/120932187
  • https://blog.csdn.net/qq_28205153/article/details/55798628
  • https://blog.csdn.net/chengqiuming/article/details/82429227

分类:

后端

标签:

后端

作者介绍

山雨欲来
V1