对称加密-分组密码

它的核心算法是在数据分组或数据分组序列上进行一系列运算。

它由加密算法和解密算法组成。

加密算法(E)使用密钥K和明文P,生成密文C。我们将加密过程记为C=E(K,P)。

解密算法(D)与加密算法相反,将消息解密为原始明文P。该操作被记为P=D(K,C)。

安全性

分组密码应该是伪随机置换(PRP)的,这意味着只要密钥是秘密的,攻击者就不能从任何输入计算分组密码的输出。也就是说,只要对攻击者来说,K是秘密的和随机的,那么对于任何给定的P,他们就不应该知道E(K,P)的结果。

攻击者应该无法在分组密码的输入/输出值中发现任何规律。

分组大小 

分组密码的安全性取决于分组大小和密钥大小。

DES的分组有64比特,AES的分组有128比特。

不过,分组不能太大,在保证足够安全的前提下,应使密文的长度和内存占用都尽量最小

假如在分组为128比特时对16比特消息进行加密,首先需要将消息转换为128比特分组,然后分组密码算法处理该消息并返回128比特密文,这样看来,分组越大,占用内存越大。

 码本攻击

用16比特分组进行:

1.得到对应于每个16比特明文分组的65 536(2^16)个密文。

2.建立一个查找表——码本,其将每个密文分组映射到相应的明文分组。

3.对未知的密文分组进行解密,查找表中对应的明文分组。


一般仅当分组密码使用较小分组时才有效,因为16比特分组需要16×2^16=128KB内存,32比特分组需要32×2^32=128GB,64比特分组需要2^70比特,在可见的未来都不可能有这么大内存。

构造分组密码

 在实践中使用的分组密码是一个循环多轮的运算,轮本身是很弱的一系列运算,但数量很多。构造一个循环主要有两种技术:

置换网络(如在AES中)

Feistel方案(如在DES中)。

轮数

 计算一个分组密码可归结为计算一系列轮的序列。在分组密码中,轮是一个基本变换,它易于指定和实现,并且被多次迭代以形成分组密码的算法。这种由多次重复的小组件组成的结构比由单个大型算法组成的结构更容易实现和分析。


例如,具有三轮的分组密码通过计算C=R3(R2(R1(P)))加密明文,R1、R2和R3是轮函数,P是明文。每个轮函数也应该有一个逆,以便接收者可以计算回明文。p=iR1(iR2(iR3(C))),其中iR1是R1的逆。


轮函数(R1、R2等)通常用的是相同的算法,但它们由不同的子密钥进行区分。具有不同子密钥的两个轮函数将具有不同的行为,因此相同的输入也将产生不同的输出。

子密钥 

子密钥是由主密钥K产生的密钥,使用的是被称为密钥调度的算法。每一轮的子密钥是互不相同的。

例如,R1使用子密钥K1,R2使用子密钥K2,...,Rn使用子密钥Kn。


使用子密钥的一个好处是可防止边信道攻击,或者利用从密码中泄露的信息实现攻击(例如,电磁辐射)。如果从主密钥K转换为子密钥Ki是不可逆的,那么即使攻击者找到Ki,他们也无法还原主密钥K。不过,很少有分组密码使用单向密钥。AES的密钥生成算法允许攻击者从任何子密钥Ki计算K。

滑动攻击

滑动攻击寻找两个明文-密文对(P1,C1)和(P2,C2),其中P2=R(P1),R是轮函数。当轮函数相同时,假设是1轮,由C1=R(P1),P2=R(P1),C2=R(P2)得到C2=R(C1)。通过这些输入输出的关系,有助于攻击者找到破解的办法。


下面的图显示了3个轮,但不管轮的数目是多少,可用数学归纳法证明C2=R(C1)始终成立。

轮函数相同时,使用不同的子密钥作为参数可确保每轮运算不同,从而抵御滑动攻击。

AES No Padding 语言 aes指令_分组密码

S盒 

S盒(Substitution-box)是对称密钥算法执行置换计算的基本结构。


S盒的功能就是一种简单的代替操作。S盒是将48比特压缩成32比特,S盒接受特定数量的输入48比特,经过8个盒将其转换为32比特输出。


一个S盒就是一个4行16列的表,盒中的每一项都是一个4位二进制数表示的十进制数。S盒的6个输入确定了其对应的那个盒。输入的最高、最低两位(高位在左)做为行号H,中间四位做为列号L,在对应的S盒中查找第H行L列对应的数据。(行列号计数都是从0开始。)


S盒的指标的好坏直接决定了密码算法的好坏,它们应该尽可能地非线性(输入和输出应该与复杂方程相关),并且没有统计偏差(这意味着改变任意一个输入比特都将潜在地影响输出比特)。

替换-置换网络 (SPN)

密码学中的混淆指输入(明文和加密密钥)经历复杂的变换,而扩散指这些变换等同地依赖于输入的所有比特位。在高层次上,混淆是关于深度的,而扩散是关于宽度的。在分组密码的设计中,混淆和扩散采取替换和置换操作的形式,包含在替换-置换网络(SPN)中。


替换通常以S盒或替换盒的形式出现,一般是4比特或8比特的小型找表。

例如,分组密码Serpent的8个S盒中的第一个由16个元素组成(3 8 f 1 a 6 5 b e d 4 2 7 0 9 c),其中每个元素表示4比特区域。这个特定的S盒将4比特0000(下标0)映射到3(0011),4比特0101(下标5)映射到6(0110)。


替换-置换网络中的置换可以轻松改变输入比特的顺序,这很容易实现,但是它不会产生太多混淆。一些密码不重新排序输入比特,而是使用基本的线性代数和矩阵乘法来混合比特:

它们执行一系列具有固定值(矩阵的系数)的乘法运算,然后得到结果。这种线性代数运算可以快速地创建密码中所有比特之间的依赖关系,从而确保强扩散。

 Feistel结构

在20世纪70年代,IBM的工程师Horst Feistel设计了一种分组密码,称为Lucifer。

工作原理:

1.将64比特分组分成两个32比特的L和R。

2.设L为L⊕F(R),其中F是替换-置换轮函数。

3.交换L和R的值。

4.转到步骤2,重复15次。

5.合并L和R为64比特输出分组。


这称为Feistel结构,下图左边是算法的步骤,右边是其等价描述,使用轮交替操作L=L⊕F(R)和R=R⊕F(L)代替3步骤。第一个F取第一个子密钥,第N个F取第N个子密钥。

DES的F函数是48比特子密钥,是从56比特主密钥K生成的。

AES No Padding 语言 aes指令_分组密码_02

在Feistel结构中,F函数是伪随机置换(PRP)或者伪随机函数(PRF)。PRP对于不同的输入有不同的输出;但是对于两个不同的输入,PRF可能产生相同的输出,即对于具有不同值的X和Y,可以有F(X)=F(Y)。但在Feistel结构中,只要F密码性强大,就不会造成影响。


DES执行16轮,GOST 28147-89执行32轮。如果F函数足够强大,理论上4轮就足够了,但是真正的密码使用更多的轮来抵御F中的潜在弱点。

 AES(高级加密标准)

介绍

AES是一种加密标准,由于DES现在已不再安全(密钥只有56比特),AES现在替代了DES(数据加密标准)及其衍生版本,AES也称为Rijndael(发明者名字的缩写,Rijmen和Daemen)算法。


严格地说,AES和Rijndael并不完全一样(虽然在实际应用中二者可以互换),因为Rijndael加密法可以支持更大范围的区块和密钥长度:AES的区块长度(分组)固定为128比特,密钥长度则可以是128,192或256比特;而Rijndael使用的密钥和区块长度可以是32比特的整数倍,以128比特为下限,256比特为上限。加密过程中使用的密钥是由Rijndael密钥生成方案产生。


它将一个16字节的明文看作二维字节数组(s=s0,s1,… ,s15)(4×4),AES转换该数组中的字节、列和行,以产生最终值即密文。AES的内部状态如下图所示。

AES No Padding 语言 aes指令_分组密码_03

为了转换其状态,AES使用下图所示的SPN结构,其中128比特密钥计算10轮,192比特密钥计算12轮,256比特密钥计算14轮。


AES No Padding 语言 aes指令_AES No Padding 语言_04

AES加密过程

 除了最后一轮外,所有轮都按照SubBytes、ShiftRows、MixColumns和AddRoundKey的顺序


AddRoundKey:异或(XOR)子密钥以生成一个内部状态。


SubBytes:利用S盒把(s0,s1,…,s15)中的每个字节都替换成一个新的字节。在这个例子中,S盒是256个元素的查找表。


ShiftRows:对状态中第i行循环向左移动i个位置,范围从0到3。第一行维持不变,第二行里的每个字节都向左循环移动一格。第三行及第四行向左循环位移的偏移量分别是2和3。128位和192比特的区块在此步骤的循环位移的模式相同。对于长度256比特的区块,第一行仍然维持不变,第二行、第三行、第四行的偏移量分别是1字节、3字节、4字节。经过ShiftRows之后,矩阵中每一竖列,都是由输入矩阵中的每个不同列中的元素组成。


MixColumns:对状态的4列中的每一列应用相同的线性转换(即每组单元格具有相同的灰色阴影,下图中左侧)。做法是用一个常量矩阵C×状态矩阵,这里的加法和乘法运算不同于平时,它是有限域GF(2^8)上的二元运算(多项式运算):

对于两个多项式f(x) = x^7 + x^4 + x + 1,g(x) = x^4 + x^3 + 1。

加法和减法等价于两个字节的异或;

f(x) + g(x)  = x^7 + (1⊕1)x^4 + x^3 + x + (1⊕1)1 = x^7 + x^3 + x 。

f(x) – g(x)等于f(x) + g(x)。

乘法:

f(x) * g(x) =(x^11 + x^10 + x^7)  ⊕  (x^10 + x^7 + x^4 + x^3)  ⊕  (x^7+ x^4 + x + 1)

= x^11 + x^7 + x^3 + x + 1。

有限域就是函数的运算结果全都包含在一个值域中,有限域有一个最大值,所有超过这个最大值的数最终都会回到这个域中,2^8代表值域的最大值为256。


AES的最后一轮不包括MixColumns操作,以另一个AddRoundKey取代。

AES No Padding 语言 aes指令_AES No Padding 语言_05

在SPN中,S代表替换,P代表置换。

这里SubBytes是替换,ShiftRows和MixColumns是置换。

密钥调度函数KeyExpansion是AES密钥调度算法,它创建11个16字节的子密钥(K0,K1,…,K10),每个子密钥都是由16字节的密钥使用相同S盒进行SubBytes和XOR运算得到的。KeyExpansion的一个重要特点是给定任何一个子密钥Ki,攻击者可以通过可逆算法来确定所有其他的子密钥以及主密钥K。可以从任何子密钥获取密钥,通常被看作是对边信道攻击无法进行有效防御,即攻击者可以很容易恢复子密钥。


解密过程:AES通过取其逆函数展开每个操作:SubBytes逆查询表,反向(向右)移动的ShiftRows,MixColumns的逆(与编码其操作的矩阵的逆一样),AddRoundKey的XOR不变,因为XOR的逆也是XOR。

安全性

AES达到了分组密码的最高安全等级,并且永远不会被攻破。从根本上说,AES是安全的,因为所有输出比特依赖于所有输入比特进行复杂的伪随机运算。为了达到这个目的,AES的设计人员出于特定的原因仔细地选择每个组件——MixColumns具有最大的扩散特性,SubBytes具有最佳的非线性——并且他们已经表明,这种组合能够防止AES受到所有类型的密码分析攻击。

没有证据证明AES对所有可能的攻击都免疫。唯一眼见为实的是:许多技术人员尝试攻破AES,但都失败了。

不安全的场景 

缺乏以下运算,AES将完全不安全。

如果没有KeyExpansion,所有轮都使用相同的密钥K,AES将容易受到滑动攻击。


如果没有AddRoundKey,加密将不依赖于密钥;因此,任何人都可以在没有密钥的情况下解密任何密文。


SubBytes引入非线性操作,增加了密码强度。没有它,AES将只是一个由线性函数构成的大系统,利用高等代数就可以实现破解。


没有ShiftRows,给定列中的更改永远不会影响其他列,这意味着可以通过为每列构造4个232个元素的查找表来攻破 AES。(记住,在安全的分组密码中,在输入中更改一个比特应该影响所有的输出比特。)


没有MixColumns,字节的变化不会影响该状态的任何其他字节。选择明文攻击者在存储16个查找表(每个查找表256字节)之后可以解密任何密文,因为这些查找表保存每个字节可能的加密值。

实现AES

AES在实际中的工作方式与上述介绍的算法不同。不会调用SubBytes()函数、ShiftRows()函数和MixColumns()函数,这样做效率很低。相反,AES快速实现软件使用特殊的技术,其被称为基于表的实现原生指令集

基于表的实现

AES的基于表的实现利用查询硬编码在程序中并在执行时加载到内存中的表和XOR运算组合操作替换了SubBytes-ShiftRows-MixColumns运算。MixColumns等效于XOR 4个32比特的值,其中每个值都依赖于来自AES内部状态的单个字节和SubBytes。所以可以构建4个表,每个表有256项,每项1字节,通过查找4个32比特的值并对它们进行XOR运算来实现SubBytes-MixColumns序列。


一个基于查找表的AES加密实现需要4KB的表,因为每个表存储256个32比特的值,这些值占用256×32=8192比特,大约为1KB。解密一共需要4个表,因此需要4KB。有一些技巧可以使存储从4KB减少到1KB及以下。


基于查找表的实现容易受到基于时间的缓存攻击,当程序读取或写入缓存内存中的元素时,基于时间的缓存攻击利用时间变化差异进行攻击,因为访问高速缓冲存储器中元素的相对位置的时间不同。访问时间泄露了访问哪个元素的信息,这又泄露了涉及的秘密信息。


基于时间的缓存攻击是很难避免的,采用硬件原生指令集可以有效避免这个问题。

原生指令集 

AES原生指令集(AES-NI)解决了AES软件实现中存在的基于时间的缓存攻击问题。

为了实现一个密码算法,我们通常只将其表述为加法、乘法、XOR等基本运算的组合,微处理器按照规定的顺序激活它的加法器、乘法器和XOR器。


当使用AES-NI时,只需调用指令AESENC或AESENCLAST,芯片将自动计算轮。

# xmm0存储加密结果
# xmm5~xmm15存储子密钥,结果写入xmm0
# PXOR是第0轮先对第一个密钥进行XOR运算
# AESENCLAST是没有MixColumns的轮
PXOR %xmm5,%xmm0
AESENC %xmm6,%xmmO
AESENC %xmm7,%xmmO
AESENC %xmm8,%xmmO
AESENC %xmm9,%xmmO
AESENC %xmm10,%xmm0
AESENC %xmm11,%xmm0
AESENC %xmm12,%xmm0
AESENC %xmm13,%xmmO
AESENC %xmm14,%xmmO
AESENCLAST %xmm15,%xmmO

上面的汇编代码首先在寄存器xmm0中对128比特明文进行加密,假设寄存器xmm5到xmm15保存预先计算的子密钥,每个指令将结果写入xmm0。初始PXOR指令在第一轮计算之前对第一子密钥进行XOR运算,而执行最后一轮的AESENCLAST指令省略了MixColumns操作。

分组密码工作方式

共有五种模式。

电码本模式(ECB) 

分组密码加密模式中最简单的是电码本模式(ECB),它是一种运算模式。ECB取明文分组P1,P2,…,PN,并分别独立运算C1=E(K,P1)、C2=E(K,P2),...,Cn=E(K,PN)。

ECB模式是非常不安全的,不要在生产环境使用


AES No Padding 语言 aes指令_安全_06

ECB原理图

密码分组链接模式(CBC)

密码分组链接(CBC)类似于ECB,和ECB模式仅仅加密第i个分组Ci=E(K,Pi)不同,在CBC模式中,Ci=E(K,Pi⊕C(i-1)),其中C(i-1)是前一个密文分组,这说明Ci分组依赖C(i-1)分组,也就是依赖之前的所有分组。当加密第一个分组P1时,没有以前的密文分组可以使用,所以CBC取一个随机的初始值(IV)。


因为初始值是随机的,所以相同的明文加密后也会是不同的密文。 


AES No Padding 语言 aes指令_主密钥_07

CBC原理图

不过,CBC通常与固定IV一起使用,而不是使用随机IV,这将暴露不同的明文是以相同分组开始的。例如,将两个明文分组P1 || P2在CBC模式下加密到两个密文分组C1 || C2。如果P1 || P2'是用相同的IV加密的,其中P2'是与P2不同的分组,则密文是C1 || C2',这可以看出C2'与C2不同但C1相同。因此,攻击者可以猜测出两个明文的第一个分组是相同的。


在CBC模式中,解密函数需要知道使用IV用于解密,所以IV将以明文的形式和密文一起被发送。


CBC模式下,解密可以比加密快得多。当加密新分组Pi时需要等待前一分组Ci-1,但是分组的解密计算Pi=D(K,Ci)⊕C(i-1)并不需要前一个明文分组Pi-1。这意味着只要同时知道上一个密文分组,所有分组都可以同时进行解密。

加密长度不是分组长度倍数的明文 

例如当分组为16字节时,用AES-CBC加密60字节的明文,通常有两种办法处理:

1.填充

2.密文窃取

填充消息

填充是一种允许加密任意长度消息的技术,可以用于比单个分组还小的消息。

分组密码的填充采用RFC 5652中制定的PKCS#7标准。


填充通过在明文中添加额外字节将消息扩展成一个完整的分组。下面是填充16字节分组的规则:

1.如果剩下1字节,如明文是1字节、17字节,则用15个字节0x0f来填充消息。

2.如果剩下2字节,如明文是2字节、18字节,则用14个字节0x0e来填充消息。

3.如果剩下3字节,如明文是3字节、19字节,则用13个字节0x0d来填充消息。

...

15.如果剩下15字节,如明文是15字节、31字节,则用1个字节0x01来填充消息。

如果明文长度已经是16的倍数,则可以增加16字节0x10。

这个方法可以推广到不超过255字节长度的任意分组。


解密操作:

1.用无填充的CBC解密所有分组。

2.确保最后一个分组的最后字节符合填充规则:它们以至少1个字节的0x01、至少2个字节的0x02或至少3个字节的0x03等结束,否则填充无效。

例如,如果最后的字节是0x01 0x02 0x03,则消息被拒绝。

如果解密成功则去掉填充字节并返回左边的明文字节。


填充的一个缺点是,它使密文延长至少1字节,最多是1个分组。

密文窃取 

密文窃取比填充更复杂,一般不使用,但它可至少提供三个好处:

1.明文可以是任何比特长度的,而不仅仅是字节长度的。例如,可以加密一个131比特的消息。

2.密文与明文的长度完全相同。

3.密文窃取可以抵御Padding Oracle攻击,这种攻击对采用填充的CBC模式非常有效。


在CBC模式下,密文窃取用前一个密文分组的比特 扩展最后一个不完整的明文分组,然后对得到的分组进行加密。最后一个不完整的密文分组是由前一个密文分组的前一部分组成的;也就是说,是没有加到最后一个明文分组的那些。


下图假设我们有三个分组,其中最后一个分组P3是不完整的(用零表示)。P3异或(XOR)前一个分组密文的后一部分,得到的加密结果为C2。最后的密文分组C3,是前一个密文分组的前一部分组成的。


AES No Padding 语言 aes指令_分组密码_08

密文窃取

计数模式(CTR)

CTR不是分组密码模式:它把分组密码转换成序列密码,只接收比特,然后输入比特。)

在CTR模式中,分组密码算法不会转换明文数据。相反,它将加密由计数器和随机数N组成的分组。计数器是随着每个分组递增的整数。在消息中没有两个分组使用相同的计数器,但是不同的消息可以使用相同的计数器序列(1,2,3,...)。随机数是一个只使用一次的数字,对于一条消息中的所有分组都是相同的,但没有两个消息会使用相同的随机数。


在CTR模式下,加密算法就是使明文异或(XOR)随机数N和计数器Ctr得到的序列,解密是和加密相同的,因为XOR的逆运算也是XOR。


AES No Padding 语言 aes指令_分组密码_09

CTR加密原理图

与CBC中的初始值一样,CTR中的随机数是由加密器提供的,并混在密文中以明文形式发送。但与CBC的初始值不同,CTR的随机数不需要是随机的,它只需要是唯一的。一个随机数应该是唯一的,目的是防止被重复利用。


随机数只有在足够长时才会起作用。例如,如果随机数是n比特的,那么在加密随机数达到2^(n/2)之后就有可能出现重复。因此,64比特对于随机数是不够的,可以估计在大约2^32个随机数之后可能出现重复,这是一个不可接受的小的数目。


如果计数器对于每个新明文都递增,并且足够长,则可以保证它是唯一的。

CTR的一个特别的好处是,它比其他模式更快。

密码反馈模式(CFB)

暂时用不到,后面补充。

输出反馈模式(OFB)

暂时用不到,后面补充。

针对分组密码的常用攻击手段 

中间相遇攻击(MitM)

3DES分组密码是20世纪70年代标准DES的升级版本,它采用56×3=168比特的密钥(DES是56比特密钥)。因为中间相遇攻击的原因,3DES的安全级别是112比特的,而不是168比特的。

3DES使用DES加密和解密函数对分组进行加密:首先使用密钥K1进行加密,然后使用密钥K2进行解密,最后使用密钥K3进行加密。如果K1=K2,前两个运算相互抵消,3DES则归结为密钥K3的单个DES。3DES使用加密-解密-加密模式而非加密三次,是因为系统可能在调用3DES时使用DES,这样K1 = K2即可获得DES的加密效果。


AES No Padding 语言 aes指令_分组密码_10

3DES加密过程

已经证明MitM攻击将使得2DES的安全性只能像DES一样。

攻击2DES过程如下:

1.假设现在有一个已知P及两个未知的56比特密钥K1和K2,C=E(K2,E(K1,P))。(2DES是112比特的密钥。)构建一个包含2^56项的E(K1,P)的密钥值表(类似哈希表,K1加密后的密文做键,K1做值),其中E是DES加密函数。


2.对于K2的所有2^56个值,计算D(K2,C)并检查结果值是否作为键在步骤1的密钥值表中找到,其中D是DES解密函数。


3.如果能找到,则从密钥值表中取出所有符合的K1,并通过使用其他相对应的P和C验证找到的(K1,K2)是否正确。做法是使用K1和K2加密P,然后检查获得的密文是否为给定的C。


步骤1加密2^56个分组,步骤2解密最多2^56个分组,总共2^56+2^56=2^57次操作。还需要存储2^56个15字节长的数值,大约128 PB。


可以将MitM攻击2DES的方法应用到3DES中,除了第三阶段需要计算K2和K3的所有2^112个值。整个攻击在执行约2^112次运算后成功,这表示3DES只有112比特的安全性。


AES No Padding 语言 aes指令_主密钥_11

中间相遇攻击

密文填塞攻击(Padding Oracle)

CBC填充模式中,填充明文中的额外字节可得到完整的分组。例如,111字节的明文是6个16字节的分组序列和一个15字节的分组。要形成完整的分组,需要填充一个0x01字节。对于110字节的明文,需要填充2个0x02字节,以此类推。


Padding Oracle是一个根据CBC加密密文中的填充是否有效而有不同行为的系统。可以将其视为黑盒或API,它返回成功或错误。在远程主机上的服务中,Padding Oracle接收到错误的密文时将返回错误消息。给定一个Padding Oracle,其记录哪些是有效的输入,哪些是无效的输入,并利用这些信息来解密所选择的密文。


假设要解密密文分组C2,X为D(K,C2)的值,P2为在CBC模式下解密后获得的明文分组。如果选择一个随机分组C1,并将两个分组密文C1 || C2发送到Padding Oracle,则解密将在C1⊕P2=X以有效填充结尾时才成功,如1个字节的0x01,2个字节的0x02,或3个字节的0x03,等等。


Padding Oracle攻击CBC加密可以以如下方式解密C2(X[15]代表X的第16个字节):

1.选择一个随机分组C1并改变它的最后一个字节,直到Padding Oracle接受密文有效为止。通常,在一个有效的密文中,P2[15] = C1[15]⊕X[15]=0x01,在最多尝试128个C1[15]的值之后,可以找到X[15](如果没找到说明不是0x01结尾)。


2.通过将C1[15]设置为X[15]⊕0x02并尝试给出正确填充的C1[14],找出值X[14]。直到Padding Oracle没有返回错误时,说明已经找到C1[14],满足P2[14] = C1[14]⊕X[14]=0x02。


3.对所有16个字节重复步骤1和步骤2。


攻击16个字节中的每个字节平均查询128次,总共大约有2000次查询。(注意,每次查询必须使用相同的初始值。)