
这篇文章就是专门解决这个痛点的:从PHP环境下SM4扩展的安装(或纯PHP实现的选择),到密钥、向量的正确生成,再到加密函数的编写、数据填充的细节,把每个环节都拆成“普通人能看懂”的步骤;更贴心的是,文中附了可直接复制使用的完整示例代码,注释详细到每一行的作用,就算是刚接触加密的新手,改改密钥和待加密内容就能跑通。
不管你是要保护接口传输的敏感数据,还是满足业务的加密合规要求,跟着这篇手把手操作,半小时就能搞定PHP SM4加密,再也不用为“怎么实现”发愁啦!
你有没有过这种情况?想给PHP项目里的敏感数据加SM4加密,查了一堆资料要么讲得太专业听不懂,要么代码复制过去根本跑不通,最后搞得自己头大?我去年帮电商朋友做用户手机号加密的时候,就踩过这种坑——一开始没搞懂SM4的分组填充,加密后的数据总是比原边长,找了3天bug才发现是填充方式错了;后来又因为密钥没存好,差点把用户数据泄露,现在想想都后怕。今天我把自己踩过的雷都整理出来,给你讲清楚PHP实现SM4加密的具体步骤,再给你一套可直接用的代码,你跟着做肯定能搞定。
先搞懂SM4加密的基础逻辑,避免踩我之前的坑
其实SM4就是对称加密算法里的“居家必备款”——加密和解密用同一个“钥匙”(密钥),就像你家的门锁,钥匙对了才能打开。它属于“分组加密”,意思是把数据分成16字节(128位)的小方块逐个处理,要是数据不够16字节,就得“凑数”(填充)——比如你要加密“123456”(6字节),得用PKCS#7填充成16字节(后面补10个x0A),不然加密函数会直接报错。
我去年踩的第一个大雷就是填充方式:当时帮朋友加密用户手机号,用了零填充(后面补0),结果加密后的数据解密时总是少几位,查了SM4的国家标准才知道,官方明确推荐PKCS#7填充——换成这个后,数据终于能正确解密了。还有个容易忘的点:SM4的密钥长度必须是128位(16字节),不能长也不能短,就像你买裤子得选对腰围,我之前随便写了个8字节的密钥,结果加密函数直接抛出“密钥长度错误”,折腾了半小时才反应过来。
再通俗点说,SM4的逻辑就像“装快递”:
搞懂这个逻辑,你后面写代码时就不会犯“填充错了”“密钥短了”这种低级错误了。
PHP实现SM4加密的具体步骤,我踩过的雷都帮你避开了
PHP实现SM4加密其实就两条路:装扩展或用openssl自带支持。装扩展(比如php-sm4)虽然方便,但很多服务器(尤其是共享主机)不让装,所以我更推荐用openssl——毕竟openssl 1.1.1及以上版本已经原生支持SM4了,你只要用openssl_get_cipher_methods()
函数查一下,能看到“sm4-cbc”“sm4-ctr”这些模式就没问题。
下面我把步骤拆得明明白白,每一步都告诉你“为什么要这么做”,避免你像我当初一样摸黑踩坑:
密钥是SM4的“核心钥匙”,必须用真随机函数生成——PHP里的openssl_random_pseudo_bytes(16)
就很好用,它能生成不可预测的16字节密钥,比你自己写的“1234567890abcdef”安全100倍。
还有个容易被忽略的“小钥匙”叫IV(初始化向量),它的作用是让“相同明文加密出不同结果”——比如你用同一把密钥加密“138XXXX1234”,用不同的IV加密,结果完全不一样,这样黑客就没法通过“相同密文”猜明文了。IV也得是16字节,同样用openssl_random_pseudo_bytes(16)
生成。
我之前帮朋友做的时候,一开始没在意IV,用了固定的“1234567890abcdef”当IV,结果被安全审计驳回了——审计说“固定IV等于给黑客留后门”,后来改成随机IV才通过。
openssl的openssl_encrypt
和openssl_decrypt
函数其实已经帮我们封装了SM4的逻辑,你只要填对参数就行。我把最常用的CBC模式(兼顾安全和易用)的代码写出来,每一行都加了注释,你复制过去就能跑:
// 生成16字节的随机密钥和IV(必做!)
$key = openssl_random_pseudo_bytes(16);
$iv = openssl_random_pseudo_bytes(16);
// 待加密的敏感数据(比如用户手机号、地址)
$originalData = "用户敏感信息:138XXXX1234,地址:北京市朝阳区XX路";
/
SM4加密函数(CBC模式,PKCS#7填充)
@param string $data 待加密明文
@param string $key 16字节密钥
@param string $iv 16字节IV
@return string base64编码的密文(包含IV,方便解密)
/
function sm4Encrypt($data, $key, $iv) {
$cipher = "sm4-cbc"; // 选CBC模式,安全又常用
// 先检查环境是否支持SM4-CBC(避免报错)
if (!in_array($cipher, openssl_get_cipher_methods())) {
throw new Exception("当前PHP环境不支持SM4加密,请升级openssl到1.1.1及以上版本");
}
// 加密:OPENSSL_RAW_DATA表示返回二进制数据
$encrypted = openssl_encrypt($data, $cipher, $key, OPENSSL_RAW_DATA, $iv);
// 把IV和密文拼接,再base64编码(方便存储/传输)
return base64_encode($iv . $encrypted);
}
/
SM4解密函数(对应CBC模式)
@param string $encryptedData base64编码的密文(含IV)
@param string $key 16字节密钥
@return string 解密后的明文
*/
function sm4Decrypt($encryptedData, $key) {
$cipher = "sm4-cbc";
// 先解码base64(因为加密时编码过)
$decoded = base64_decode($encryptedData);
// 从开头取出IV(前16字节)
$iv = substr($decoded, 0, 16);
// 剩下的是真正的密文
$encrypted = substr($decoded, 16);
// 解密:参数要和加密时一致
return openssl_decrypt($encrypted, $cipher, $key, OPENSSL_RAW_DATA, $iv);
}
// 测试一下:加密+解密
$encrypted = sm4Encrypt($originalData, $key, $iv);
$decrypted = sm4Decrypt($encrypted, $key);
echo "原数据:" . $originalData . "n";
echo "加密后:" . $encrypted . "n";
echo "解密后:" . $decrypted . "n";
这段代码的核心优势是把IV和密文拼接在一起了——我之前帮朋友做的时候,一开始把IV存在数据库的另一个字段里,结果数据库迁移时丢了IV,导致所有密文都解不出来,后来改成“IV+密文”的形式,再也没出过这种问题。
SM4有好几种模式,不同模式的安全性和适用场景差很多,我帮你整理了一张对比表,直接照着选就行:
模式 | 是否需要IV | 安全性 | 适用场景 |
---|---|---|---|
ECB | 不需要 | 低(相同明文加密后结果一样) | 非敏感数据,比如静态配置 |
CBC | 需要 | 中(依赖IV的随机性) | 敏感数据,比如用户手机号、地址 |
CTR | 需要(作为计数器) | 高(流加密模式,避免分组重放) | 实时数据传输,比如API接口 |
我 你优先选CBC或CTR模式,ECB真的别碰——去年有个朋友用ECB加密用户密码,结果被黑客破解了,因为相同的密码加密后结果一样,黑客用彩虹表一查就出来了。
最后再提醒你3个“保命”注意事项
openssl_random_pseudo_bytes(16)
生成——不然黑客很容易通过“相同密文”猜明文。如果你按这些步骤试了,遇到“环境不支持SM4”“解密乱码”这种问题,可以留言告诉我具体情况,我帮你看看;或者你有更顺手的实现方法,也欢迎分享,咱们一起避坑!
本文常见问题(FAQ)
PHP用openssl实现SM4加密,需要什么版本的openssl?怎么检查自己的环境支持?
一般来说,openssl 1.1.1及以上版本才原生支持SM4加密。你可以在PHP里用openssl_get_cipher_methods()函数查一下,如果能看到“sm4-cbc”“sm4-ctr”这些模式,就说明环境支持。比如直接写个php文件,echo implode(‘,’, openssl_get_cipher_methods()); 运行后看输出里有没有SM4相关的模式就行。
SM4加密时数据填充错了会怎样?怎么选正确的填充方式?
填充错了最常见的问题就是解密乱码或者加密直接报错。比如我之前帮朋友加密手机号用了零填充,结果解密后数据总是少几位。SM4是分组加密,数据要分成16字节的块,不够的就得填充,官方明确推荐用PKCS#7填充——比如数据不够16字节,后面补的字节值等于需要填充的位数(比如缺10字节就补10个x0A)。
SM4的密钥有什么要求?随便写个密钥能用吗?
SM4的密钥必须是128位(16字节),不能长也不能短。随便写个8字节的密钥肯定用不了,会直接报错“密钥长度错误”。而且密钥得用真随机函数生成,比如PHP里的openssl_random_pseudo_bytes(16),别自己写“1234567890abcdef”这种固定密钥,不然安全系数特别低,容易被破解。
SM4加密时IV是什么?必须随机吗?
IV是初始化向量,作用是让相同明文加密出不同结果,避免黑客通过相同密文猜明文。IV也得是16字节,而且必须随机——比如用openssl_random_pseudo_bytes(16)生成。别用固定的IV(比如“1234567890abcdef”),不然审计会驳回,还容易给黑客留后门。
PHP SM4加密后解密乱码,可能是什么原因?
首先检查填充方式对不对,是不是用了PKCS#7;然后看密钥是不是16字节,加密和解密的密钥是不是同一个;再看IV——如果加密时用了随机IV,解密时是不是从密文里取出了正确的IV(比如加密时把IV和密文拼接,解密时要先拆出来)。我之前帮朋友做的时候,就是因为IV没对应上,导致解密乱码,后来把IV和密文放一起才解决。