ZUC算法分析(国密学习 3/5)

注:本blog参考《祖冲之序列密码算法 第1部分:算法描述》(GB/T 33133_1-2016),源码为本人手搓(ai辅助),如有漏洞请轻点喷 XD

国家标准全文公开系统:https://openstd.samr.gov.cn

算法基本信息

ZUC算法是流密码,加密本质是 $密文 = 明文 \oplus 密钥流$,解密是$密文 \oplus 密钥流$,

可分为ZUC-128、ZUC-256


ZUC-128
密钥长度:128bit
iv长度:128bit


ZUC-256
密钥长度:256bit
iv长度:根据具体的加密保护场景,可分为:184bit(23字节)、200bit(25字节)或规范性补充中的128bit(16字节)

算法结构

由线性反馈寄存器(LFSR)、比特重组(BR)和非线性函数F组成。
如图:

LFSR

基础信息:
包括16个31比特寄存器单元变量$s_0,s_1,…,s_15$
俩种模式:初始化模式、工作模式

初始化模式

LFSR接收1个31比特字u的输入,对寄存器单元变$s_0,s_1,…,s_{15}$进行更新,计算如下:

1
2
3
4
5
6
7
8
9
MASK32 = 0xFFFFFFFF    # 模 2^32 掩码
LFSR_MOD = 0x7FFFFFFF  # 模 2^31 - 1

def LFSRWithInitialisationMode(self, u):
v = (2**15*self.s[15]+2**17*self.s[13]+2**21*self.s[10]+2**20*self.s[4]+(1+2**8)*self.s[0]) % LFSR_MOD
self.s.append((v+u) % LFSR_MOD)
if self.s[16] == 0:
self.s[16] = LFSR_MOD
self.s.pop(0)

工作模式

1
2
3
4
5
6
def LFSRWithWorkMode(self):
v = (2**15*self.s[15]+2**17*self.s[13]+2**21*self.s[10]+2**20*self.s[4]+(1+2**8)*self.s[0]) % LFSR_MOD
if v == 0:
v = LFSR_MOD
self.s.append(v)
self.s.pop(0)

比特重组 BR

输入:LFSR寄存器单元变量$s_0,s_2,s_5,s_7,s_9,s_11,s_14,s_15$
输出:4个32比特字$X_0、X_1、X_2、X_3$

计算过程:
1.$s_{15}$高16位 || $s_{14}$低16位 -> $X_0$
2.$s_{11}$低16位 || $s_{9}$高16位拼接->$X_1$
3.$s_{7}$低16位 || $s_{5}$高16位拼接->$X_2$
4.$s_{2}$低16位 || $s_{0}$高16位拼接->$X_3$
注:|| 是指左边和右边直接拼接在一起(注意不要被高位和低位混淆了,是直接拼在一起,例如:第二步中$s_{11}$取的是低16位,但是它拼接后是$X_1$的高16位)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def BitReconstruction(self):
# X0 = S15的高16位 | S14的低16位
# 0x7fff8000 是第 30-15 位,左移 1 位正好到 31-16 位
self.X[0] = ((self.s[15] >> 15) << 16) | (self.s[14] & 0xffff)

# X1 = S11的低16位 | S9的高16位
# 低16位左移16位到高处,高16位右移15位到低处
self.X[1] = ((self.s[11] & 0xffff) << 16) | (self.s[9] >> 15)

# X2 = S7的低16位 | S5的高16位
self.X[2] = ((self.s[7] & 0xffff) << 16) | (self.s[5] >> 15)

# X3 = S2的低16位 | S0的高16位
self.X[3] = ((self.s[2] & 0xffff) << 16) | (self.s[0] >> 15)

密钥装入

$d_i$ 是定义好的16个16bit常量串
1
2
3
4
5
6
7
8
9
k = [0] * 16
iv_ = [0] * 16 # k_i 和 iv_i 均为8比特字节

for i in range(16):
k[i] = key[i]
iv_[i] = iv[i]

for i in range(16):
self.s[i] = (k[i] << 23) | (d[i] << 8) | iv_[i]

S盒的定义

表A.1和表A.2里存的是S盒的具体数值,在最后的代码演示中有放具体数值,这里先看S盒的定义

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def _S(self, W):
b8 = [0] * 4
b8[0] = (W >> 24) & 0xff
b8[1] = (W >> 16) & 0xff
b8[2] = (W >> 8) & 0xff
b8[3] = W & 0xff
for i in range(4):
row = b8[i] >> 4
col = b8[i] & 0xf
if i % 2 == 0:
b8[i] = S0[row][col] # i=0,2时使用S0
else:
b8[i] = S1[row][col] # i=1,3时使用S1
return (b8[0] << 24) | (b8[1] << 16) | (b8[2] << 8) | b8[3]

非线性函数F

在加密过程中还会用到非线性函数F,函数F的定义如下:

其中:

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def rotl32(x, n):   # 循环左移函数,x为32位整数,n为左移位数
    return ((x << n) | (x >> (32 - n))) & 0xFFFFFFFF

def F(self, X0, X1, X2):
W = ((X0 ^ self.R1) + self.R2) & MASK32
W1 = (self.R1 + X1) & MASK32
W2 = (self.R2 ^ X2) & MASK32

L1_X = ((W1 & 0xffff) << 16) | (W2 >> 16)
L2_X = ((W2 & 0xffff) << 16) | (W1 >> 16)

u = self.L1(L1_X)
v = self.L2(L2_X)
self.R1 = self._S(u)
self.R2 = self._S(v)
return W

def L1(self, L1_X):
return L1_X ^ rotl32(L1_X, 2) ^ rotl32(L1_X, 10) ^ rotl32(L1_X,18) ^ rotl32(L1_X,24)

def L2(self, L2_X):
return L2_X ^ rotl32(L2_X, 8) ^ rotl32(L2_X, 14) ^ rotl32(L2_X, 22) ^ rotl32(L2_X, 30)

加密流程

  1. 将密钥装入到LFSR的寄存器单元变量$s_0,s_1,…,s_15$中,作为LFSR的初态
  2. 重复执行下述过程32次
    a. $BitReconstruction()$
    b. $W = F(X_0,X_1,X_2)$
    c. 输出32比特字W
    d. $LFSRWithInitialisationMode(W >> 1)$
    前俩步只是初始化步骤,下面开始工作步骤
  3. 执行下述三步:
    a. $BitReconstruction()$
    b. $F(X_0,X_1,X_2)$
    c. $LFSRWithWorkMode()$
  4. 重复计算L次(下面会解释这个L)下述过程:
    a. $BitReconstruction()$
    b. $Z = F(X_0,X_1,X_2)\oplus X_3$
    c. 输出32比特密钥字Z
    d. $LFSRWithWorkMode()$

这里的 L 是指需要产生的密钥流长度(以32比特字为单位)
L 表示实际加密所需的32比特密钥字的个数,每执行一次步骤4,就输出一个32比特密钥字 Z,因此循环 L 次后,共输出 L × 32 比特的密钥流
举例来说,如果需要加密一段160比特的数据,则需要 L = 5(5 × 32 = 160比特)。
L 的值由实际应用场景中的数据长度决定,不是一个固定常量。

按照ZUC标准,每次循环应输出一个32比特(4字节)的密钥字 Z,循环次数 L 应为:

1
L = ⌈明文字节数 / 4⌉

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def encrypt(self, plaintext):
ciphertext = bytearray()
L = (len(plaintext) + 3) // 4 # 向上取整

for i in range(L):
self.BitReconstruction()
W = self.F(self.X[0], self.X[1], self.X[2])
Z = W ^ self.X[3]
self.LFSRWithWorkMode()
print(f'Z{i} = {hex(Z)[2:].zfill(8)}')

# 每个Z处理4个字节
for j in range(4):
idx = i * 4 + j
if idx < len(plaintext):
ciphertext.append(plaintext[idx] ^ ((Z >> (24 - 8 * j)) & 0xFF))

return bytes(ciphertext)

解密流程

ZUC算法是流密码,也就是说,加密本质是 $密文 = 明文 \oplus 密钥流$,解密就是$密文 \oplus 密钥流$,问题就在密钥流的生成要与加密时一致,所以解密和加密流程是一致的:

1
2
def decrypt(self, ciphertext):
return self.encrypt(ciphertext)  # ZUC 的加密和解密过程相同

代码

下面只展示ZUC这个类的定义,想要包括有其他常量的请移步Baymax-Fools\ZUC.py

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
class ZUC:
def __init__(self, key, iv): # key和iv均为16字节(128比特)的输入
self.s = [0] * 16 # 线性反馈移位寄存器的16个31比特寄存器单元变量
self.X = [0] * 4 # 比特重组输出的4个32比特字

self.R1 = 0
self.R2 = 0

k = [0] * 16
iv_ = [0] * 16 # k_i 和 iv_i 均为8比特字节

for i in range(16):
k[i] = key[i]
iv_[i] = iv[i]

for i in range(16):
self.s[i] = (k[i] << 23) | (d[i] << 8) | iv_[i]

for _ in range(32):
self.BitReconstruction()
W = self.F(self.X[0], self.X[1], self.X[2])
self.LFSRWithInitialisationMode(W >> 1) # 用 W 的高 31 位

self.BitReconstruction()
self.F(self.X[0], self.X[1], self.X[2])
self.LFSRWithWorkMode()

def LFSRWithInitialisationMode(self, u):
v = (2**15*self.s[15]+2**17*self.s[13]+2**21*self.s[10]+2**20*self.s[4]+(1+2**8)*self.s[0]) % LFSR_MOD
self.s.append((v+u) % LFSR_MOD)
if self.s[16] == 0:
self.s[16] = LFSR_MOD
self.s.pop(0)

def LFSRWithWorkMode(self):
v = (2**15*self.s[15]+2**17*self.s[13]+2**21*self.s[10]+2**20*self.s[4]+(1+2**8)*self.s[0]) % LFSR_MOD
if v == 0:
v = LFSR_MOD
self.s.append(v)
self.s.pop(0)

def BitReconstruction(self):
# X0 = S15的高16位 | S14的低16位
# 0x7fff8000 是第 30-15 位,左移 1 位正好到 31-16 位
self.X[0] = ((self.s[15] >> 15) << 16) | (self.s[14] & 0xffff)

# X1 = S11的低16位 | S9的高16位
# 低16位左移16位到高处,高16位右移15位到低处
self.X[1] = ((self.s[11] & 0xffff) << 16) | (self.s[9] >> 15)

# X2 = S7的低16位 | S5的高16位
self.X[2] = ((self.s[7] & 0xffff) << 16) | (self.s[5] >> 15)

# X3 = S2的低16位 | S0的高16位
self.X[3] = ((self.s[2] & 0xffff) << 16) | (self.s[0] >> 15)

def F(self, X0, X1, X2):
W = ((X0 ^ self.R1) + self.R2) & MASK32
W1 = (self.R1 + X1) & MASK32
W2 = (self.R2 ^ X2) & MASK32

L1_X = ((W1 & 0xffff) << 16) | (W2 >> 16)
L2_X = ((W2 & 0xffff) << 16) | (W1 >> 16)

u = self.L1(L1_X)
v = self.L2(L2_X)
self.R1 = self._S(u)
self.R2 = self._S(v)
return W

def _S(self, W):
b8 = [0] * 4
b8[0] = (W >> 24) & 0xff
b8[1] = (W >> 16) & 0xff
b8[2] = (W >> 8) & 0xff
b8[3] = W & 0xff
for i in range(4):
row = b8[i] >> 4
col = b8[i] & 0xf
if i % 2 == 0:
b8[i] = S0[row][col] # i=0,2时使用S0
else:
b8[i] = S1[row][col] # i=1,3时使用S1
return (b8[0] << 24) | (b8[1] << 16) | (b8[2] << 8) | b8[3]


def L1(self, L1_X):
return L1_X ^ rotl32(L1_X, 2) ^ rotl32(L1_X, 10) ^ rotl32(L1_X,18) ^ rotl32(L1_X,24)

def L2(self, L2_X):
return L2_X ^ rotl32(L2_X, 8) ^ rotl32(L2_X, 14) ^ rotl32(L2_X, 22) ^ rotl32(L2_X, 30)

def encrypt(self, plaintext):
ciphertext = bytearray()
L = (len(plaintext) + 3) // 4 # 向上取整

for i in range(L):
self.BitReconstruction()
W = self.F(self.X[0], self.X[1], self.X[2])
Z = W ^ self.X[3]
self.LFSRWithWorkMode()
print(f'Z{i} = {hex(Z)[2:].zfill(8)}')

# 每个Z处理4个字节
for j in range(4):
idx = i * 4 + j
if idx < len(plaintext):
ciphertext.append(plaintext[idx] ^ ((Z >> (24 - 8 * j)) & 0xFF))

return bytes(ciphertext)

def decrypt(self, ciphertext):
return self.encrypt(ciphertext) # ZUC 的加密和解密过程相同

ZUC算法分析(国密学习 3/5)
https://baymax-fools.github.io/2026/04/14/crypto/ZUC算法分析/
Author
Baymax
Posted on
April 14, 2026
Updated on
June 17, 2026
Licensed under