应优先使用经认证的现成库,因自写函数易违反《gmt 0004-2012》标准,导致填充错误、常量误用或字节序偏差,使哈希值失效;生产环境必须通过国家密码管理局测试向量验证方可手写。

直接用现成库还是自己写函数?先看合规底线
国密SM3不是“能算出结果就行”的算法,它必须严格遵循《GMT 0004-2012》标准。自定义函数如果跳过消息填充规则、用错常量表、或在字节序处理上出错,生成的哈希值哪怕只差1位,下游系统(比如政务CA、银行网关)就会直接拒签。所以,除非你已通过国家密码管理局认证的测试向量验证(如SM3-KAT.txt里的100+组输入/输出),否则不建议从零手写核心压缩函数。
sm3() 函数调用时最常踩的编码坑
PHP默认字符串是字节流,但SM3要求输入按UTF-8原始字节处理,不是字符。常见错误包括:
- 直接对
$_POST['password']调用sm3(),而没确认前端是否用了UTF-8编码——若前端用GBK提交,sm3()会把乱码当原文哈希,验证永远失败 - 对中文字符串用
mb_convert_encoding($str, 'UTF-8', 'auto')后再哈希,看似稳妥,但auto可能误判为ASCII,丢失BOM或导致多字节截断 - 盐值(salt)用
uniqid()生成,它返回的是ASCII字符串,而非二进制随机字节;正确做法是bin2hex(random_bytes(16))
安全写法示例:
$raw_input = file_get_contents('php://input'); // 原始二进制流
$utf8_clean = mb_convert_encoding($raw_input, 'UTF-8', 'UTF-8'); // 强制重编码,不依赖auto
$hash = sm3($utf8_clean . $salt);
文件哈希必须用 sm3_file(),别用 file_get_contents + sm3()
大文件(比如>10MB)用file_get_contents()加载到内存再哈希,极易触发Allowed memory size exhausted错误,且无法中断或进度反馈。而sm3_file($path)内部使用fopen()+fread()分块读取,每块处理完立即释放内存,还支持流式校验。
- 路径必须是本地绝对路径,
sm3_file('http://...')会失败 - 若文件被其他进程锁定(如日志正在写入),
sm3_file()默认返回false,需配合is_readable()提前检查 - 不要对符号链接调用——某些Linux发行版下
sm3_file()会哈希链接本身,而非目标文件内容
生产环境必须加 hash_equals() 防时序攻击
用==或===比较SM3哈希值,攻击者可通过响应时间差异暴力猜解存储的哈希前缀。所有验证场景必须用hash_equals():
// 错误
if ($db_hash === sm3($input . $salt)) { ... }
// 正确
if (hash_equals($db_hash, sm3($input . $salt))) { ... }
注意:hash_equals()要求两个参数都是字符串,且长度一致。如果$db_hash来自数据库字段定义为CHAR(64)(SM3十六进制长度),而sm3()返回小写字符串,需统一大小写,否则hash_equals()可能因长度隐式截断失效。
真正难的不是写出一个能跑的函数,而是确保每次调用都落在国密标准的精确刻度上——填充字节、常量轮转、字节序、编码边界,漏掉任何一个,合规性就归零。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











