Java实现AES加密算法

栏目: Java · 发布时间: 6年前

内容简介:最近恶补了一些关于加密算法的知识,然后用编程语言的来实现没有经过加密的数据用来加密明文的密码,

最近恶补了一些关于加密算法的知识,然后用编程语言的来实现

AES简介

高级加密标准(AES,Advanced Encryption Standard)为最常见的 对称加密算法 (微信小程序加密传输就是用这个加密算法的)。 对称加密算法也就是加密和解密用相同的密钥 ,具体的加密流程如下图:

Java实现AES加密算法

下面简单介绍下各个部分的作用与意义:

  • 明文P

没有经过加密的数据

  • 密钥K

用来加密明文的密码, 在对称加密算法中,加密与解密的密钥是相同的 ,密钥为接收方与发送方协商产生,但不可以直接在网络上传输,否则会导致密钥泄露,通常是通过非对称加密算法加密密钥,然后再通过网络传输给对方,或者直接面对面商量密钥。密钥绝对不能泄露,否则会被攻击者还原密钥,窃取数据

  • AES加密函数

设AES加密函数为E,则 C = E(K,P) .其中P为明文,K为密钥,C为密文。也就是说,把明文P和密钥K作为加密函数的参数输入,则加密函数E会输出密文C

  • 密文C

经过加密函数处理后的数据

  • AES解密函数

设AES解密函数为D,则 P = D() .其中C为密文,K为密钥,P为明文。也就是说,把密文C和密钥K作为解密函数的参数输入,则解密函数会输出明文P

这里简单解释下 对称加密算法非对称加密算法

  • 对称加密算法

加密和解密使用的密钥是相同的,这种加密方式加密 速度非常快 ,适合经常发送数据的场合。 缺点是密钥的传输比较麻烦

  • 非对称加密算法

加密和解密使用的密钥是不同的,这种加密方式是用数学定理或者公式构造的,通常加密解密的 速度比较慢 ,适合偶尔发送数据的场合。 优点是密钥传输方便 。常见的非对称加密算法为RSA、ECC和EIGamal

实际中,一般是通过RSA加密AES的密钥,传输到接收方,接收方解密得到AES密钥

AES的基本结构

AES为分组密码,分组密码也就是把明文分成一组一组的,每组长度相等,每次加密一组数据,直到加密完所有组。在AES标准规范中,分组长度只能是128位,也就是说,每个分组为16个字节。密钥的长度可以使用128位、192位或256位。密钥的长度不同,推荐加密轮数也不同。如下表所示:

AES 密钥长度(32bit) 分组长度(32bit) 加密轮数
AES-128 4 4 10
AES-192 6 4 12
AES-256 8 4 14

上面说到,AES的加密公式为 C = E(K,P) ,在加密函数E种,会执行一个轮函数,并且执行n(n为加密轮数)次这个轮函数,这个轮函数的前n-1次执行的操作是一样的,只有第n次有所不同

AES的处理单位是字节,128位的输入明文分组P和输入密钥K都被分成16字节,分别记为$P = P_0,P_1,...,P_{15}$和$K = K_0,K_1,...,K_{15}$。如明文分组为P=abcdefghijklmnop,其中字符a对应$P_0$,p对应$P_{15}$。一般地,明文分组用字节为单位地正方形矩阵描述,称为状态矩阵。在算法地每一轮中,状态矩阵地内容不断发生变化,最后的结果作为密文输出。该矩阵中字节地排列顺序为从上到下、从左至右依次排列,如下图所示:

Java实现AES加密算法

现在假设明文分组P=abcdefghijklmnop,则对应上面生成地状态矩阵图如下:

Java实现AES加密算法

上图中,0x61为a字符的十六进制表示,可以看到,明文经过AES加密后,已经面目全非了

类似地,128位密钥也是用以字节为单位的矩阵表示,矩阵的每一列被称为1个32为比特字。通过密钥编排函数可以将该密钥矩阵扩展成一个44字组成的序列W[0],W[1],...,W[43]。该序列的前4个元素W[0],W[1],W[2],W[3]是原始密钥,用于加密运算中的初始密钥。后面40个字分为10组,每组4个字(128bit)分别用于10轮加密运算中的轮密钥加,如下图所示:

Java实现AES加密算法

上图中,设K=abcdefghijklmnop,则$K_0=a,K_{15}=p$,w[0]$=K_0k_1K_2K_3=abcd$

AES整体的结构如下图所示,其中W[0,3]是指W[0]、W[1]、W[2]和W[3]串联组成的128位密钥。加密的第一轮到第9轮的轮函数一样,包括4个操作,字节代换、行位移、列混合和轮密钥加。最后一轮迭代不执行列混合。另外,在第一轮迭代之前,先将明文和原始密钥进行一次异或加密操作

Java实现AES加密算法

上图也展示了AES的解密过程,解密过程仍为10轮,每一轮的操作是加密操作的逆操作。由于AES的4个轮操作都是可逆的,因此,解密操作的每一轮就是顺序执行逆行移位、逆字节代换、轮密钥加和逆列混合。同加密操作类似,最后一轮不执行逆列混合,在第1轮解密之前,要执行1次密钥加操作

下面分别介绍AES中一轮的4个操作阶段,这4分操作阶段使输入位得到充分的混淆

字节代换

1.字节代换操作

AES的字节代换其实就是一个简单的查表操作。AES定义了一个S盒和一个逆S盒

AES的S盒:

Java实现AES加密算法

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

Java实现AES加密算法

2.字节代换逆操作

逆字节代换也就是查逆S盒来变换,逆S盒如下:

Java实现AES加密算法

行位移

1.行位移变换

行移位是一个简单的左循环移位操作。当密钥长度为128比特时,状态矩阵的第0行左移0字节,第1行左移1字节,第2行左移2字节,第3行左移3字节,如下图所示:

Java实现AES加密算法

2.行位移逆变换

行移位的逆变换是将状态矩阵中的每一行执行相反的移位操作,例如AES-128中,状态矩阵的第0行右移0字节,第1行右移1字节,第2行右移2字节,第3行右移3字节

列混合

1.列混合预算

列混合变换是通过矩阵相乘来实现的,经行移位后的状态矩阵与固定的矩阵相乘,得到混淆后的状态矩阵,如下图的公式所示:

Java实现AES加密算法

状态矩阵中的第j列(0 ≤j≤3)的列混合可以表示为:

$$

S'_{0,j} = (2 S_{0,j}) \oplus (3 S_{1,j}) \oplus S_{2,j} \oplus S_{3,j}

S'_{1,j} = S_{0,j} \oplus (2 S_{1,j}) \oplus (3 S_{2,j}) \oplus S_{3,j}

S'_{2,j} = S_{0,j} \oplus S_{1,j} \oplus (2 S_{2,j}) \oplus (3 S_{3,j})

S'_{3,j} = (3 S_{0,j}) \oplus S_{1,j} \oplus S_{2,j} \oplus (2 S_{3,j})

$$

2.列混合逆运算

逆向列混合变换可由下图的矩阵乘法定义:

Java实现AES加密算法

可以验证,逆变换矩阵同正变换矩阵的乘积恰好为单位矩阵

轮密钥加

轮密钥加是将128位轮密钥$K_i$同状态矩阵中的数据进行逐位异或操作,如下图所示。其中,密钥$K_i$中每个字W[4i],W[4i+1],W[4i+2],W[4i+3]为32位比特字,包含4个字节,他们的生成算法下面在下面介绍。轮密钥加过程可以看成是字逐位异或的结果,也可以看成字节级别或者位级别的操作。也就是说,可以看成S0 S1 S2 S3 组成的32位字与W[4i]的异或运算

Java实现AES加密算法

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

密钥扩展

Java实现AES加密算法 这个4*4矩阵的每一列的4个字节组成一个字,矩阵4列的4个字依次命名为W[0]、W[1]、W[2]和W[3],它们构成一个以字为单位的数组W。例如,设密钥K=abcdefghijklmnop,则$K_0 = a,K_1 = b,K_2 = c,K_3 = d$,W[0] = abcd

接着,对W数组扩充40个新列,构成总共44列的扩展密钥数组。新列以如下的递归方式产生:

W[i]=W[i-4]⨁W[i-1]
W[i]=W[i-4]⨁T(W[i-1])

其中,函数T由3部分组成:字循环、字节代换和轮常量异或,这3部分的作用分别如下:

  • 字循环:将1个字中的4个字节循环左移1个字节。即将输入字[b0, b1, b2, b3]变换成[b1,b2,b3,b0]
  • 字节代换:对字循环的结果使用S盒进行字节代换
  • 轮常量异或:将前两步的结果同轮常量Rcon[j]进行异或,其中j表示轮数

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

j 1 2 3 4 5
Rcon[j] 01 00 00 00 02 00 00 00 04 00 00 00 08 00 00 00 10 00 00 00
j 6 7 8 9 10
Rcon[j] 20 00 00 00 40 00 00 00 80 00 00 00 1B 00 00 00 36 00 00 00

举个例子,设初始的128为密钥为: 3C A1 0B 21 57 F0 19 16 90 2E 13 80 AC C1 07 BD ,那么4个初始值为

W[0] = 3C A1 0B 21 
W[1] = 57 F0 19 16 
W[2] = 90 2E 13 80 
W[3] = AC C1 07 BD

下面求扩展的第1轮的子密钥(W[4],W[5],W[6],W[7])

由于4是4的倍数,所以: W[4] = W[0] ⨁ T(W[3]) ,T(W[3])的计算步骤如下:

  1. 循环地将W[3]的元素移位: AC C1 07 BD 变成 C1 07 BD AC
  2. C1 07 BD AC 作为S盒的输入,输出为 78 C5 7A 91
  3. 78 C5 7A 91 与第一轮轮常量Rcon[1]进行异或运算,将得到 79 C5 7A 91 ,因此, T(W[3]) = 79 C5 7A 91 ,故 W[4] = 3C A1 0B 21 ⨁ 79 C5 7A 91 = 45 64 71 B0

其余3个子密钥段的计算如下:

W[5] = W[1] ⨁ W[4] = 57 F0 19 16 ⨁ 45 64 71 B0 = 12 94 68 A6 
W[6] = W[2] ⨁ W[5] =90 2E 13 80 ⨁ 12 94 68 A6 = 82 BA 7B 26 
W[7] = W[3] ⨁ W[6] = AC C1 07 BD ⨁ 82 BA 7B 26 = 2E 7B 7C 9B

所以,第一轮的密钥为 45 64 71 B0 12 94 68 A6 82 BA 7B 26 2E 7B 7C 9B

Java实现AES算法

由于 Java 有自带的函数,因此可以直接调用

首先生成密钥,密钥是 SecretKey 类型的对象

static final String ALGORITHM = "AES";

public static SecretKey generateKey() throws NoSuchAlgorithmException { // 生成密钥
    KeyGenerator secretGenerator = KeyGenerator.getInstance(ALGORITHM);
    SecureRandom secureRandom = new SecureRandom();
    secretGenerator.init(secureRandom);
    SecretKey secretKey = secretGenerator.generateKey();
    return secretKey;
}

首先创建密钥生成器 KeyGenerator 对象,产生的是AES算法的密钥,因此传入参数AES。这里使用 安全的随机数 SecureRandom 作为参数传入密钥生成器的 init() 函数中,最后调用密钥生成器的 generateKey() 方法产生密钥对象 secretKey

然后实现加密方法和解密方法

static Charset charset = Charset.forName("UTF-8");
public static byte[] encrypt(String content, SecretKey secretKey) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException { // 加密
    return aes(content.getBytes(charset),Cipher.ENCRYPT_MODE,secretKey);
}

public static String decrypt(byte[] contentArray, SecretKey secretKey) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException { // 解密
    byte[] result =  aes(contentArray,Cipher.DECRYPT_MODE,secretKey);
    return new String(result,charset);
}

加密方法传入的是需要加密的明文 content 以及密钥;解密方法传入的是密文 contentArray 以及密钥

最后实现aes函数

private static byte[] aes(byte[] contentArray, int mode, SecretKey secretKey)
        throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
    Cipher cipher = Cipher.getInstance(ALGORITHM);
    cipher.init(mode, secretKey);
    byte[] result = cipher.doFinal(contentArray);
    return result;
}

完整代码如下:

import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;

public class Aes {

    
    static final String ALGORITHM = "AES";

    public static SecretKey generateKey() throws NoSuchAlgorithmException { // 生成密钥
        KeyGenerator secretGenerator = KeyGenerator.getInstance(ALGORITHM);
        SecureRandom secureRandom = new SecureRandom();
        secretGenerator.init(secureRandom);
        SecretKey secretKey = secretGenerator.generateKey();
        return secretKey;
    }

    static Charset charset = Charset.forName("UTF-8");
    public static byte[] encrypt(String content, SecretKey secretKey) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException { // 加密
        return aes(content.getBytes(charset),Cipher.ENCRYPT_MODE,secretKey);
    }

    public static String decrypt(byte[] contentArray, SecretKey secretKey) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException { // 解密
        byte[] result =  aes(contentArray,Cipher.DECRYPT_MODE,secretKey);
        return new String(result,charset);
    }

    private static byte[] aes(byte[] contentArray, int mode, SecretKey secretKey)
            throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(mode, secretKey);
        byte[] result = cipher.doFinal(contentArray);
        return result;
    }

    public static void main(String[] args) {
        String content = "你好,我很喜欢加密算法";
        SecretKey secretKey;
        try {
            long timeStart = System.currentTimeMillis();
            secretKey = generateKey();
            byte[] encryptResult = encrypt(content, secretKey);
            long timeEnd = System.currentTimeMillis();
            System.out.println("加密后的结果为:" + new String(encryptResult,charset));
            String decryptResult = decrypt(encryptResult,secretKey);
            System.out.println("解密后的结果为:" + decryptResult);
            System.out.println("加密用时:" + (timeEnd - timeStart));
        } catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) {
            e.printStackTrace();
        }
    }
}

运行GIF图如下所示:

Java实现AES加密算法

以上所述就是小编给大家介绍的《Java实现AES加密算法》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Coding the Matrix

Coding the Matrix

Philip N. Klein / Newtonian Press / 2013-7-26 / $35.00

An engaging introduction to vectors and matrices and the algorithms that operate on them, intended for the student who knows how to program. Mathematical concepts and computational problems are motiva......一起来看看 《Coding the Matrix》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具