一、简介

国密算法是由国家密码管理局认定的自主可控国产密码算法体系,包含SM系列及祖冲之(ZUC)算法等核心技术。该体系涵盖对称加密(SM1、SM4、SM7)、非对称加密(SM2、SM9)、哈希算法(SM3)与流密码(ZUC)等类型,形成完整的密码技术生态。

二、核心组件

轮函数结构

SM4的轮函数设计结合了多种运算:

· 异或运算(模2加)

· 循环左移

· 非线性字节变换(S盒)

S盒变换

S盒是8位输入、8位输出的非线性置换。查询规则是:以输入的高4位为行号,低4位为列号,查找对应值。例如,输入 0xEF,查询S盒第E行、第F列得到输出值 0x84。

密钥扩展算法

SM4的子密钥生成算法与加密算法结构类似,通过初始密钥生成32个轮密钥。

三、加密与解密过程

加密

SM4采用对合运算设计,即解密算法与加密算法相同,这大大简化了实现。它的加密过程如下:
110803f0cf3356cddb80820c60841a72.png

  1. 分组:128位明文分为4个32位的字。
  2. 经过32轮加密变换。每一轮的加密变换为:

(a)循环左移

(b)最后一个字经过轮函数F得到

(c)32轮的加密变换结束后,将4个字反序变化后,得到128位密文。

  1. SM4每轮结构

上一轮的数据,i=1,2…32。循环左移32位后经过加密函数(轮函数),得左移后最后32位数据,此轮输出作为下一轮迭代的输入,迭代32轮后做反序变换,得到密文。

解密

13975f88dddb6e6ed3472d71eca0c45d.png

四、工作模式

1、ECB模式(电子密码本模式)

  • 原理:每个明文块独立加密,相同明文块生成相同密文块。
  • 优缺点:实现简单、可并行处理,但安全性较低,可能泄露数据模式。
  • 加密模版
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def sm4_ecb_encrypt(plaintext, key):
"""
SM4 ECB模式加密
:param plaintext: 明文(字节串)
:param key: 128位密钥(16字节)
:return: 密文(字节串)
"""
# 第1行:检查明文长度,如果不是16字节的倍数,进行PKCS7填充
if len(plaintext) % 16 != 0:
plaintext = pkcs7_padding(plaintext)

# 第2行:将明文分成16字节的块
blocks = [plaintext[i:i+16] for i in range(0, len(plaintext), 16)]
cipher_blocks = []

# 第3-5行:对每个块独立进行SM4加密
for block in blocks:
cipher_block = sm4_encrypt_block(block, key) # 调用SM4核心加密函数
cipher_blocks.append(cipher_block)

# 第6行:将所有加密后的块连接成最终密文
return b''.join(cipher_blocks)
  • 解密模版
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def sm4_ecb_decrypt(ciphertext, key):
"""
SM4 ECB模式解密
:param ciphertext: 密文(字节串)
:param key: 128位密钥(16字节)
:return: 解密后的明文(字节串)
"""
# 第1行:检查密文长度,必须是16字节的倍数
if len(ciphertext) % 16 != 0:
raise ValueError("密文长度必须是16字节的倍数")

# 第2行:将密文分成16字节的块
blocks = [ciphertext[i:i+16] for i in range(0, len(ciphertext), 16)]
plain_blocks = []

# 第3-5行:对每个块独立进行SM4解密
for block in blocks:
plain_block = sm4_decrypt_block(block, key) # 调用SM4核心解密函数
plain_blocks.append(plain_block)

# 第6行:将所有解密后的块连接,并去除填充
decrypted_data = b''.join(plain_blocks)
return pkcs7_unpadding(decrypted_data)

gmssl库解密

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
from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT
import os

def sm4_ecb_decrypt_gmssl(ciphertext, key):
"""
使用gmssl库进行SM4 ECB模式解密
:param ciphertext: 密文(字节串)
:param key: 128位密钥(16字节)
:return: 解密后的明文(字节串)
"""
# 第1行:创建SM4加密对象
crypt_sm4 = CryptSM4()

# 第2行:设置解密密钥,指定为解密模式
crypt_sm4.set_key(key, SM4_DECRYPT)

# 第3行:执行ECB模式解密
# gmssl会自动处理PKCS7填充的去除
plaintext = crypt_sm4.crypt_ecb(ciphertext)

# 第4行:返回解密结果
return plaintext

def sm4_ecb_encrypt_gmssl(plaintext, key):
"""
对应的ECB加密函数
"""
crypt_sm4 = CryptSM4()
crypt_sm4.set_key(key, SM4_ENCRYPT)
ciphertext = crypt_sm4.crypt_ecb(plaintext)
return ciphertext

2、CBC模式(密码分组链接模式)

  • 原理:每个明文块与前一个密文块异或后再加密。首个块使用初始化向量IV。
  • 优缺点:安全性高于ECB,相同明文块加密结果不同;但加密过程无法并行。
  • 加密模版
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
def sm4_cbc_encrypt(plaintext, key, iv):
"""
SM4 CBC模式加密
:param plaintext: 明文(字节串)
:param key: 128位密钥(16字节)
:param iv: 初始化向量(16字节)
:return: 密文(字节串)
"""
# 第1行:对明文进行PKCS7填充
plaintext = pkcs7_padding(plaintext)

# 第2行:将填充后的明文分成16字节的块
blocks = [plaintext[i:i+16] for i in range(0, len(plaintext), 16)]
cipher_blocks = []

# 第3行:将IV作为第一个"前一个密文块"
prev_block = iv

# 第4-8行:CBC模式加密循环
for block in blocks:
# 第5行:当前明文块与前一个密文块异或
xored_block = bytes(a ^ b for a, b in zip(block, prev_block))
# 第6行:对异或结果进行SM4加密
cipher_block = sm4_encrypt_block(xored_block, key)
# 第7行:将加密结果添加到密文列表
cipher_blocks.append(cipher_block)
# 第8行:更新前一个块为当前密文块(用于下一轮)
prev_block = cipher_block

# 第9行:连接所有密文块
return b''.join(cipher_blocks)
  • 解密模版
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
def sm4_cbc_decrypt(ciphertext, key, iv):
"""
SM4 CBC模式解密
:param ciphertext: 密文(字节串)
:param key: 128位密钥(16字节)
:param iv: 初始化向量(16字节)
:return: 解密后的明文(字节串)
"""
# 第1行:检查密文长度,必须是16字节的倍数
if len(ciphertext) % 16 != 0:
raise ValueError("密文长度必须是16字节的倍数")

# 第2行:将密文分成16字节的块
blocks = [ciphertext[i:i+16] for i in range(0, len(ciphertext), 16)]
plain_blocks = []

# 第3行:将IV作为第一个"前一个密文块"
prev_block = iv

# 第4-9行:CBC模式解密循环
for block in blocks:
# 第5行:对当前密文块进行SM4解密
decrypted_block = sm4_decrypt_block(block, key)
# 第6行:将解密结果与前一个密文块异或
plain_block = bytes(a ^ b for a, b in zip(decrypted_block, prev_block))
# 第7行:将解密后的明文块添加到列表
plain_blocks.append(plain_block)
# 第8行:更新前一个块为当前密文块(用于下一轮)
prev_block = block

# 第10行:连接所有明文块并去除填充
decrypted_data = b''.join(plain_blocks)
return pkcs7_unpadding(decrypted_data)

gmssl库解密

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
def sm4_cbc_decrypt_gmssl(ciphertext, key, iv):
"""
使用gmssl库进行SM4 CBC模式解密
:param ciphertext: 密文(字节串)
:param key: 128位密钥(16字节)
:param iv: 初始化向量(16字节)
:return: 解密后的明文(字节串)
"""
# 第1行:创建SM4加密对象
crypt_sm4 = CryptSM4()

# 第2行:设置解密密钥
crypt_sm4.set_key(key, SM4_DECRYPT)

# 第3行:执行CBC模式解密
# 需要提供IV,库会自动处理CBC链式操作和填充去除
plaintext = crypt_sm4.crypt_cbc(iv, ciphertext)

# 第4行:返回解密结果
return plaintext

def sm4_cbc_encrypt_gmssl(plaintext, key, iv):
"""
对应的CBC加密函数
"""
crypt_sm4 = CryptSM4()
crypt_sm4.set_key(key, SM4_ENCRYPT)
ciphertext = crypt_sm4.crypt_cbc(iv, plaintext)
return ciphertext

3、CTR模式(计数器模式)

  • 原理:将分组密码转换为流密码。通过加密计数器值生成密钥流,与明文异或得到密文。
  • 优缺点:加解密可并行,适合高性能场景;无需填充。
  • 模版(CTR模式的解密与加密完全相同,dectypt encrypt)
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
def sm4_ctr_encrypt(plaintext, key, nonce):
"""
SM4 CTR模式加密(也是解密)
:param plaintext: 明文/密文(字节串)
:param key: 128位密钥(16字节)
:param nonce: 随机数(8字节)
:return: 密文/明文(字节串)
"""
# 第1行:初始化计数器为0
ctr = 0
# 第2行:创建字节数组存储结果
ciphertext = bytearray()

# 第3-11行:CTR模式处理循环
for i in range(0, len(plaintext), 16):
# 第4-5行:构造计数器块(nonce + 计数器值,大端序)
counter_block = nonce + ctr.to_bytes(8, 'big')

# 第6行:加密计数器值生成密钥流
keystream_block = sm4_encrypt_block(counter_block, key)

# 第7行:获取当前处理的数据块(可能小于16字节)
block = plaintext[i:i+16]

# 第8行:密钥流与数据块异或
encrypted_block = bytes(a ^ b for a, b in zip(block, keystream_block[:len(block)]))

# 第9行:将结果添加到输出
ciphertext.extend(encrypted_block)

# 第10行:计数器递增
ctr += 1

# 第12行:返回字节串结果
return bytes(ciphertext)

pycryptodome库解密

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
from Crypto.Cipher import SM4
from Crypto.Util import Counter

def sm4_ctr_decrypt_pycryptodome(ciphertext, key, nonce):
"""
使用pycryptodome库进行SM4 CTR模式解密
:param ciphertext: 密文(字节串)
:param key: 128位密钥(16字节)
:param nonce: 随机数(8字节)
:return: 解密后的明文(字节串)
"""
# 第1行:创建计数器对象
# 使用nonce作为前8字节,后8字节从0开始计数
ctr = Counter.new(64, prefix=nonce, initial_value=0)

# 第2行:创建SM4 CTR模式密码器
cipher = SM4.new(key, SM4.MODE_CTR, counter=ctr)

# 第3行:执行解密(CTR模式加密解密使用相同操作)
plaintext = cipher.decrypt(ciphertext)

# 第4行:返回解密结果
return plaintext

def sm4_ctr_encrypt_pycryptodome(plaintext, key, nonce):
"""
CTR模式加密函数(与解密相同)
"""
ctr = Counter.new(64, prefix=nonce, initial_value=0)
cipher = SM4.new(key, SM4.MODE_CTR, counter=ctr)
ciphertext = cipher.encrypt(plaintext)
return ciphertext

gmssl替代方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def sm4_ctr_gmssl(plaintext, key, iv):
"""
gmssl的CTR模式(实际上叫"OFB"模式但行为类似CTR)
注意:gmssl的CTR实现与标准略有不同
"""
crypt_sm4 = CryptSM4()
crypt_sm4.set_key(key, SM4_ENCRYPT)
# gmssl中CTR模式加解密使用相同函数
result = crypt_sm4.crypt_ctr(iv, plaintext)
return result

# CTR解密与加密相同
sm4_ctr_decrypt_gmssl = sm4_ctr_gmssl
sm4_ctr_encrypt_gmssl = sm4_ctr_gmssl

4、辅助函数实现

PKCS7填充函数

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
def pkcs7_padding(data):
"""PKCS7填充"""
# 第1行:计算需要填充的字节数
padding_length = 16 - (len(data) % 16)
# 第2行:生成填充字节(值为填充长度)
padding = bytes([padding_length] * padding_length)
# 第3行:返回填充后的数据
return data + padding

def pkcs7_unpadding(data):
"""PKCS7去除填充"""
# 第1行:检查数据长度
if len(data) % 16 != 0 or len(data) == 0:
raise ValueError("无效的填充数据")

# 第2行:获取最后一个字节的值(填充长度)
padding_length = data[-1]

# 第3行:验证填充长度有效性
if padding_length < 1 or padding_length > 16:
raise ValueError("无效的填充长度")

# 第4行:验证所有填充字节是否正确
if data[-padding_length:] != bytes([padding_length] * padding_length):
raise ValueError("无效的填充数据")

# 第5行:返回去除填充的数据
return data[:-padding_length]

5、总结

  1. ECB模式:简单但不安全,每个块独立加密
  2. CBC模式:更安全,需要IV,加解密过程不同
  3. CTR模式:流密码模式,加解密相同,无需填充
  4. IV/Nonce:CBC需要16字节IV,CTR需要8字节Nonce
  5. 填充:ECB和CBC需要填充,CTR不需要

五、注意

  1. IV(初始化向量)管理:CBC、CTR等模式需要IV,它应当随机生成且无需保密,但每次加密应使用不同的IV。
  2. 密钥安全:密钥需要安全存储和管理,避免硬编码在代码中。
  3. 认证加密:基本加密模式只提供机密性,如需同时验证完整性,可考虑SM4-GCM或专用认证加密算法如SMRAE。
  4. 性能优化:可通过查找表、并行处理等方式优化SM4实现。