我最近有机会使用AES加密/解密内容,但是我并不十分了解。我不禁对它的工作方式感到好奇,并且我意识到只有深入研究它的实现才能满足我的想法。这篇文章通过阅读用Go编写的核心实现,向您介绍AES加密的工作方式。
高级加密标准(AES)是对称分组密码,是DES的替代方法。来自世界各地的密码学家提出了算法,2000年从中选择了Rijndael算法。因此,我们将研究该Rijndael的实现。
Rijndael是一种分组密码算法。分组密码是一次处理特定位数的密码算法,一次要处理的位数(称为块长)取决于算法,AES是128位(16字节)。我们加密的大多数纯文本都比该块的长度长,因此我们通常会重复使用此块密码算法。请记住,本文仅针对一个块的过程进行了详细介绍,以更加关注核心算法。
Go语言正式提供crypto / aes软件包,该软件包使用Go Assembly来利用Intel对AES的硬件支持(如果它是为amd64构建的),否则将使用纯Go编写的实现。让我们看一下人脑相对可以使用的纯Go实施。
// AES块大小(以字节为单位)。 const BlockSize = 16 func(c * aesCipher)Encrypt(dst,src [] byte){如果len(src)<如果len(dst)<,则为[Blockic {panic(" crypto / aes:input not full block")}}。 BlockSize {panic(" crypto / aes:输出不完整的块")}如果很微妙。 InexactOverlap(dst [:BlockSize],src [:BlockSize]){panic(" crypto / aes:无效的缓冲区重叠")} cryptoBlockGo(c。enc,dst,src)}
//使用扩展密钥xk将src中的一个块加密为dst。 func cryptoBlockGo(xk [] uint32,dst,src []字节){_ = src [15] //早期边界检查s0:=二进制。 BigEndian。 Uint32(src [0:4])s1:=二进制。 BigEndian。 Uint32(src [4:8])s2:=二进制。 BigEndian。 Uint32(src [8:12])s3:=二进制。 BigEndian。 Uint32(src [12:16])//第一轮只用键输入XOR。 s0 ^ = xk [0] s1 ^ = xk [1] s2 ^ = xk [2] s3 ^ = xk [3] //使用表格进行中间回合。 //轮数由扩展键的长度设置。 nr:= len(xk)/ 4-2--2:2:在k:= 4上方,在k:= 4以下var t0,t1,t2,t3 uint32 for r:= 0; & 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> s0)> 16)] ^ te2 [uint8(s1>> 8)] ^ te3 [uint8(s2)] k + = 4 s0,s1,s2,s3 = t0,t1,t2,t3} //最后一轮使用s -box直接和XOR产生输出。 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] //早期边界检查二进制。 BigEndian。 PutUint32(dst [0:4],s0)二进制。 BigEndian。 PutUint32(dst [4:8],s1)二进制。 BigEndian。 PutUint32(dst [8:12],s2)二进制。 BigEndian。 PutUint32(dst [12:16],s3)}
不要害怕。我们将重点关注重要部分。
如上所示,单个块的输入(表示纯文本)为16字节。首先将输入分为四行,并以四个字节分隔。
这说明了在AES加密过程中如何处理输入。牢记这种格式绝对是理解的关键。
//中间回合使用表格进行洗牌。 //轮数由扩展键的长度设置。 nr:= len(xk)/ 4-2--2:2:在k:= 4上方,在k:= 4以下var t0,t1,t2,t3 uint32 for r:= 0; & 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> s0)> 16)] ^ te2 [uint8(s1> 8)] ^ te3 [uint8(s2)] k + = 4 s0,s1,s2,s3 = t0,t1,t2,t3}
Rijndael通过重复一个称为回合的过程对一个块进行加密。在一个回合中,共有四个过程:SubBytes,ShiftRows,MixColumns和AddRoundKey。如摘要中所述,轮数取决于密钥长度。
根据称为S-box的转换表,一次转换为1字节,该表具有256个值。下面说明了如何很好地执行sbox [s0 [1]]:
与DES不同,我们可以看到此时所有字节都已转换。本文不涉及AES的S-Box,因为它在很多地方都提到过,并且比我能做的要好得多。
下一步是处理以4个字节为单位并定期向左移动的行。要移动的字节数取决于行,如下所示:
在上一步中,它处理行,但是下一步是处理列中的字节。 它涉及有限域中的乘法运算,因此此步骤很难描述。 有关更多详细信息,请参见Wikipedia。 最后,它使用Round键对MixColumns的输出进行XOR。 下图显示了s0 [1] ^ xk [4]的执行情况(^表示Go中的XOR操作)。 虽然这篇文章仅向您显示了加密过程,但这些过程可以完全逆向,只要使用逆混合列,ShiftRows和子字节即可。 多么美丽!我将继续阅读实现,以加深对加密的理解。