type
Post
status
Published
date
Mar 10, 2025
slug
summary
Golang代码解析AES加密
tags
学习
category
技术分享
icon
password
Property
Mar 17, 2025 09:44 AM

什么是AES加密

高级加密标准(Advanced Encryption Standard)是一种对称分组密码,是 DES 的替代方案。来自全球的密码学家提出了多种算法,最终在 2000 年选择了 Rijndael 算法。因此,我们将来探讨一下这种 Rijndael 算法的实现。

Rijndael算法

Rijndael 是一种分组加密算法。分组加密算法是一种一次处理固定数量比特的加密方法。每次处理的比特数(称为分组长度)取决于具体算法,对于 AES 来说为128 比特(16 字节)。大多数需要加密的明文都超出这个分组长度,因此我们通常会多次应用这种分组加密算法。

Go语言的实现

// The AES block size in bytes. const BlockSize = 16 func (c *aesCipher) Encrypt(dst, src []byte) { if len(src) < BlockSize { panic("crypto/aes: input not full block") } if len(dst) < BlockSize { panic("crypto/aes: output not full block") } if subtle.InexactOverlap(dst[:BlockSize], src[:BlockSize]) { panic("crypto/aes: invalid buffer overlap") } encryptBlockGo(c.enc, dst, src) }
标准库aes/cipher.go
Go 语言官方提供了crypto/aes包,该包在构建 amd64 版本时利用 Go 汇编调用 Intel 的硬件 AES 支持,否则则采用纯 Go 实现。让我们看一下相对易于人理解的纯 Go 实现。
// Encrypt one block from src into dst, using the expanded key xk. func encryptBlockGo(xk []uint32, dst, src []byte) { _ = src[15] // early bounds check s0 := binary.BigEndian.Uint32(src[0:4]) s1 := binary.BigEndian.Uint32(src[4:8]) s2 := binary.BigEndian.Uint32(src[8:12]) s3 := binary.BigEndian.Uint32(src[12:16]) // First round just XORs input with key. s0 ^= xk[0] s1 ^= xk[1] s2 ^= xk[2] s3 ^= xk[3] // Middle rounds shuffle using tables. // Number of rounds is set by length of expanded key. nr := len(xk)/4 - 2 // - 2: one above, one more below k := 4 var t0, t1, t2, t3 uint32 for r := 0; r < nr; r++ { t0 = xk[k+0] ^ te0[uint8(s0>>24)] ^ te1[uint8(s1>>16)] ^ te2[uint8(s2>>8)] ^ te3[uint8(s3)] t1 = xk[k+1] ^ te0[uint8(s1>>24)] ^ te1[uint8(s2>>16)] ^ te2[uint8(s3>>8)] ^ te3[uint8(s0)] t2 = xk[k+2] ^ te0[uint8(s2>>24)] ^ te1[uint8(s3>>16)] ^ te2[uint8(s0>>8)] ^ te3[uint8(s1)] t3 = xk[k+3] ^ te0[uint8(s3>>24)] ^ te1[uint8(s0>>16)] ^ te2[uint8(s1>>8)] ^ te3[uint8(s2)] k += 4 s0, s1, s2, s3 = t0, t1, t2, t3 } // Last round uses s-box directly and XORs to produce output. s0 = uint32(sbox0[t0>>24])<<24 | uint32(sbox0[t1>>16&0xff])<<16 | uint32(sbox0[t2>>8&0xff])<<8 | uint32(sbox0[t3&0xff]) s1 = uint32(sbox0[t1>>24])<<24 | uint32(sbox0[t2>>16&0xff])<<16 | uint32(sbox0[t3>>8&0xff])<<8 | uint32(sbox0[t0&0xff]) s2 = uint32(sbox0[t2>>24])<<24 | uint32(sbox0[t3>>16&0xff])<<16 | uint32(sbox0[t0>>8&0xff])<<8 | uint32(sbox0[t1&0xff]) s3 = uint32(sbox0[t3>>24])<<24 | uint32(sbox0[t0>>16&0xff])<<16 | uint32(sbox0[t1>>8&0xff])<<8 | uint32(sbox0[t2&0xff]) s0 ^= xk[k+0] s1 ^= xk[k+1] s2 ^= xk[k+2] s3 ^= xk[k+3] _ = dst[15] // early bounds check binary.BigEndian.PutUint32(dst[0:4], s0) binary.BigEndian.PutUint32(dst[4:8], s1) binary.BigEndian.PutUint32(dst[8:12], s2) binary.BigEndian.PutUint32(dst[12:16], s3) }
如上代码包括2个要点
  • 块大小16字节
  • dstsrc 在任一索引处共享内存
 
Rijndael 通过重复一个名为 round 的过程来对数据块进行加密。在一次 round 中,包含四个过程:SubBytes、ShiftRows、MixColumns 和 AddRoundKey。如代码注释所示,round 的数量取决于密钥长度。
Rijndael 通过重复一个名为 round 的过程来对数据块进行加密。在一次 round 中,包含四个过程:SubBytes、ShiftRows、MixColumns 和 AddRoundKey。如代码注释所示,round 的数量取决于密钥长度。
转换是基于一个名为 S-box 的包含 256 个值的转换表,每次转换 1 字节完成的。下面展示如何正确执行 sbox[s0[1]]。
转换是基于一个名为 S-box 的包含 256 个值的转换表,每次转换 1 字节完成的。下面展示如何正确执行 sbox[s0[1]]。
下一步是处理被分为 4 字节单元并定期向左移动的行。要移动的字节数取决于行。
下一步是处理被分为 4 字节单元并定期向左移动的行。要移动的字节数取决于行。
在前一步中处理的是行,现在要处理的是以列为单位的字节。
在前一步中处理的是行,现在要处理的是以列为单位的字节。
最后,它将 MixColumns 的输出与轮密钥进行异或操作。下图展示了s0[1] ^ xk[4]的执行过程(其中^在 Go 中表示异或操作)
最后,它将 MixColumns 的输出与轮密钥进行异或操作。下图展示了s0[1] ^ xk[4]的执行过程(其中^在 Go 中表示异或操作)
 
2025年NAS装机示例文章

  • Giscus
  • Utterance