国密 SM4 对称加密:原理与 Python 加解密实战

张 张站长 · 2026-09-14 15:16 · 技术-逆向 · 43 次浏览 · 0 条评论

从零跑通:一段代码加密再解密看到往返一致,讲清 ECB/CBC 与 key/iv/编码四个坑。

做接口时经常遇到:请求体或响应体不是明文 JSON,而是一串看不懂的 base64/十六进制,前端用一段 JS 加解密。国内很多系统用的是国密 SM4。这篇把 SM4 讲清楚,并写成从零跑通的教程——一段完整代码,加密再解密还原,你能亲眼看到"往返一致"。

SM4 是什么

SM4 是中国国家密码标准(GB/T 32907)里的对称分组加密算法。三个词拆开说:

  • 对称:加密解密用同一把密钥。和 RSA 那种"公钥加密、私钥解密"不一样,你拿到密钥就能同时加解密。
  • 分组:一次只处理固定长度一块数据。SM4 的分组和密钥都是 16 字节。数据长了切成一块块,不够 16 字节要补齐(padding)。
  • 可以理解成"国产 AES"——用法、分组大小、思路都和 AES 几乎一样,只是内部算法是国密自己的一套。

对逆向最重要的一句:SM4 是标准算法,只要拿到那把 16 字节密钥(CBC 模式还要 16 字节 IV),加解密就是调库的事。 难点从来不是算法,而是"密钥藏在哪"。

第一步:装环境

pip install gmssl

第二步:完整代码,直接复制

新建 demo_sm4.py,整段贴进去。它把一段 JSON 加密成 base64,再解密回来,最后比对是否一致:

# demo_sm4.py   依赖:pip install gmssl
import base64
from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT

KEY = "1234567890abcdef"          # 16 字节密钥(演示用;真实场景 key 来自抓包/接口)

def sm4_ecb_encrypt(key, text):
    c = CryptSM4(); c.set_key(key.encode(), SM4_ENCRYPT)
    return base64.b64encode(c.crypt_ecb(text.encode())).decode()   # 密文常用 base64 装

def sm4_ecb_decrypt(key, b64):
    c = CryptSM4(); c.set_key(key.encode(), SM4_DECRYPT)
    return c.crypt_ecb(base64.b64decode(b64)).decode()             # 先还原字节再解密

if __name__ == "__main__":
    plain = '{"user":"zhang","code":1000}'
    enc = sm4_ecb_encrypt(KEY, plain)
    dec = sm4_ecb_decrypt(KEY, enc)
    print("原文  :", plain)
    print("加密后:", enc)
    print("解密后:", dec)
    print("往返一致:", plain == dec)

第三步:运行,看结果

python demo_sm4.py

输出:

原文  : {"user":"zhang","code":1000}
加密后: Hp7/NNpolA7qJzIoIE6jr7gPqI3NVnS7NNH9P6WsfUs=
解密后: {"user":"zhang","code":1000}
往返一致: True

往返一致: True 说明加密解密对上了。真实场景里,接口返回的那串密文,就用 sm4_ecb_decrypt 加上正确的 key 解出明文 JSON;要构造加密请求体,就用 sm4_ecb_encrypt。

两种模式:ECB 和 CBC

数据切成好几块后,"块与块之间怎么处理"有不同模式,最常见 ECB 和 CBC:

SM4 的 ECB 与 CBC 模式

  • ECB(电子密码本):每块各自独立加密。最简单,只需一把密钥。缺点是相同明文块会加出相同密文块,安全性差,但很多系统图省事就用它(上面代码就是 ECB)。
  • CBC(密码块链接):每块加密前先和上一块密文异或,第一块和一个 16 字节的 IV(初始向量)异或。相同明文也能加出不同密文,更安全。用 CBC 需要两样:密钥 + IV。

CBC 的解密只比 ECB 多传一个 IV:

def sm4_cbc_decrypt(key, iv, b64):
    c = CryptSM4(); c.set_key(key.encode(), SM4_DECRYPT)
    return c.crypt_cbc(iv.encode(), base64.b64decode(b64)).decode()   # 多一个 iv

逆向时先判断模式:JS 里加密只传一个 key,多半是 ECB;还传了 iv,就是 CBC。

逆向时的四个坑

① key/iv 的编码方式要对上。 密钥可能是普通字符串(.encode() 得 16 字节),也可能是 16 进制字符串(32 个十六进制字符,要 bytes.fromhex()),还可能是 base64。看 JS 里怎么把 key 传进加密函数,用一样的方式转字节。转错了解出来就是乱码。

key_bytes = bytes.fromhex("0123...")       # JS 里 key 是 16 进制
key_bytes = "abcdef0123456789".encode()    # JS 里 key 是普通 16 位字符串

② 密文载体是 base64 还是 hex,别搞混。 base64 含大小写字母数字和 +/=,用 base64.b64decode;纯十六进制只有 0-9a-f,用 bytes.fromhex。用错第一步就报错。

③ padding。 分组 16 字节,不够要补,最常见是 PKCS7,gmssl 默认按这个处理。如果解出来的明文尾部有几个多余字节(像 \x05\x05\x05\x05\x05),是 padding 没去掉,说明对方补齐方式不同,得手动去。

④ 密钥到底在哪。 这才是真正花时间的地方。key/iv 可能写死在 JS 里,也可能登录后服务端下发、每次会话都变。写死的直接抠出来用;动态的要顺着 JS 找到"服务端把 key 发给前端"的那个接口,先拿到 key 再解密。

总结一句:遇到 SM4 不要慌,它就是国产 AES,标准算法、gmssl 三行搞定。真正要下功夫的是三件事——哪种模式、key/iv 什么编码、密钥从哪来。这三样对上,再看不懂的加密报文也能还原成明文。

评论 0

还没有评论
← 全部文章

找站长

站长微信二维码 扫码加微信
售后群二维码 扫码进售后群
站长收款码 建站不易,感谢打赏
微信 18581469390
本站不支持自助充值;需要充值或开通额度,请直接联系站长。
加微信可以找我重置密码、充值、问工具怎么用