AES 高级加密标准(Advanced Encryption Standard),它是 DES 的进化版,加密强度更好,更难破解。
一、AES 算法总体结构
首先 AES 属于是分组加密的方式加密,算法执行时会把明文分成一组一组,每组长度都是 16 个字节,也就是 128 位,密钥的长度分为 128 位 192 位和 256 位;每组的加密算法都一样,所以我们就介绍一组加密的过程即可;这里以 128 位密钥为例,当把一组 16 个字节的明文给到算法时会执行以下步骤:
- 密钥扩展,这里会根据原始密钥扩展成 44 列的矩阵,作为后面 10 轮加密的密钥,具体扩展后文会有详细描述。
- 初始轮密钥加,轮密钥加(addRoundKey)就是将 128 位轮密钥 Ki 和状态矩阵中的数据进行逐位异或操作。
- 前 9 轮加密,每一轮会执行 4 个操作:字节代替、行移位、列混淆、轮密钥加(后面会详细介绍这四个操作。
- 第 10 轮加密,这是最后一轮加密,和前九轮的区别是没有了列混淆。
- 输出 16 字节密文。
以上就是一组加密的过程,然后再以 16 字节为一组循环整个明文,最后把所有分组得到的密文拼起来就是最终的密文。
二、核心步骤解析
1. 字节代替
这个操作其实就是根据行和列来定位,然后查找元素的过程。
取状态矩阵中的字节:把该字节的高 4 位作为行数,低 4 位作为列树,取出 S 盒中对应的行的元素作为输出。如,字节 0x12,就是查 S 盒的第 0x01 行和 0x02 列,得到值 0xc9,然后替换 S1 原有的 0x12 为 0xc9。S 盒表如下:
2. 行移位
行移位的功能是实现一个 4x4 矩阵内部字节之间的置换。
3. 正向行移位
正向行移位的原理图如下:
实际移位的操作即是:第一行保存不变,第二行循环左移 1 个字节,第三行循环左移 2 个字节,第四行循环左移 3 个字节。假设矩阵的名字为 state,用公式表示如下:state’[ i ][ j ] = state[ i ][ ( j+i )%4 ];其中 i、j 的范围是 [ 0,3 ]
- 逆向行移位
逆向行移位即是相反的操作,用公式表示如下:state’[ i ][ j ] = state[ i ][ ( 4+j-i )%4 ];其中 i、j 的范围是 [ 0,3 ]。
4. 列混合
列混淆和逆向列混淆,实际上是使用乘法矩阵,但是其加法和乘法均为定义在有限域上的加法和乘法,如下图:
还需要理解有限域的概念,在文章开头的相关知识点中有介绍,矩阵元素的乘法和加法都是定义在基于 GF(28</sup)上的二元运算,和普通的乘法和加法不一样。
乘法的规则是,拿状态矩阵中的第一列和常数矩阵中的第一行相乘,每一个元素分别相乘然后做异或,就得到了新矩阵的第一列的第一个元素,如果想得到第一列的第二个元素,则需要第一列和常数矩阵的第二行相乘,然后依次类推。比如根据上图中的数据得出:
- S’0,0 = (2 * S0,0)⨁(3 * S1,0)⨁(1 * S2,0)⨁(1*S3,0) :第一列(S0,0,S1,0,S2,0,S3,0)和常数矩阵第一行(02,03,01,01)分别相乘做异或
- S’1,0 = (1 * S0,0)⨁(2 * S1,0)⨁(3 * S2,0)⨁(1*S3,0) :第一列(S0,0,S1,0,S2,0,S3,0)和常数矩阵第二行(01,02,03,01)分别相乘做异或
- S’2,0 = (1 * S0,0)⨁(1 * S1,0)⨁(2 * S2,0)⨁(3 * S3,0) :第一列(S0,0,S1,0,S2,0,S3,0)和常数矩阵第三行(01,01,02,03)分别相乘做异或
- S’3,0 = (3 * S0,0)⨁(1 * S1,0)⨁(1 * S2,0)⨁(2*S3,0) :第一列(S0,0,S1,0,S2,0,S3,0)和常数矩阵第四行(03,01,01,02)分别相乘做异或
5. 密钥扩展
把 128 位的密钥转为 4 * 4 的状态矩阵,然后把每一列都合成一个字,4 列可得到 4 个字:W[0]、W[1]、W[2]、W[3],如图所示(先从上往下,从左往右看):
例如,密钥为“asdfefghijklmnop”则 K0 = ‘a’、K1 = ‘s’、K2 = ‘d’、K3 = ‘f’、W[0] = “asdf”。
W[0]、W[1]、W[2]、W[3] 这四个是由初始密钥生成的,则作为种子密钥。由于这次选择的是 128 位密钥,需要 10 轮加密,所要还需要 10 个密钥,也就是 40 个字,这 40 个字的生成公式分为两种情况:
- 当 n 不是 4 的倍数时:W[n] = W[n-4]⨁W[i-1]
- 当 n 是 4 的倍数时:W[n] = W[n-4]⨁T(W[i-1]);可以看出多了一个 T 函数,这个 T 函数干了三件事情:
- 字循环:将 1 个字中的 4 个字节循环左移 1 个字节,即将输入字 [b0, b1, b2, b3] 变换成 [b1,b2,b3,b0]。
- 字节代替:对字循环的结果使用 S 盒进行字节代替。
- 轮常量异或:将前两步的结果同轮常量 RC[j] 进行异或,其中 j 表示轮数,常量表如下图:
最后得到 44 个字,前 4 个字是初始种子密钥,后面 40 个字,4 个为一组得到 10 个密钥,用于后面 10 轮加密。
6. 轮密钥加
基于的原理是:任何数和自身的异或结果为 0。加密时,每轮的输入与轮密钥异或一次。轮密钥就是上面生成的 40 个字的其中一组。如下图中,状态矩阵里的一列和轮密钥的一个字做异或。
- 状态矩阵
|S0 S4 S8 S12|
|S1 S5 S9 S13|
|S2 S6 S10 S14|
|S3 S7 S11 S15|
- 轮密钥(i 表示第几轮)
w[4i] w[4i+1] w[4i+2] w[4i+3]
过程是 S0、S1、S2、S3 和 w[4i] 进行异或,过程是 S4、S5、S6、S7 和 w[4i+1] 进行异或,依次类推,最后得到一个全新的矩阵:
|A0 A4 A8 A12|
|A1 A5 A9 A13|
|A2 A6 A10 A14|
|A3 A7 A11 A15|
接下来会把这个全新的矩阵当作是下一轮的输入。
经过 9 轮处理后,该第 10 轮加密时,注意了,和前 9 次不一样,少了一步列混淆,第 10 轮加密包含:
- 字节代替
- 行移位
- 轮密钥加
第 10 轮加密后得到的 16 字节密文,就是一组密文,之后依次循环所有明文进行加密,把得到的所有密文组合在一起,就是完整的密文。
三、总结
介绍了 AES 属于分组加密,一组 16 个字节,首先会进行初始轮密钥加,然后进行 10 轮加密,其中每一轮中会进行:字节代替、行移位、列混淆、轮密钥加。只有第 10 轮特殊,少了一个列混淆,最后输出的就是一组明文对应的密文,之后把所有明文按照这种方式分组加密得到全部密文。
四、AES算法Go语言实践
秘钥长度分别为16位、24位、32位分别对应AES-128、AES-192、AES-256
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"fmt"
)
//为最后一组填充数据
func PaddingText(src []byte,blockSize int)[]byte{
length := len(src)%blockSize
//获取需要填充的字节数
padding := blockSize-length
//填充数据
paddText := bytes.Repeat([]byte{byte(padding)},padding)
//将填充数据追加到原始数据
newText := append(src,paddText...)
return newText
}
func UnPaddingText(src []byte) []byte{
length := len(src)
//取出原始数据最后一个字节
number := int(src[length-1])
//去除填充数据
newText := src[:length-number]
return newText
}
//使用DES算法对文件进行加密
//src:需要被加密的明文
//key:秘钥
func EncryptDES(src,key []byte)([]byte,error){
//生成加密用的block
block, err := aes.NewCipher(key)
if err!=nil{
return []byte(""),err
}
length := block.BlockSize()
//拼接数据
src = PaddingText(src,length)
//NewCBCEncrypter第二个参数是初始化向量,长度要求和块大小一样,内容随意(需要和解密初始化向量相同)
//根据块和向量创建CBC加密模式
blockMode := cipher.NewCBCEncrypter(block,key[:block.BlockSize()])
//创建切片,用于存储加密之后的密文
dest := make([]byte,len(src))
//加密
blockMode.CryptBlocks(dest,src)
return dest,nil
}
//使用DES算法解密
//src:需要被解密的密文
//key:秘钥,需要和加密时使用的秘钥相同
func DecryptDES(src,key []byte)([]byte,error){
block, err := aes.NewCipher(key)
if err != nil{
return []byte(""),err
}
//准备初始化向量
//创建解密模式
blockMode := cipher.NewCBCDecrypter(block,key[:block.BlockSize()])
//创建切片,用于存储解密之后的明文
dest := make([]byte,len(src))
//解密
blockMode.CryptBlocks(dest,src)
NewText := UnPaddingText(dest)
return NewText,nil
}
func main(){
src := []byte("单枪匹马你别怕,一腔孤勇又如何!")
key := []byte("1234567812345678")
ciphertext,err := EncryptDES(src,key)
if err != nil{
fmt.Println("Encrypt err:",err)
return
}
fmt.Printf("%x\n",ciphertext)
plaintext,err := DecryptDES(ciphertext,key)
if err != nil{
fmt.Println("Decrypt err:",err)
return
}
fmt.Println(string(plaintext))
}