从零理解SM4算法:用Python手搓一个国密加密工具(附完整代码)
从零理解SM4算法用Python手搓一个国密加密工具附完整代码第一次接触SM4算法时我被它优雅的设计所吸引——作为我国自主研发的商用密码标准它既保持了足够的安全性又在实现上展现出令人惊叹的简洁性。本文将带你从零开始用Python逐步实现SM4的每个核心组件最终打造一个可加密文件的命令行工具。不同于单纯的理论讲解我们会通过代码实现反向理解算法设计这种手搓式学习能让你真正掌握国密算法的精髓。1. SM4算法基础认知SM4作为分组密码算法采用128位分组长度和128位密钥长度。它的核心设计哲学在于混淆与扩散的平衡——通过S盒实现非线性混淆通过线性变换实现位级扩散。有趣的是SM4的加密和解密使用相同结构仅轮密钥顺序相反这种对合运算特性大幅降低了实现复杂度。算法主要包含三大模块轮函数32轮迭代的核心运算单元密钥扩展从初始密钥生成32个轮密钥加解密流程数据分组处理框架在开始编码前我们需要理解几个关键数学运算循环左移x n表示将32位字x循环左移n位字/字节处理SM4以4字节字为基本处理单位S盒变换8位输入输出的非线性置换表提示SM4的S盒设计是其安全性的关键我们将在实现时使用标准S盒数据。2. 核心组件Python实现2.1 S盒与基础变换SM4的S盒是一个256元素的预定义置换表。我们先实现这个基础组件S_BOX [ 0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x05, # ...完整S盒数据见文末完整代码 ] def s_box_lookup(byte): S盒查表函数 return S_BOX[byte]接下来实现非线性变换τ和线性变换Ldef tau_transform(word): 4字节字的非线性τ变换 bytes_ [(word (24 - i*8)) 0xff for i in range(4)] return (s_box_lookup(bytes_[0]) 24) | (s_box_lookup(bytes_[1]) 16) | (s_box_lookup(bytes_[2]) 8) | s_box_lookup(bytes_[3]) def l_transform(word): 线性变换L return (word ^ ((word 2) | (word 30)) ^ ((word 10) | (word 22)) ^ ((word 18) | (word 14)) ^ ((word 24) | (word 8)))2.2 轮函数实现轮函数是SM4的核心运算单元其结构如下def round_function(x0, x1, x2, x3, rk): SM4轮函数实现 b x1 ^ x2 ^ x3 ^ rk t tau_transform(b) l l_transform(t) return x0 ^ l这个简洁的实现完美对应了算法公式F(X0,X1,X2,X3,rk) X0 ⊕ T(X1⊕X2⊕X3⊕rk)2.3 密钥扩展算法密钥扩展需要预定义系统参数FK [0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc] CK [ 0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269, # ...完整CK表见文末代码 ]密钥扩展实现def key_expansion(mk): 密钥扩展算法 k [mk[i] ^ FK[i] for i in range(4)] for i in range(32): # T变换与加密T变换略有不同 b k[i1] ^ k[i2] ^ k[i3] ^ CK[i] t tau_transform(b) l_prime t ^ ((t 13) | (t 19)) ^ ((t 23) | (t 9)) rk k[i] ^ l_prime k.append(rk) return k[4:36] # 返回32个轮密钥3. 完整加解密流程3.1 加密算法实现SM4加密采用32轮Feistel结构def sm4_encrypt(plaintext, mk): SM4加密主函数 rk key_expansion(mk) x list(plaintext) for i in range(32): x_next round_function(x[i], x[i1], x[i2], x[i3], rk[i]) x.append(x_next) return x[-4:][::-1] # 最后4个字逆序输出3.2 解密算法实现得益于对合特性解密仅需反转轮密钥def sm4_decrypt(ciphertext, mk): SM4解密主函数 rk key_expansion(mk)[::-1] # 轮密钥逆序 x list(ciphertext) for i in range(32): x_next round_function(x[i], x[i1], x[i2], x[i3], rk[i]) x.append(x_next) return x[-4:][::-1]4. 工程化封装与应用4.1 数据分组处理实际应用中需要处理任意长度数据def pad_data(data): PKCS#7填充 pad_len 16 - (len(data) % 16) return data bytes([pad_len] * pad_len) def unpad_data(data): 去除填充 pad_len data[-1] return data[:-pad_len]4.2 文件加密工具实现最终封装成命令行工具import argparse def main(): parser argparse.ArgumentParser(descriptionSM4文件加密工具) parser.add_argument(input, help输入文件) parser.add_argument(output, help输出文件) parser.add_argument(key, help16字节密钥(十六进制)) parser.add_argument(-d, --decrypt, actionstore_true, help解密模式) args parser.parse_args() # 密钥处理 mk [int(args.key[i*8:i*88], 16) for i in range(4)] # 文件处理 with open(args.input, rb) as f: data f.read() processed b for i in range(0, len(data), 16): block data[i:i16] if len(block) 16 and not args.decrypt: block pad_data(block) words [int.from_bytes(block[j*4:j*44], big) for j in range(4)] if args.decrypt: result sm4_decrypt(words, mk) else: result sm4_encrypt(words, mk) processed b.join([r.to_bytes(4, big) for r in result]) if args.decrypt: processed unpad_data(processed) with open(args.output, wb) as f: f.write(processed) if __name__ __main__: main()使用示例# 加密 python sm4_tool.py plain.txt encrypted.bin 0123456789abcdeffedcba9876543210 # 解密 python sm4_tool.py encrypted.bin decrypted.txt 0123456789abcdeffedcba9876543210 -d5. 算法优化与安全实践5.1 性能优化技巧通过预计算和查表可以显著提升性能# 预计算S盒的线性变换结果 S_BOX_LUT [l_transform(s 24) 24 for s in S_BOX] def optimized_round(x0, x1, x2, x3, rk): 优化版轮函数 b x1 ^ x2 ^ x3 ^ rk bytes_ [(b (24 - i*8)) 0xff for i in range(4)] s [S_BOX_LUT[bytes_[i]] (24 - i*8) for i in range(4)] l s[0] ^ s[1] ^ s[2] ^ s[3] return x0 ^ l5.2 安全使用建议密钥管理避免硬编码密钥使用密钥派生函数生成SM4密钥定期轮换密钥工作模式选择CBC模式适合文件加密GCM模式提供认证加密避免使用ECB模式侧信道防护确保实现是常数时间的避免分支和查表依赖秘密数据完整实现中最容易出错的是字节序处理——SM4标准采用大端序而现代CPU多为小端序需要特别注意转换。在实际测试中建议使用官方测试向量验证实现正确性。