高级加密标准AES(Advanced Encryption Standard)是美国联邦政府采用的一种区块加密标准。DES算法由于其密钥较短,难以抵抗现有的攻击,因此不再作为加密标准。AES用来替代原先的DES,现在AES已成为对称密钥加密中流行的算法
算法概述
AES算法说明:
-
AES中明文分组可变:128、192、256比特
-
密钥长度可变:各自可独立指定128、192、256比特,平时大家所说的AES128,AES192,AES256,实际上就是指的AES算法对不同长度密钥的使用
-
AES加密过程是在一个4×4的字节矩阵上运作,这个矩阵又称为“状态(state)”,其初值就是一个明文区块,矩阵中一个元素大小就是明文区块中的一个Byte,其矩阵的列数可视情况增加
-
循环加密的次数由的长度和的长度决定
-
循环加密过程中除最后一轮外均包含4个步骤
- 加轮密钥
AddRoundKey
- 字节代替
SubBytes
- 行移位
ShiftRows
- 列混淆
MixColumns
- 加轮密钥
![image-20210225213410269](https://kay-rick.oss-cn-beijing.aliyuncs.com/img/image-20210225213410269.png)
接下来我们分析AES加密过程中每一轮的处理步骤
AES加密
字节替换
16字节的明文块在每一个处理步骤中都被排列成4X4的二维数组
在字节替换的步骤中,矩阵中的各字节透过一个8位的S盒进行转换。这个步骤提供了加密法非线性的变换能力。
![image-20210225172357175](https://kay-rick.oss-cn-beijing.aliyuncs.com/img/image-20210225172357175.png)
S盒的使用
- 这里的S盒是一个16行16列的矩阵,输入8a,则去找第8行第10列交界点处的值:输出7e,即7e=S(8a)
- 这里针对每一个字节对照S盒进行字节替换
![image-20210225181454137](https://kay-rick.oss-cn-beijing.aliyuncs.com/img/image-20210225181454137.png)
行移位
行移位描述矩阵的列操作。在此步骤中,每一行都向左循环位移某个偏移量
偏移量的多少和状态矩阵的列数密切相关
![image-20210225182751031](https://kay-rick.oss-cn-beijing.aliyuncs.com/img/image-20210225182751031.png)
图中所示是一个4列的矩阵,移位的结果是:第一行不变,第二行循环左移1个字节,第三行循环左移2个字节,第四行循环左移3个字节
![image-20210225181905001](https://kay-rick.oss-cn-beijing.aliyuncs.com/img/image-20210225181905001.png)
列混淆
- 将每列视为有限域上多项式,每一行的四个元素分别当作的系数,与固定的多项式进行模后相乘,记为
![image-20210225182212003](https://kay-rick.oss-cn-beijing.aliyuncs.com/img/image-20210225182212003.png)
- 列混淆运算也可写为矩阵乘法:
![image-20210225184704499](https://kay-rick.oss-cn-beijing.aliyuncs.com/img/image-20210225184704499.png)
- 最后一个加密循环中省略列混淆步骤,目的是在解密过程中统一每一轮解密都进行逆列混淆
加轮密钥
密钥编排
密钥编排指从种子密钥(以字节为元素的矩阵阵列描述密钥,阵列为4行,列数为密钥长度除32)得到轮密钥的过程
![image-20210225193449885](https://kay-rick.oss-cn-beijing.aliyuncs.com/img/image-20210225193449885.png)
-
密钥字符单位是以4字节(32bit)为元素的一维阵列,如果明文要经过轮加密,状态矩阵一共有列,则一共有个密钥字符数量。例如AES-128就需要 个密钥字符,每一轮加密使用4个字符
获得
![image-20210225200250807](https://kay-rick.oss-cn-beijing.aliyuncs.com/img/image-20210225200250807.png)
前个字取为种子密钥,以后每个字的计算按照递归方式定义
-
如果
- 如果不是的倍数,那么第列:
- 如果是的倍数,那么第列由如下等式确定:,这里的T函数较为复杂
- 自循环移位RotByte:将1个字中的4个字节循环左移1个字节
- S盒变换SubByte:对字循环的结果使用S盒进行字节代换
- 与轮常数Rcon异或
void KeyExpansion (byteKey[4 * Nk] , W[Nb * (Nr + 1)]) { for (i = 0; i < Nk; i++) W[i] = (Key[4 * i], Key[4 * i + 1], Key[4 * i + 2], Key[4 * i + 3] ); for (i = Nk; i < Nb * (Nr + 1); i++) { temp = W[i - 1]; if (i % Nk == 0) temp = SubByte(RotByte(temp)) ^ Rcon[i / Nk]; W[i] = W[i - Nk] ^ temp; } }
轮常数
为轮常数,其值与无关,定义为(字节用十六进制表示,同时理解为上的元素):
- ,其中是有限域中值为的元素
-
如果,区别在于:当为的整倍数时,须先将前一个字经过SubByte变换
void KeyExpansion (byteKey[4 * Nk] , W[Nb * (Nr + 1)]) { for (i = 0; i < Nk; i++) W[i] = (Key[4 * i], Key[4 * i + 1], Key[4 * i + 2], Key[4 * i + 3] ); for (i = Nk; i < Nb * (Nr + 1); i++) { temp = W[i - 1]; if (i % Nk == 0) temp = SubByte(RotByte(temp)) ^ Rcon[i / Nk]; else if (i % Nk == 4) temp = SubByte(temp); W[i] = W[i - Nk] ^ temp; } }
异或
通过密钥编排,我们得到轮密钥(扩展密钥),这把密钥大小会跟原矩阵一样,以与原矩阵中每个对应的字节作异或即生成了输出值
![image-20210225182050401](https://kay-rick.oss-cn-beijing.aliyuncs.com/img/image-20210225182050401.png)
AES解密
![image-20210225171918112](https://kay-rick.oss-cn-beijing.aliyuncs.com/img/image-20210225171918112.png)
AES的每一轮解密都是AES加密相对应的逆操作
-
逆行移位:它对状态的每一行进行循环右移
-
逆字节替换:应用逆S盒进行字节替换
-
逆列混淆:将状态矩阵中的每一列视为系数在上的次数小于4的多项式与同一个固定的多项式相乘,和互逆
-
加轮密钥
AES的工作模式和填充模式已经在DES中做有相应介绍,只需将DES分组的64位换为AES的128位即可,不再赘述
AES in Java
/*
* @Author: Kay_Rick@outlook.com
* @Date: 2021-02-24 22:25:15
* @LastEditors: Kay_Rick@outlook.com
* @LastEditTime: 2021-02-25 22:18:49
* @Description: AESEncryptionExample
*/
package Crypt;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
public class AESEncryptionExample {
private static Cipher encryptCipher;
private static Cipher decryptCipher;
// AES 16Byte(128bits) paramters
private static final byte[] iv = { 11, 22, 33, 44, 99, 88, 77, 66, 43, 65, 76, 43, 34, 64, 43, 92};
public static void main(String[] args) {
String clearTextFile = "D:\\Server\\Cryption\\source.txt";
String cipherTextFile = "D:\\Server\\Cryption\\cipher.txt";
String clearTextNewFile = "D:\\Server\\Cryption\\source-new.txt";
try {
// create SecretKey using KeyGenerator
// SecretKey key = KeyGenerator.getInstance("AES").generateKey();
KeyGenerator kgen = KeyGenerator.getInstance("AES");
// specify the AES256 (default AES128)
kgen.init(256, new SecureRandom());
SecretKey key = kgen.generateKey();
// create iv parameter using CBC encrypt mode,
// default ECB encrypt mode does not need iv parameters
AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv);
// get Cipher instance and initiate in encrypt mode
// this cipher instance using CBC encrypt mode and padding 16Byte when the datablock less than 16Byte
encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
encryptCipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
// get Cipher instance and initiate in decrypt mode
decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
decryptCipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
// method to encrypt clear text file to encrypted file
encrypt(new FileInputStream(clearTextFile), new FileOutputStream(cipherTextFile));
// method to decrypt encrypted file to clear text file
decrypt(new FileInputStream(cipherTextFile), new FileOutputStream(clearTextNewFile));
System.out.println("done");
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
| InvalidAlgorithmParameterException | IOException e) {
e.printStackTrace();
}
}
private static void encrypt(InputStream is, OutputStream os) throws IOException {
// create CipherOutputStream to encrypt the data using encryptCipher
os = new CipherOutputStream(os, encryptCipher);
writeData(is, os);
}
private static void decrypt(InputStream is, OutputStream os) throws IOException {
// create CipherOutputStream to decrypt the data using decryptCipher
is = new CipherInputStream(is, decryptCipher);
writeData(is, os);
}
// utility method to read data from input stream and write to output stream
private static void writeData(InputStream is, OutputStream os) throws IOException {
byte[] buf = new byte[1024];
int numRead = 0;
// read and write operation
while ((numRead = is.read(buf)) >= 0) {
os.write(buf, 0, numRead);
}
os.close();
is.close();
}
}