块密码

块密码/分组密码

在密码学中,分组加密(英语:Block cipher),又称分块加密或块密码,是一种对称密钥算法。它将明文分成多个等长的模块(block),使用确定的算法和对称密钥对每组分别加密解密。分组加密是极其重要的加密协议组成,其中典型的如AES和3DES作为美国政府核定的标准加密算法,应用领域从电子邮件加密到银行交易转帐,非常广泛。

分组加密包含两个成对的算法:加密算法 $E$ 和解密算法 $D$,两者互为反函数。每个算法有两个输入:长度为 $n$ 位的组,和长度为 $k$ 位的密钥;两组输入均生成 $n$ 位输出。将两个算法看作函数,$K$ 表示长度为 $k$ 的密钥(密钥长度),$P$ 表示长度为 $n$ 的分组,$P$ 也被表示为明文,$C$ 表示密文,则满足:

$E_K(P) = C \; ; \quad E_K^{-1}(C)=P$

$E_K(P) := E(K,P): \{0,1\}^k \times \{0,1\}^n \rightarrow \{0,1\}^n,$

对于任意密钥 $K$,$E_K(P)$ 是输入的组的一个置换函数,且可逆地落在 $\{0,1\}^n$ 区间。$E$ 的反函数(解密算法)定义为:

$E_K^{-1}(C) := D_K(C) = D(K,C): \{0,1\}^k \times \{0,1\}^n \rightarrow \{0,1\}^n,$

例如,一个分组加密算法使用一段 128 位的分组作为明文,相应输出 128 位的密文;而其转换则受加密算法中第二个输入的控制,也就是密钥 $k$。解密算法类似,使用 128 位的密文和对应的密钥,得到原 128 位的明文。

每一个密钥实际上是选择了 $n$ 位输入排列的 $(2^n)!$ 种组合中的一种。

大多数的分组密码在在加密算法中会重复使用某一函数进行多轮运算,典型的轮数在4-32次之间,每一轮的函数R使用不同的子密钥 $K_i$,由原密钥生成,作为输入:

$M_i = R_{K_i}(M_{i-1})$

其中 $M_{0}$ 是最初的明文,$M_{i}$ 是第 $i$ 轮加密后的密文。

AES

高级加密标准(英语:Advanced Encryption Standard,缩写:AES),又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。现在,高级加密标准已然成为对称密钥加密中最流行的算法之一。

该算法为比利时密码学家Joan Daemen和Vincent Rijmen所设计,结合两位作者的名字,以Rijndael为名投稿高级加密标准的甄选流程。

加密方法

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

大多数AES计算是在一个特别的有限域完成的。

AES加密过程是在一个4×4的字节矩阵上运作,这个矩阵又称为“体(state)”,其初值就是一个明文区块(矩阵中一个元素大小就是明文区块中的一个Byte)。(Rijndael加密法因支持更大的区块,其矩阵的“列数(Row number)”可视情况增加)加密时,各轮AES加密循环(除最后一轮外)均包含4个步骤:

  1. AddRoundKey 矩阵中的每一个字节都与该次回合密钥(round key)做XOR运算;每个子密钥由密钥生成方案产生。
  2. SubBytes 透过一个非线性的替换函数,用查找表的方式把每个字节替换成对应的字节。
  3. ShiftRows 将矩阵中的每个横列进行循环式移位。
  4. MixColumns 为了充分混合矩阵中各个直行的操作。这个步骤使用线性转换来混合每内联的四个字节。最后一个加密循环中省略MixColumns步骤,而以另一个AddRoundKey取代。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//Java实现
import java.io.*;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.util.Arrays;
import javax.crypto.Cipher;

class test
{
public static void main (String[] args) throws java.lang.Exception
{
byte[] plain = "flag{xxxxxxxxx}".getBytes();
System.out.println(Arrays.toString(plain));
byte[] key = {-114, 62, 98, 26, 54, -7, -59, -47, 55, 88, 18, -1, -99, 116, -51, 62};
SecretKeySpec secretKeySpec = new SecretKeySpec(key, 0, key.length, "AES");
MessageDigest instance = MessageDigest.getInstance("SHA-256");
instance.reset();
byte[] digest = instance.digest(plain);
Cipher instance2 = Cipher.getInstance("AES/ECB/NoPadding");
instance2.init(1, secretKeySpec);
byte[] cipher = instance2.doFinal(digest);
System.out.println(Arrays.toString(cipher));


byte[] c = {11, -35, 55, 10, 62, 79, 125, 62, -28, 115, 77, 4, 73, 0, 11, 121, -126, 85, -83, 109, 1, -98, 35, -68, -4, -122, 14, 110, -28, 111, 22, -125};
secretKeySpec = new SecretKeySpec(key, 0, key.length, "AES");
instance2 = Cipher.getInstance("AES/ECB/NoPadding");
instance2.init(2, secretKeySpec);
byte[] p = instance2.doFinal(c);
System.out.println(Arrays.toString(p));

String out = Base64.getEncoder().encodeToString(p);
System.out.println(out);

}
}

DES

数据加密标准(英语:Data Encryption Standard,缩写为 DES)是一种对称密钥加密块密码算法,1976年被美国联邦政府的国家标准局确定为联邦资料处理标准(FIPS),随后在国际上广泛流传开来。它基于使用56位密钥的对称算法。这个算法因为包含一些机密设计元素,相对短的密钥长度以及怀疑内含美国国家安全局(NSA)的后门而在开始时有争议,DES因此受到了强烈的学院派式的审查,并以此推动了现代的块密码及其密码分析的发展。

DES现在已经不是一种安全的加密方法,主要因为它使用的56位密钥过短。

加密方法

DES是一种典型的块密码—一种将固定长度的明文通过一系列复杂的操作变成同样长度的密文的算法。对DES而言,块长度为64位。同时,DES使用密钥来自定义变换过程,因此算法认为只有持有加密所用的密钥的用户才能解密密文。密钥表面上是64位的,然而只有其中的56位被实际用于算法,其余8位可以被用于奇偶校验,并在算法中被丢弃。因此,DES的有效密钥长度仅为56位。

算法的整体结有16个相同的处理过程,称为“回次”(round),并在首尾各有一次置换,称为IP与FP(或称IP-1,FP为IP的反函数(即IP“撤销”FP的操作,反之亦然)。IP和FP几乎没有密码学上的重要性,为了在1970年代中期的硬件上简化输入输出数据库的过程而被显式的包括在标准中。

在主处理回次前,数据块被分成两个32位的半块,并被分别处理;这种交叉的方式被称为费斯妥结构。费斯妥结构保证了加密和解密过程足够相似—唯一的区别在于子密钥在解密时是以反向的顺序应用的,而剩余部分均相同。这样的设计大大简化了算法的实现,尤其是硬件实现,因为没有区分加密和解密算法的需要。

费斯妥函数(F函数)将数据半块与某个子密钥进行处理。然后,一个F函数的输出与另一个半块异或之后,再与原本的半块组合并交换顺序,进入下一个回次的处理。在最后一个回次完成时,两个半块需要交换顺序,这是费斯妥结构的一个特点,以保证加解密的过程相似。

费斯妥函数(F函数)每次对半块(32位)进行操作,并包括四个步骤:

  1. 扩张 用扩张置换将32位的半块扩展到48位,其输出包括8个6位的块,每块包含4位对应的输入位,加上两个邻接的块中紧邻的位。
  2. 与密钥混合 用异或操作将扩张的结果和一个子密钥进行混合。16个48位的子密钥—每个用于一个回次的F变换—是利用密钥调度从主密钥生成的。
  3. S盒 在与子密钥混合之后,块被分成8个6位的块,然后使用“S盒”,或称“置换盒”进行处理。8个S盒的每一个都使用以查找表方式提供的非线性的变换将它的6个输入位变成4个输出位。S盒提供了DES的核心安全性—如果没有S盒,密码会是线性的,很容易破解。
  4. 置换 最后,S盒的32个输出位利用固定的置换,“P置换”进行重组。这个设计是为了将每个S盒的4位输出在下一回次的扩张后,使用4个不同的S盒进行处理。

S盒,P置换和E扩张各自满足了克劳德·香农在1940年代提出的实用密码所需的必要条件,“混淆与扩散”。

弱密钥

在DES的计算中,56bit的密钥最终会被处理为16个轮密钥,每一个轮密钥用于16轮计算中的一轮,DES弱密钥会使这16个轮密钥完全一致,所以称为弱密钥。

四个弱密钥:

1
2
3
4
\x01\x01\x01\x01\x01\x01\x01\x01
\xFE\xFE\xFE\xFE\xFE\xFE\xFE\xFE
\xE0\xE0\xE0\xE0\xF1\xF1\xF1\xF1
\x1F\x1F\x1F\x1F\x0E\x0E\x0E\x0E

如果不考虑校验位的密钥,以下也属于弱密钥:

1
2
3
4
\x00\x00\x00\x00\x00\x00\x00\x00
\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF
\xE1\xE1\xE1\xE1\xF0\xF0\xF0\xF0
\x1E\x1E\x1E\x1E\x0F\x0F\x0F\x0F

如果使用弱密钥,PC1计算的结果会导致轮密钥全部为0、全部为1或全部01交替。
因为所有的轮密钥都是一样的,并且DES是 Feistel网络的结构,这就导致加密函数是自反相 (self-inverting) 的,结果就是加密一次看起来没什么问题,但是如果再加密一次就得到了明文。

部分弱密钥

部分弱密钥是指只会在计算过程中产生两个不同的子密钥,每一个在加密的过程中使用8次。这就意味着这对密钥$k_1$ 和 $k_2$ 有如下性质:$E_{k_1}(E_{k_2}(M))=M$。

6个常见的部分弱密钥对:

1
2
3
4
5
6
0x011F011F010E010E + 0x1F011F010E010E01
0x01E001E001F101F1 + 0xE001E001F101F101
0x01FE01FE01FE01FE + 0xFE01FE01FE01FE01
0x1FE01FE00EF10EF1 + 0xE01FE01FF10EF10E
0x1FFE1FFE0EFE0EFE + 0xFE1FFE1FFE0EFE0E
0xE0FEE0FEF1FEF1FE + 0xFEE0FEE0FEF1FEF1

加解密工具

  • openssl

    加密

    openssl enc -aes-128-cbc -in plain.txt -out encrypt.txt -iv f123 -K 1223 -p

    -p: 打印加密用的salt,key,iv

    解密

    openssl aes-128-cbc -d -in encrypt.txt -out encrypt_decrypt.txt -S E0DEB1EAFE7F0000 -iv F1230000000000000000000000000000 -K 12230000000000000000000000000000

    openssl enc -d -aes256 -in fl@g.txt -out 1.txt

    -S: salt

模式攻击

ECB模式

最简单的加密模式即为电子密码本(Electronic codebook,ECB)模式。需要加密的消息按照块密码的块大小被分为数个块,并对每个块进行独立加密。

中间人攻击(MITM)

假如存在一个攻击者,当他作为中间人截获两方的通信时,他能够改变密文的分组顺序,当接收者对密文进行解密时,由于密文分组的顺序被改变了,因此相应的明文分组的顺序也被改变了,那么接收者实际上是解密出了一段被篡改后的密文。在这种场景中,攻击者不需要破译密码,也不需要知道分组密码的算法,他只需要知道哪个分组记录了什么样的数据。

参考:Boston Key Party CTF 2013 - MITM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#python2
from Crypto.Cipher import AES
from binascii import hexlify,unhexlify
from string import printable

alph = printable
m = ''
c = ''
flag_c = ''
middle = dict();

for x in alph:
for y in alph:
for z in alph:
key1 = '%s%s%s%s' % ('0' * 13, x, y, z)
cipher = AES.new(key1)
middle.update({cipher.encrypt(m): key1})

print "\nTable built...\n";

for x in alph:
for y in alph:
for z in alph:
key2 = '%s%s%s%s' % (x, y, z, '0' * 13)
cipher = AES.new(key2)
d = cipher.decrypt(c)
if d in middle:
print "\nKeys found: %s; %s\nFlag:" % (middle[d].encode('hex'), key2.encode('hex'))
cipher1 = AES.new(middle[d])
print cipher1.decrypt(cipher.decrypt(flag_c))

模式攻击

动态填充加密+枚举secret字符

方法一

1.明文由输入值m+flag+padding组成,$m$ 为空时, $c$ 可分 $k$ 块,不断调整 $m$ 的长度,直到 $m$ 长度为 $l+1$ 时 $c$ 可分 $k+1$ 块,那么说明 $m$ 长度为 $l$ 时 $c$ 刚好可分 $k$ 块,即无padding情况下,$m+flag$ 可分 $k$ 块,则flag长度即为 $8k-l$。

2.利用上面的思想,在 $m$ 长度为 $l$ 的基础上,长度不断加1,则可以把flag从后开始的每一位推到下一个块中,得到下一个块的密文 $c_i$;

3.又已爆破出的flag位+padding已知,则下一个块的构成为未知字符1位+已爆破出的flag位(+padding)

4.根据DES-ECB的性质,相同明文块对应的密文块相同。爆破第一位未知字符,将上面的块构成作为输入值输入,得到对应的密文的第一块,分别与实际密文 $c_i$ 比较,匹配的即为正确的明文字符。

5.以此类推,得到完整flag。

参考:

ECB模式攻击

1024杯 - 密码系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
from pwn import *
import gmpy2
import string

host = 'das.wetolink.com'
port = 42887
block = 16
secret_len = 38
ori_padding_len = 10
dic = '{}-'+string.ascii_lowercase+string.digits

p = connect(host,port)
flag = ''

def pad(leng):
pad_len = block - (leng % block) if leng % block != 0 else 0
return chr(pad_len) * pad_len

padding = [pad(k) for k in range(16)]
padding = padding[1:]+[padding[0]]

for i in range(secret_len):
find=0
payload = ('*'*(ori_padding_len+i+1)).encode('hex')

group = i//block
p.recvuntil('Amazing function: ')
p.sendline(payload)
data = p.recvline().strip()
print([data[i:i+32] for i in range(0,len(data),32)])
print(data)
if group == 0:
prob=data[-32:]
else:
prob=(data[-32*(group+1):-32*(group+1)+32])
print(str(i+1)+' prob = '+str(prob))
for j in dic:
p.recvuntil('Amazing function: ')
flag_suffix = flag[:min(len(flag),15)]
payload = (j + flag_suffix + padding[min(len(flag_suffix),15)]).encode('hex')
p.sendline(payload)
data = p.recvline().strip()
if data[:32]==prob:
flag = j + flag
print(str(i+1)+' flag = '+flag)
print()
find=1
break
if find == 0:
print(str(i+1)+' cannot find!')
break

方法二

1.由于是ECB的模式,所以当我们输入十五个’0’后,服务会将十五个’0’+flag加密,而此时第一组就是十五个’0’和flag的第一个字符。即,返回的明文的第一组是’0’*15 + flag[0]的密文。

2.我们遍历0-255,发送’0’*15+chr(i),看返回的密文是不是和最初获得的密文的第一组一致,如果一致,那么此时的chr(i)就是flag的第一位。

3.有了第一位我们就可以发送’0’*14+flag[0]过去,此时返回的第一组密文就是’0’*14+flag[0]+flag[1]的密文了,我们继续用第2步的方法就可以恢复flag[1]了。

4.如此循环往复,逐位爆破flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def exp():  
sh = remote("0.0.0.0","9999")

pre="0"*47
flag=""
for block in range(41):
#发送填充,泄露一位flag,并获取密文
sh.recvuntil("Amazing function: ")
sh.sendline(pre.encode('hex'))
target = sh.recvuntil("\n")[:-1][64:96]
for i in range(256):
#遍历字符,找到与获取密文一致时的情况时,即得到一位明文
tmp = '0'*(47-block)+flag+chr(i)
sh.recvuntil("Amazing function: ")
sh.sendline(tmp.encode('hex'))
now = sh.recvuntil("\n")[:-1]
if now[64:96] == target:
flag += chr(i)
#修改填充
pre = pre[:-1]
break
return flag[7:-2]

重排攻击

在块加密中ECB模式中每个块都是独立加密的。因此攻击者可以在未知密钥的情况下,对密文中的块进行重新排列,组合成合法的可解密的新密文。

考虑这么一种场景,某CMS的cookie格式为DES-ECB加密后的数据,而明文格式如下:

1
admin=0;username=pan 

由于DES使用的块大小是8字节,因此上述明文可以切分成三个块,其中@为填充符号:

1
2
3
admin=0;
username
=pan@@@@

假设我们可以控制自己的用户名(在注册时),那么有什么办法可以在不知道密钥的情况下将自己提取为管理员呢(即admin=1)?首先将用户名设置为pan@@@@admin=1;,此时明文块的内容如下:

1
2
3
4
admin=0;
username
=pan@@@@
admin=1;

我们所需要做的,就是在加密完成后,将服务器返回的cookie使用最后一个块替换第一个块,这样一来就获得了一个具有管理员权限的合法cookie了。

CBC模式

IBM发明了密码分组链接(CBC,Cipher-block chaining)模式。在CBC模式中,每个明文块先与前一个密文块进行异或后,再进行加密。在这种方法中,每个密文块都依赖于它前面的所有明文块。同时,为了保证每条消息的唯一性,在第一个块中需要使用初始化向量。

CBC

加密:

Ciphertext-0 = Encrypt(Plaintext XOR IV) ——只用于第一个组块
Ciphertext-N= Encrypt(Plaintext XOR Ciphertext-N-1) ——用于第二及剩下的组块

$\left\{\begin{array} {c} c_1 = \text{Enc}(m_1 \oplus \text{iv}) \newline c_2 = \text{Enc}(m_2 \oplus c_1) \newline \vdots \newline c_i = \text{Enc}(m_i \oplus c_{i-1}) \end{array} \right.$

CBC

解密:

Plaintext-0 = Decrypt(Ciphertext) XOR IV ——只用于第一个组块
Plaintext-N= Decrypt(Ciphertext) XOR Ciphertext-N-1 ——用于第二及剩下的组块

$\left\{\begin{array} {c} m_1 = \text{Dec}(c_1)\oplus \text{iv} \newline m_2 = \text{Dec}(c_2)\oplus c_1 \newline \vdots \newline m_i = \text{Dec}(c_i)\oplus c_{i-1} \end{array} \right.$

CBC字节翻转

Plaintext:明文数据

IV:初始向量

Key:分组加密使用的密钥

Ciphertext:密文数据

每组解密时,先进行分组加密算法的解密,然后与前一组的密文进行异或才是最初的明文。

对于第一组则是与IV进行异或。

上一块密文用来产生下一块明文,如果改变上一块密文的一个字节,然后与下一个解密后的组块异或,就可以得到一个不同的明文。

CBC

目标:要把Plain2中的某一字节翻转为另一字节。

由于C1来自于Cipher2进行Block Cipher Decryption之后的结果,而且Key未知,就不能直接得知C1的值;

但可通过字节翻转修改上一组密文Cipher1来翻转下一组的明文Plain2,从而可以完全忽视未知的C1值。

由异或运算可以推导:B1 = A1 ^ C1,则也有:C1 = A1 ^ B1

假设修改后的上一组密文为A1',要翻转的下一组明文为B1',则有:B1' = A1' ^ C1

如果能够修改Cipher1,那么就能够修改A1的值为A1',即A1' = A1 ^ B1 ^ B1'

即:

1
2
3
4
5
6
7
8
B = A ^ C
B' = A' ^ C
A' = A ^ B ^ B'

A=原上一组密文Cipher1
B=原下一组明文Plain2
B‘=要翻转的下一组明文Plain2'
A'=要修改的上一组密文Cipher1'

要求:1. 对于A1完全可控;2. 已知B1的值。

由于修改了A1,Cipher1在进行Block Cipher Decryption的时候会得出错误的结果,再与IV异或会导致Plain1出错;

如果能够得到修改A1之后产生的错误的Plain1的值,而且IV可以完全控制的话,那么就能够再套用一次前面的方法。

即:

1
2
3
4
5
6
7
8
B = A ^ C
B' = A' ^ C
A' = A ^ B ^ B'

A=原IV值iv
B=错误的明文Plain1
B‘=要翻转的明文Plain1'
A'=要修改的IV值iv'

参考:SYPUCTF2020 - Yusa的密码学课堂—CBC第一课

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from pwn import *
import binascii
import time

p = remote('das.wetolink.com',42888) # 连接
p.recvline()
p.recvline()
p.recvline()
p.recvline()
p.sendline('1') # 注册
p.recv()
p.sendline('Admin') # 用户名为Admin,方便之后修改
data0 = p.recvline().decode()
data0 = data0[28:124] # 提取返回数据部分
iv0 = data0[:32] #返回的IV
cipher00 = data0[32:64] #'yusayusayusayusa'的加密结果
cipher01 = data0[64:96] #'Admin'的加密结果
replacement0 = str(hex(int(cipher00[:2],16)^ord('A')^ord('a')))[2:] # 计算替换密文的值
payload0 = iv0+replacement0+cipher00[2:]+cipher01 # 发送替换密文
print('data0: ',data0)
print('payload0: ',payload0)
p.recvline()
p.recvline()
p.recvline()
p.recvline()
p.sendline('2') # 登入
p.recv()
p.sendline(payload0) # 发送Payload0
data1 = p.recvline()[:64+len('Admin')*2] # 得到返回的数据
data1 = data1.decode()
plain1 = data1[32:64] # 'yusayusayusayusa'由于密文被替换,解出来的明文是错误的,之后可以进行异或修改
print('data1: ',data1)
print('plain1: ',plain1)
iv1 = str(hex(int(binascii.hexlify('yusa'.encode()).decode()*4,16)^int(plain1,16)^int(iv0,16)))[2:] # 计算IV,用于修改错误的明文
print('iv1: ',iv1)
payload1 = iv1+replacement0+cipher00[2:]+cipher01
print('payload1: ',payload1)
p.recvline()
p.recvline()
p.recvline()
p.recvline()
p.sendline('2') # 登入
p.recv()
p.sendline(payload1) # 发送Payload1
p.recvline()
p.recvline()
print(p.recvline()) # 得到flag

CBC字节翻转(Web)

参考:bugkuctf–login4字节翻转攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//原密文修改字符
<?php
$enc=base64_decode("bIpgPK29vVQosJ+smzh0pOdq7QrP3H9CN0MBfynL1eKtILs/ayew1snTYbeYSIz8rQctkAUMORS76SWQHXwuKg==");
$enc[13] = chr(ord($enc[13]) ^ ord("k") ^ ord ("n"));
echo base64_encode($enc);
?>

//新密文,修改对应iv值保证首块解密无错
<?php
$enc=base64_decode("4quudO++PAeVPQfcFJ0bbm1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjM6IjEyMyI7fQ==");
$iv=base64_decode("TrphJjWLH37sj6+EBqh28A==");
$cleartext = 'a:2:{s:8:"userna';
$newiv = '';
for ($i=0;$i<16;$i++){
$newiv=$newiv.chr(ord($iv[$i]) ^ ord($enc[$i]) ^ ord ($cleartext[$i]));
}
echo base64_encode($newiv);
?>

Padding Oracle Attack

Padding Oracle Attack漏洞主要是由于设计使用的场景不当,导致可以利用密码算法通过”旁路攻击“被破解,利用该漏洞可以破解出密文的明文以及将明文加密成密文,该漏洞存在条件如下:

  • 攻击者能够获取到密文(基于分组密码模式),以及IV向量(通常附带在密文前面,初始化向量)
  • 攻击者能够修改密文触发解密过程,解密成功和解密失败存在差异性
根源

这个攻击的根源是明文分组和填充,同时应用程序对于填充异常的响应可以作为反馈,例如请求 http://www.example.com/decrypt.jsp?data=0000000000000000EFC2807233F9D7C097116BB33E813C5E,当攻击者在篡改data值时会有以下不同的响应:

  • 如果data值没有被篡改,则解密成功,并且业务校验成功,响应200
  • 如果data值被篡改,服务端无法完成解密,解密校验失败,则响应500
  • 如果data值被篡改,但是服务端解密成功,但业务逻辑校验失败,则可能返回200或302等响应码,而不是响应500

攻击者只需要关注解密成功和解密失败的响应即可(第三种属于解密成功的响应),即可完成攻击。

破解明文

解密最后一个数据块,其结尾应该包含正确的填充序列。如果这点没有满足,那么加/解密程序就会抛出一个填充异常。Padding Oracle Attack的关键就是利用程序是否抛出异常来判断padding是否正确。

在不知道明文的情况下,如何猜解出明文?首先将密文分组,前面8个字节为初始化向量,后面16个字节为加密后的数据:

1
2
3
初始化向量:7B  21  6A  63  49  51  17  0F
第一组密文:F8 51 D6 CC 68 FC 95 37
第二组密文:85 87 95 A2 8E D4 AA C6

看如何通过构造前面初始向量来破解第一组密文:http://www.example.com/decrypt.jsp?data=7B216A634951170FF851D6CC68FC9537

  • 将初始化向量全部设置为0,提交如下请求http://www.example.com/decrypt.jsp?data=00000000000000000F851D6CC68FC9537 ,服务器势必会解密失败,返回HTTP 500,是因为在对数据进行解密的时候,明文最后一个字节的填充是0x3D,不满足填充规则,校验失败,此时示意图:

img

  • 依次将初始化向量最后一个字节从0x01~0xFF递增,直到解密的明文最后一个字节为0x01,成为一个正确的padding,当初始化向量为000000000000003C时,成功,服务器返回HTTP 200,解密示意图:

img

  • 已知构造成功的IV最后一个字节为0x3C,最后一个填充字符为0x01,则能通过XOR计算出,第一组密文解密后的中间值最后一个字节:0x01 xor 0x3C = 0x3D;重点:第一组密文解密的中间值是一直不变的,同样也是正确的,通过构造IV值,使得最后一位填充值满足0x01,符合padding规则,则意味着程序解密成功(当前解密的结果肯定不是原来的明文),通过循环测试的方法,猜解出中间值的最后一位,再利用同样的方式猜解前面的中间值,直到获取到完整的中间值。
  • 下面将构造填充值为0x02 0x02的场景,即存在2个填充字节,填充值为0x02,此时已经知道了中间值得最后一位为0x3D,计算出初始向量的最后一位为 0x3D xor 0x02 = 0x3F,即初始向量为0000000000000003F,遍历倒数第二个字节从0x00~0xFF,直到响应成功,猜解出中间值得后两个字节分别为 0x26 0x3D,示意图:

img

  • 通过同样的方式,完成第一组密文中间值的猜解。

img

  • 当第一组密文的中间值猜解成功后,将中间值和已知的IV做异或,则得到第一组密文的明文:
1
2
3
4
5
0x39 0x73 0x23 0x22 0x07 0x6A 0x26 0x3D 
xor
0x7B 0x21 0x6A 0x63 0x49 0x51 0x17 0x0F
=
BRIAN;12
  • 续破解第二组密文,第二组密文的IV向量是第一组密文,按照上述的逻辑构造第一组密文,即可破解出第二组明文。
伪造密文

已经知道了中间值,那么只需传递指定的IV,就能制造任意想要的密文,如加密TEST

img

BP插件:PaddingOracleHunter

SM4

SM4是一种分组密码算法,其分组长度为128位(即16字节,4字),密钥长度也为128位(即16字节,4字)。其加解密过程采用了32轮迭代机制(与DES、AES类似),每一轮需要一个轮密钥(与DES、AES类似)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# Python
class SM4Cipher:
def __init__(self, key: bytes):
if not len(key) == 16:
raise ValueError("SM4 key must be length of 16. ")
self._key_r = self._generate_key(key)
self.block_size = 16

def encrypt(self, plaintext: bytes):
return self._do(plaintext, self._key_r)

def decrypt(self, ciphertext: bytes):
return self._do(ciphertext, self._key_r[::-1])

def _do(self, text: bytes, key_r: list):
text_ = [0 for _ in range(4)]
# 将 128bit 转化成 4x32bit
for i in range(4):
text_[i] = int.from_bytes(text[4 * i:4 * i + 4], 'big')
for i in range(32):
box_in = text_[1] ^ text_[2] ^ text_[3] ^ key_r[i]
box_out = self._s_box(box_in)
temp = text_[0] ^ box_out ^ self._rot_left(box_out, 2) ^ self._rot_left(box_out, 10)
temp = temp ^ self._rot_left(box_out, 18) ^ self._rot_left(box_out, 24)
text_ = text_[1:] + [temp]
text_ = text_[::-1] # 结果逆序
# 将 4x32bit 合并成 128bit
result = bytearray()
for i in range(4):
result.extend(text_[i].to_bytes(4, 'big'))
return bytes(result)

def _generate_key(self, key: bytes):
"""密钥生成"""
key_r, key_temp = [0 for _ in range(32)], [0 for _ in range(4)]
FK = [0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc]
CK = [0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269, 0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249, 0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229, 0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209, 0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279]
# 将 128bit 拆分成 4x32bit
for i in range(4):
temp = int.from_bytes(key[4 * i:4 * i + 4], 'big')
key_temp[i] = temp ^ FK[i]
# 循环生成轮密钥
for i in range(32):
box_in = key_temp[1] ^ key_temp[2] ^ key_temp[3] ^ CK[i]
box_out = self._s_box(box_in)
key_r[i] = key_temp[0] ^ box_out ^ self._rot_left(box_out, 13) ^ self._rot_left(box_out, 23)
key_temp = key_temp[1:] + [key_r[i]]
return key_r

@staticmethod
def _s_box(n: int):
BOX = [0xD6, 0x90, 0xE9, 0xFE, 0xCC, 0xE1, 0x3D, 0xB7, 0x16, 0xB6, 0x14, 0xC2, 0x28, 0xFB, 0x2C, 0x05, 0x2B,
0x67, 0x9A, 0x76, 0x2A, 0xBE, 0x04, 0xC3, 0xAA, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99, 0x9C, 0x42,
0x50, 0xF4, 0x91, 0xEF, 0x98, 0x7A, 0x33, 0x54, 0x0B, 0x43, 0xED, 0xCF, 0xAC, 0x62, 0xE4, 0xB3, 0x1C,
0xA9, 0xC9, 0x08, 0xE8, 0x95, 0x80, 0xDF, 0x94, 0xFA, 0x75, 0x8F, 0x3F, 0xA6, 0x47, 0x07, 0xA7, 0xFC,
0xF3, 0x73, 0x17, 0xBA, 0x83, 0x59, 0x3C, 0x19, 0xE6, 0x85, 0x4F, 0xA8, 0x68, 0x6B, 0x81, 0xB2, 0x71,
0x64, 0xDA, 0x8B, 0xF8, 0xEB, 0x0F, 0x4B, 0x70, 0x56, 0x9D, 0x35, 0x1E, 0x24, 0x0E, 0x5E, 0x63, 0x58,
0xD1, 0xA2, 0x25, 0x22, 0x7C, 0x3B, 0x01, 0x21, 0x78, 0x87, 0xD4, 0x00, 0x46, 0x57, 0x9F, 0xD3, 0x27,
0x52, 0x4C, 0x36, 0x02, 0xE7, 0xA0, 0xC4, 0xC8, 0x9E, 0xEA, 0xBF, 0x8A, 0xD2, 0x40, 0xC7, 0x38, 0xB5,
0xA3, 0xF7, 0xF2, 0xCE, 0xF9, 0x61, 0x15, 0xA1, 0xE0, 0xAE, 0x5D, 0xA4, 0x9B, 0x34, 0x1A, 0x55, 0xAD,
0x93, 0x32, 0x30, 0xF5, 0x8C, 0xB1, 0xE3, 0x1D, 0xF6, 0xE2, 0x2E, 0x82, 0x66, 0xCA, 0x60, 0xC0, 0x29,
0x23, 0xAB, 0x0D, 0x53, 0x4E, 0x6F, 0xD5, 0xDB, 0x37, 0x45, 0xDE, 0xFD, 0x8E, 0x2F, 0x03, 0xFF, 0x6A,
0x72, 0x6D, 0x6C, 0x5B, 0x51, 0x8D, 0x1B, 0xAF, 0x92, 0xBB, 0xDD, 0xBC, 0x7F, 0x11, 0xD9, 0x5C, 0x41,
0x1F, 0x10, 0x5A, 0xD8, 0x0A, 0xC1, 0x31, 0x88, 0xA5, 0xCD, 0x7B, 0xBD, 0x2D, 0x74, 0xD0, 0x12, 0xB8,
0xE5, 0xB4, 0xB0, 0x89, 0x69, 0x97, 0x4A, 0x0C, 0x96, 0x77, 0x7E, 0x65, 0xB9, 0xF1, 0x09, 0xC5, 0x6E,
0xC6, 0x84, 0x18, 0xF0, 0x7D, 0xEC, 0x3A, 0xDC, 0x4D, 0x20, 0x79, 0xEE, 0x5F, 0x3E, 0xD7, 0xCB, 0x39,
0x48]
result = bytearray()
# 将 32bit 拆分成 4x8bit,依次进行S盒变换
for item in list(n.to_bytes(4, 'big')):
result.append(BOX[item])
return int.from_bytes(result, 'big')

@staticmethod
def _rot_left(n, m):
"""循环左移"""
return ((n << m) | (n >> (32 - m))) & 0xFFFFFFFF

key = bytes.fromhex("0123456789ABCDEFFEDCBA9876543210") # 128bit密钥
plaintext = bytes.fromhex("00112233445566778899aabbccddeeff") # 128bit明文
sm4 = SM4Cipher(key)
print(sm4.encrypt(plaintext).hex()) # 09325c4853832dcb9337a5984f671b9a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
// C 脚本1
#include <stdio.h>
#include <iostream>
#include<bits/stdc++.h>
#include <string.h>
/************************************固定参数****************************************/
//S盒
const unsigned char Sbox[256] = {
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
};
//CK为固定参数,用于密钥扩展算法
const unsigned int CK[32] = {
0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279 };
//系统参数FK,密钥扩展算法中会使用到
const unsigned int FK[4]={0xA3B1BAC6, 0x56AA3350, 0x677D9197, 0xB27022DC};
/************************************全局变量****************************************/
unsigned int rk[32];//子密钥
unsigned int X[32];//每一轮加密的结果
unsigned int X_0[32];//每一轮解密的结果
unsigned int y[4];//暂时存放32轮后的密文
unsigned int y2[4];//暂时存放32轮后的明文
/***********************************测试用例*****************************************/
unsigned int key[4]={0x01234567,0x89abcdef,0xfedcba98,0x76543210};//
//unsigned int message[4]={0x01234567,0x89abcdef,0xfedcba98,0x76543210};
//unsigned int message[16]={0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01};
/*
*利用S盒非线性置换
*传进来的A(32位)取高位的8位(大端机真实的低位在高位储存)与0xFF做与运算,
*经过Sbox置换后放置低位的8位中,再与其他置换出的数做或运算得到t
*截断用 >>(位数) & 0xff,而拼接用 |
*/
unsigned int T(unsigned int A){
unsigned int t;
t = Sbox[(A)>>24&0xFF]<<24|Sbox[(A)>>16&0xFF]<<16|Sbox[(A)>>8&0xFF]<<8|Sbox[(A)&0xFF];
return t;
}
/*
*循环右移
*x为数,y为右移的位数
*/
unsigned int Rotl(unsigned int x, unsigned int y){
unsigned int t = ((x)>>(y)|(x)<<(32-(y)));
return t;
}
/*
*线性转换L1
*在轮函数中用到
*/
unsigned int L1(unsigned int t){
t = (t)^Rotl(t,2)^Rotl(t,10)^Rotl(t,18)^Rotl(t,24);
return t;
}
/*
*线性转换L2
*在子密钥扩展中用到
*/
unsigned int L2(unsigned int t){
t = (t)^Rotl(t,12)^Rotl(t,22);
return t;
}
/*
*sms4加密算法
*/
void encrypt(unsigned int x[], unsigned int rk[32], int num){
for(int i=0; i<32; i+=4){
x[0+num] = x[0+num]^L1(T(x[1+num]^x[2+num]^x[3+num]^rk[i+0]));
x[1+num] = x[1+num]^L1(T(x[2+num]^x[3+num]^x[0+num]^rk[i+1]));
x[2+num] = x[2+num]^L1(T(x[3+num]^x[0+num]^x[1+num]^rk[i+2]));
x[3+num] = x[3+num]^L1(T(x[0+num]^x[1+num]^x[2+num]^rk[i+3]));
X[i+0] = x[0+num];
X[i+1] = x[1+num];
X[i+2] = x[2+num];
X[i+3] = x[3+num];
}
y[0] = X[31];
y[1] = X[30];
y[2] = X[29];
y[3] = X[28];
}
/*
*sms4解密算法
*/
void decrypt(unsigned int x[], unsigned int rk[32], int num){
for(int i=0; i<32; i+=4){
x[0] = x[0]^L1(T(x[1]^x[2]^x[3]^rk[31-i]));
x[1] = x[1]^L1(T(x[2]^x[3]^x[0]^rk[30-i]));
x[2] = x[2]^L1(T(x[3]^x[0]^x[1]^rk[29-i]));
x[3] = x[3]^L1(T(x[0]^x[1]^x[2]^rk[28-i]));
X_0[i+0] = x[0];
X_0[i+1] = x[1];
X_0[i+2] = x[2];
X_0[i+3] = x[3];
}
y2[0] = X_0[31];
y2[1] = X_0[30];
y2[2] = X_0[29];
y2[3] = X_0[28];
}
/*
*密钥扩展算法
*/
void keyExt(unsigned int key[4]){
unsigned int K[4];
K[0] = key[0]^FK[0];
K[1] = key[1]^FK[1];
K[2] = key[2]^FK[2];
K[3] = key[3]^FK[3];
for(int i=0; i<32; i+=4){
K[0] = K[0]^L2(T(K[1]^K[2]^K[3]^CK[i+0]));
K[1] = K[1]^L2(T(K[2]^K[3]^K[0]^CK[i+1]));
K[2] = K[2]^L2(T(K[3]^K[0]^K[1]^CK[i+2]));
K[3] = K[3]^L2(T(K[0]^K[1]^K[2]^CK[i+3]));
rk[i+0] = K[0];
rk[i+1] = K[1];
rk[i+2] = K[2];
rk[i+3] = K[3];
}
}
int main(){
unsigned int cipher[4];
unsigned int *message_0;
//生成子密钥
keyExt(key);
//开始解密
FILE *p = fopen("./cipher.txt", "r+");
FILE *p2 = fopen("./decrypted_message.txt", "wb");
printf("\n明文为:\n");
for(int j=0;j<494868;j+=4){
for(int i=0;i<4;i++)
fscanf(p,"%08x", &cipher[i]);
decrypt(cipher,rk,j);
message_0 = y2;
for(int i=0;i<4;i++){
printf("%08x ", message_0[i]);
fprintf(p2,"%08x ", message_0[i]);
}
printf("\n");
fprintf(p2,"\n");
}
fclose(p);
fclose(p2);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
// C 脚本2
#ifndef _SM4_H_
#define _SM4_H_
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define u8 unsigned char
#define u32 unsigned long

void four_uCh2uLong(u8* in, u32* out); //四字节转换成u32

void uLong2four_uCh(u32 in, u8* out); //u32转换成四字节

unsigned long move(u32 data, int length); //左移,保留丢弃位放置尾部

unsigned long func_key(u32 input); //先使用Sbox进行非线性变化,再将线性变换L置换为L'

unsigned long func_data(u32 input); //先使用Sbox进行非线性变化,再进行线性变换L

void print_hex(u8* data, int len); //无符号字符数组转16进制打印

void encode_fun(u8 len, u8* key, u8* input, u8* output); //加密函数

void decode_fun(u8 len, u8* key, u8* input, u8* output); //解密函数

/******************************定义系统参数FK的取值****************************************/
const u32 TBL_SYS_PARAMS[4] = {
0xa3b1bac6,
0x56aa3350,
0x677d9197,
0xb27022dc
};

/******************************定义固定参数CK的取值****************************************/
const u32 TBL_FIX_PARAMS[32] = {

0x00070e15,0x1c232a31,0x383f464d,0x545b6269,
0x70777e85,0x8c939aa1,0xa8afb6bd,0xc4cbd2d9,
0xe0e7eef5,0xfc030a11,0x181f262d,0x343b4249,
0x50575e65,0x6c737a81,0x888f969d,0xa4abb2b9,
0xc0c7ced5,0xdce3eaf1,0xf8ff060d,0x141b2229,
0x30373e45,0x4c535a61,0x686f767d,0x848b9299,
0xa0a7aeb5,0xbcc3cad1,0xd8dfe6ed,0xf4fb0209,
0x10171e25,0x2c333a41,0x484f565d,0x646b7279
};

/******************************SBox参数列表****************************************/
const u8 TBL_SBOX[256] = {

0xd6,0x90,0xe9,0xfe,0xcc,0xe1,0x3d,0xb7,0x16,0xb6,0x14,0xc2,0x28,0xfb,0x2c,0x05,
0x2b,0x67,0x9a,0x76,0x2a,0xbe,0x04,0xc3,0xaa,0x44,0x13,0x26,0x49,0x86,0x06,0x99,
0x9c,0x42,0x50,0xf4,0x91,0xef,0x98,0x7a,0x33,0x54,0x0b,0x43,0xed,0xcf,0xac,0x62,
0xe4,0xb3,0x1c,0xa9,0xc9,0x08,0xe8,0x95,0x80,0xdf,0x94,0xfa,0x75,0x8f,0x3f,0xa6,
0x47,0x07,0xa7,0xfc,0xf3,0x73,0x17,0xba,0x83,0x59,0x3c,0x19,0xe6,0x85,0x4f,0xa8,
0x68,0x6b,0x81,0xb2,0x71,0x64,0xda,0x8b,0xf8,0xeb,0x0f,0x4b,0x70,0x56,0x9d,0x35,
0x1e,0x24,0x0e,0x5e,0x63,0x58,0xd1,0xa2,0x25,0x22,0x7c,0x3b,0x01,0x21,0x78,0x87,
0xd4,0x00,0x46,0x57,0x9f,0xd3,0x27,0x52,0x4c,0x36,0x02,0xe7,0xa0,0xc4,0xc8,0x9e,
0xea,0xbf,0x8a,0xd2,0x40,0xc7,0x38,0xb5,0xa3,0xf7,0xf2,0xce,0xf9,0x61,0x15,0xa1,
0xe0,0xae,0x5d,0xa4,0x9b,0x34,0x1a,0x55,0xad,0x93,0x32,0x30,0xf5,0x8c,0xb1,0xe3,
0x1d,0xf6,0xe2,0x2e,0x82,0x66,0xca,0x60,0xc0,0x29,0x23,0xab,0x0d,0x53,0x4e,0x6f,
0xd5,0xdb,0x37,0x45,0xde,0xfd,0x8e,0x2f,0x03,0xff,0x6a,0x72,0x6d,0x6c,0x5b,0x51,
0x8d,0x1b,0xaf,0x92,0xbb,0xdd,0xbc,0x7f,0x11,0xd9,0x5c,0x41,0x1f,0x10,0x5a,0xd8,
0x0a,0xc1,0x31,0x88,0xa5,0xcd,0x7b,0xbd,0x2d,0x74,0xd0,0x12,0xb8,0xe5,0xb4,0xb0,
0x89,0x69,0x97,0x4a,0x0c,0x96,0x77,0x7e,0x65,0xb9,0xf1,0x09,0xc5,0x6e,0xc6,0x84,
0x18,0xf0,0x7d,0xec,0x3a,0xdc,0x4d,0x20,0x79,0xee,0x5f,0x3e,0xd7,0xcb,0x39,0x48
};

#endif


//4字节无符号数组转无符号long型
void four_uCh2uLong(u8* in, u32* out)
{
int i = 0;
*out = 0;
for (i = 0; i < 4; i++)
*out = ((u32)in[i] << (24 - i * 8)) ^ *out;
}

//无符号long型转4字节无符号数组
void uLong2four_uCh(u32 in, u8* out)
{
int i = 0;
//从32位unsigned long的高位开始取
for (i = 0; i < 4; i++)
*(out + i) = (u32)(in >> (24 - i * 8));
}

//左移,保留丢弃位放置尾部
u32 move(u32 data, int length)
{
u32 result = 0;
result = (data << length) ^ (data >> (32 - length));

return result;
}

//秘钥处理函数,先使用Sbox进行非线性变化,再将线性变换L置换为L'
u32 func_key(u32 input)
{
int i = 0;
u32 ulTmp = 0;
u8 ucIndexList[4] = { 0 };
u8 ucSboxValueList[4] = { 0 };
uLong2four_uCh(input, ucIndexList);
for (i = 0; i < 4; i++)
{
ucSboxValueList[i] = TBL_SBOX[ucIndexList[i]];
}
four_uCh2uLong(ucSboxValueList, &ulTmp);
ulTmp = ulTmp ^ move(ulTmp, 13) ^ move(ulTmp, 23);

return ulTmp;
}

//加解密数据处理函数,先使用Sbox进行非线性变化,再进行线性变换L
u32 func_data(u32 input)
{
int i = 0;
u32 ulTmp = 0;
u8 ucIndexList[4] = { 0 };
u8 ucSboxValueList[4] = { 0 };
uLong2four_uCh(input, ucIndexList);
for (i = 0; i < 4; i++)
{
ucSboxValueList[i] = TBL_SBOX[ucIndexList[i]];
}
four_uCh2uLong(ucSboxValueList, &ulTmp);
ulTmp = ulTmp ^ move(ulTmp, 2) ^ move(ulTmp, 10) ^ move(ulTmp, 18) ^ move(ulTmp, 24);

return ulTmp;
}

//加密函数(可以加密任意长度数据,16字节为一次循环,不足部分补0凑齐16字节的整数倍)
//len:数据长度(任意长度数据) key:密钥(16字节) input:输入的原始数据 output:加密后输出数据
void encode_fun(u8 len, u8* key, u8* input, u8* output)
{
int i = 0, j = 0;
u8* p = (u8*)malloc(50); //定义一个50字节缓存区
u32 ulKeyTmpList[4] = { 0 }; //存储密钥的u32数据
u32 ulKeyList[36] = { 0 }; //用于密钥扩展算法与系统参数FK运算后的结果存储
u32 ulDataList[36] = { 0 }; //用于存放加密数据

/***************************开始生成子秘钥********************************************/
four_uCh2uLong(key, &(ulKeyTmpList[0]));
four_uCh2uLong(key + 4, &(ulKeyTmpList[1]));
four_uCh2uLong(key + 8, &(ulKeyTmpList[2]));
four_uCh2uLong(key + 12, &(ulKeyTmpList[3]));

ulKeyList[0] = ulKeyTmpList[0] ^ TBL_SYS_PARAMS[0];
ulKeyList[1] = ulKeyTmpList[1] ^ TBL_SYS_PARAMS[1];
ulKeyList[2] = ulKeyTmpList[2] ^ TBL_SYS_PARAMS[2];
ulKeyList[3] = ulKeyTmpList[3] ^ TBL_SYS_PARAMS[3];

for (i = 0; i < 32; i++) //32次循环迭代运算
{
//5-36为32个子秘钥
ulKeyList[i + 4] = ulKeyList[i] ^ func_key(ulKeyList[i + 1] ^ ulKeyList[i + 2] ^ ulKeyList[i + 3] ^ TBL_FIX_PARAMS[i]);
}
/***********************************生成32轮32位长子秘钥结束**********************************/

for (i = 0; i < len; i++) //将输入数据存放在p缓存区
*(p + i) = *(input + i);
for (i = 0; i < 16 - len % 16; i++)//将不足16位补0凑齐16的整数倍
*(p + len + i) = 0;

for (j = 0; j < len / 16 + ((len % 16) ? 1 : 0); j++) //进行循环加密,并将加密后数据保存(可以看出此处是以16字节为一次加密,进行循环,即若16字节则进行一次,17字节补0至32字节后进行加密两次,以此类推)
{
/*开始处理加密数据*/
four_uCh2uLong(p + 16 * j, &(ulDataList[0]));
four_uCh2uLong(p + 16 * j + 4, &(ulDataList[1]));
four_uCh2uLong(p + 16 * j + 8, &(ulDataList[2]));
four_uCh2uLong(p + 16 * j + 12, &(ulDataList[3]));
//加密
for (i = 0; i < 32; i++)
{
ulDataList[i + 4] = ulDataList[i] ^ func_data(ulDataList[i + 1] ^ ulDataList[i + 2] ^ ulDataList[i + 3] ^ ulKeyList[i + 4]);
}
/*将加密后数据输出*/
uLong2four_uCh(ulDataList[35], output + 16 * j);
uLong2four_uCh(ulDataList[34], output + 16 * j + 4);
uLong2four_uCh(ulDataList[33], output + 16 * j + 8);
uLong2four_uCh(ulDataList[32], output + 16 * j + 12);
}
free(p);
}

//解密函数(与加密函数基本一致,只是秘钥使用的顺序不同,即把钥匙反着用就是解密)
//len:数据长度 key:密钥 input:输入的加密后数据 output:输出的解密后数据
void decode_fun(u8 len, u8* key, u8* input, u8* output)
{
int i = 0, j = 0;
u32 ulKeyTmpList[4] = { 0 };//存储密钥的u32数据
u32 ulKeyList[36] = { 0 }; //用于密钥扩展算法与系统参数FK运算后的结果存储
u32 ulDataList[36] = { 0 }; //用于存放加密数据

/*开始生成子秘钥*/
four_uCh2uLong(key, &(ulKeyTmpList[0]));
four_uCh2uLong(key + 4, &(ulKeyTmpList[1]));
four_uCh2uLong(key + 8, &(ulKeyTmpList[2]));
four_uCh2uLong(key + 12, &(ulKeyTmpList[3]));

ulKeyList[0] = ulKeyTmpList[0] ^ TBL_SYS_PARAMS[0];
ulKeyList[1] = ulKeyTmpList[1] ^ TBL_SYS_PARAMS[1];
ulKeyList[2] = ulKeyTmpList[2] ^ TBL_SYS_PARAMS[2];
ulKeyList[3] = ulKeyTmpList[3] ^ TBL_SYS_PARAMS[3];

for (i = 0; i < 32; i++) //32次循环迭代运算
{
//5-36为32个子秘钥
ulKeyList[i + 4] = uKleyList[i] ^ func_key(ulKeyList[i + 1] ^ ulKeyList[i + 2] ^ ulKeyList[i + 3] ^ TBL_FIX_PARAMS[i]);
}
/*生成32轮32位长子秘钥结束*/

for (j = 0; j < len / 16; j++) //进行循环加密,并将加密后数据保存
{
/*开始处理解密数据*/
four_uCh2uLong(input + 16 * j, &(ulDataList[0]));
four_uCh2uLong(input + 16 * j + 4, &(ulDataList[1]));
four_uCh2uLong(input + 16 * j + 8, &(ulDataList[2]));
four_uCh2uLong(input + 16 * j + 12, &(ulDataList[3]));

//解密
for (i = 0; i < 32; i++)
{
ulDataList[i + 4] = ulDataList[i] ^ func_data(ulDataList[i + 1] ^ ulDataList[i + 2] ^ ulDataList[i + 3] ^ ulKeyList[35 - i]);//与加密唯一不同的就是轮密钥的使用顺序
}
/*将解密后数据输出*/
uLong2four_uCh(ulDataList[35], output + 16 * j);
uLong2four_uCh(ulDataList[34], output + 16 * j + 4);
uLong2four_uCh(ulDataList[33], output + 16 * j + 8);
uLong2four_uCh(ulDataList[32], output + 16 * j + 12);
}
}

//无符号字符数组转16进制打印
void print_hex(u8* data, int len)
{
int i = 0;
char alTmp[16] = { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' };
for (i = 0; i < len; i++)
{
printf("%c", alTmp[data[i] / 16]);
printf("%c", alTmp[data[i] % 16]);
putchar(' ');
}
putchar('\n');
}
/*在主函数中实现任意字节加密与解密,并且结果正确*/
int main(void)
{
u8 i, len;
u8 encode_Result[50] = { 0 }; //定义加密输出缓存区
u8 decode_Result[50] = { 0 }; //定义解密输出缓存区
u8 key[16] = { 0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10 }; //定义16字节的密钥
//u8 Data_plain[18] = { 0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10,0x01,0x23 };//定义18字节的原始输入数据(测试用)
//u8 Data_plain[32] = { 0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10,0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10 };//定义32字节的原始输入数据(测试用)
u8 Data_plain[16] = { 0x01,0x23,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };//定义16字节的原始输入数据(测试用)
len = 16 * (sizeof(Data_plain) / 16) + 16 * ((sizeof(Data_plain) % 16) ? 1 : 0);//得到扩充后的字节数(解密函数会用到)

decode_fun(len, key, Data_plain, decode_Result); //数据解密
printf("解密后数据是:\n");
for (i = 0; i < len; i++)
printf("%x ", *(decode_Result + i));

system("pause");
return 0;
}

Blowfish

Blowfish是一个对称加密块算法,由Bruce Schneider于1993年设计,现已应用在多种加密产品。Blowfish能保证很好的加密速度,并且目前为止没有发现有效地破解方法。目前为止AES比Blowfish有更广的知名度。

BlowFish参考:https://github.com/Rupan/blowfish/blob/master/blowfish.c