深入理解AES加密算法

高级加密标准AES(Advanced Encryption Standard)是美国联邦政府采用的一种区块加密标准。DES算法由于其密钥较短,难以抵抗现有的攻击,因此不再作为加密标准。AES用来替代原先的DES,现在AES已成为对称密钥加密中流行的算法

算法概述


AES算法说明:

  1. AES中明文分组可变:128、192、256比特

  2. 密钥长度可变:各自可独立指定128、192、256比特,平时大家所说的AES128,AES192,AES256,实际上就是指的AES算法对不同长度密钥的使用

  3. AES加密过程是在一个4×4的字节矩阵上运作,这个矩阵又称为“状态(state)”,其初值就是一个明文区块,矩阵中一个元素大小就是明文区块中的一个Byte,其矩阵的列数NbN_b可视情况增加

  4. 循环加密的次数由NkN_k的长度和NbN_b的长度决定

    image-20210225172759081
  5. 循环加密过程中除最后一轮外均包含4个步骤

    • 加轮密钥AddRoundKey
    • 字节代替SubBytes
    • 行移位ShiftRows
    • 列混淆MixColumns
image-20210225213410269

接下来我们分析AES加密过程中每一轮的处理步骤


AES加密


字节替换

16字节的明文块在每一个处理步骤中都被排列成4X4的二维数组

在字节替换的步骤中,矩阵中的各字节透过一个8位的S盒进行转换。这个步骤提供了加密法非线性的变换能力

image-20210225172357175

S盒的使用

  • 这里的S盒是一个16行16列的矩阵,输入8a,则去找第8行第10列交界点处的值:输出7e,即7e=S(8a)
  • 这里针对每一个字节对照S盒进行字节替换
image-20210225181454137

行移位

行移位描述矩阵的列操作。在此步骤中,每一行都向左循环位移某个偏移量

偏移量的多少和状态矩阵的列数NbN_b密切相关

image-20210225182751031

图中所示是一个4列的矩阵,移位的结果是:第一行不变,第二行循环左移1个字节,第三行循环左移2个字节,第四行循环左移3个字节

image-20210225181905001

列混淆


  • 将每列视为有限域GF(28)GF(2^8)上多项式,每一行的四个元素分别当作1,x,x2,x31, x, x^2, x^3的系数,与固定的多项式c(x)=3x3+x2+x+2c(x)=3x^3 + x^2 + x + 2进行模x4+1x^4+1后相乘,记为 \otimes
image-20210225182212003
  • 列混淆运算也可写为矩阵乘法:b(x)=c(x)a(x)b(x) = c(x) \otimes a(x)
image-20210225184704499
  • 最后一个加密循环中省略列混淆步骤,目的是在解密过程中统一每一轮解密都进行逆列混淆

加轮密钥


密钥编排


密钥编排指从种子密钥(以字节为元素的矩阵阵列描述密钥,阵列为4行,列数NkN_k为密钥长度除32)得到轮密钥的过程

image-20210225193449885
  • 密钥字符W[i]W[i]单位是以4字节(32bit)为元素的一维阵列,如果明文要经过NrN_r轮加密,状态矩阵一共有NbN_b列,则一共有Nb(Nr+1)N_b* (N_r+1)个密钥字符数量。例如AES-128就需要 4(10+1)=444 * (10 + 1) = 44个密钥字符,每一轮加密使用4个字符

    image-20210225195318012

获得W[Nb(Nr+1)]W[N_b * (N_r + 1)]

image-20210225200250807

NkN_k个字取为种子密钥,以后每个字的计算按照递归方式定义

  • 如果Nk<=6N_k <= 6

    • 如果ii不是NkN_k的倍数,那么第ii列:W[i]=W[i4]W[i1]W[i]=W[i-4] \oplus W[i-1]
    • 如果iiNkN_k的倍数,那么第ii列由如下等式确定:W[i]=W[i4]T(W[i1])W[i]=W[i-4] \oplus T(W[i-1]),这里的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;
    	}
    }
    
    image-20210225205907163

    轮常数

    Rcon[i/Nk]Rcon[i/N_k] 为轮常数,其值与NkN_k无关,定义为(字节用十六进制表示,同时理解为GF(28)GF(2^8)上的元素):

    • Rcon[i]=(RC[i],00,00,00)Rcon [i]=(RC[i], 00, 00, 00),其中RC[i]RC[i]是有限域GF(28)GF(2^8)中值为xi1x^{i - 1}的元素
  • 如果Nk>6N_k > 6,区别在于:i4i - 4NkN_k的整倍数时,须先将前一个字W[i1]W[i - 1]经过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

AES解密

image-20210225171918112

AES的每一轮解密都是AES加密相对应的逆操作

  • 逆行移位:它对状态的每一行进行循环右移

  • 逆字节替换:应用逆S盒进行字节替换

  • 逆列混淆:将状态矩阵中的每一列视为系数在GF(28)GF(2^8)上的次数小于4的多项式与同一个固定的多项式d(x)=0Bx3+0Dx2+09x+0Ed(x)=0Bx^3 + 0Dx^2 + 09x + 0E相乘,d(x)d(x)c(x)c(x)互逆

    image-20210225214634170
  • 加轮密钥

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();
    }

}
赞赏