ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

PHP 实现国密 SM2/SM3/SM4(纯 PHP → OpenSSL 硬件加速)

2026/8/21 14:31:13 拓冰建站 浏览量
PHP 实现国密 SM2/SM3/SM4(纯 PHP → OpenSSL 硬件加速) 第 6 章 PHP 实现国密 SM2/SM3/SM4纯 PHP → OpenSSL 硬件加速6.1 为什么必须用国密等保合规的硬性要求大白话2024 年起所有过等保三级的政务/金融系统密码算法必须全部替换为国密不允许用 MD5、SHA-1、RSA、AESSM2替代 RSA非对称加密 数字签名SM3替代 SHA-256摘要/哈希SM4替代 AES对称加密测评时会抓包检查 HTTPS 握手是否用了 SM2 证书、数据库里的密码字段是否用 SM3 存储、API 签名是否用 SM2。只要有一处用了 RSA/AES直接不通过。6.2 国密算法的三种实现方式对比方式实现性能适用场景纯 PHP用 PHP 代码实现算法如 gmssl-php 库慢 100 倍开发测试、快速验证OpenSSL 扩展PHP 8.1 的 openssl 扩展支持国密编译时要加 GmSSL中等软件实现生产环境主流方案硬件加速卡通过 PKCS#11 或厂商 SDK 调硬件如三未信安、卫士通快 1000 倍金融、军工高安全场景下面从简单到复杂依次讲解。6.3 方式 1纯 PHP 实现gmssl-php 库适合「本地开发时快速测试国密接口是否通」不能用于生产性能太差。6.3.1 安装cd/var/wwwcomposerrequire lizhichao/one-sm6.3.2 SM3 散列替代 MD5/SHA-256?phprequirevendor/autoload.php;useOneSm\Sm3;// 对密码做 SM3 哈希替代 password_hash$password123456;$hashSm3::hash($password);echoSM3 哈希:$hash\n;// 输出207cf410532f92a47dee245ce9b11ff71f578ebd763eb3bbea44ebd043d018fb// 验证密码$input123456;if(Sm3::hash($input)$hash){echo密码正确\n;}// 带盐值更安全$saltbin2hex(random_bytes(16));$salted_hashSm3::hash($password.$salt);echo盐值:$salt\n;echo加盐后哈希:$salted_hash\n;6.3.3 SM4 对称加密替代 AES?phpuseOneSm\Sm4;$key0123456789abcdef;// 16 字节密钥128 bit$plaintext敏感数据身份证号 123456789012345678;// 加密ECB 模式仅作演示生产用 CBC$ciphertextSm4::encrypt($plaintext,$key);echo密文hex: .bin2hex($ciphertext).\n;// 解密$decryptedSm4::decrypt($ciphertext,$key);echo明文:$decrypted\n;// 生产环境推荐 CBC 模式需要 IV$ivrandom_bytes(16);$ciphertext_cbcSm4::encryptCBC($plaintext,$key,$iv);echoCBC 密文: .base64_encode($ciphertext_cbc).\n;$decrypted_cbcSm4::decryptCBC($ciphertext_cbc,$key,$iv);echoCBC 解密:$decrypted_cbc\n;6.3.4 SM2 非对称加密 签名替代 RSA?phpuseOneSm\Sm2;// 生成密钥对公钥 私钥$keypairSm2::generateKeyPair();$publicKey$keypair[publicKey];// 压缩格式130 字节$privateKey$keypair[privateKey];// 64 字节echo公钥:$publicKey\n;echo私钥:$privateKey\n;// 场景 1数字签名替代 RSA 签名$messageuserId123amount1000timestamp1640000000;// 签名$signatureSm2::sign($message,$privateKey);echo签名:$signature\n;// 验签$isValidSm2::verify($message,$signature,$publicKey);echo验签结果: .($isValid?通过:失败).\n;// 场景 2加密公钥加密私钥解密$plaintext银行卡号 6222 0000 1234 5678;// 用公钥加密$ciphertextSm2::encrypt($plaintext,$publicKey);echo加密: .base64_encode($ciphertext).\n;// 用私钥解密$decryptedSm2::decrypt($ciphertext,$privateKey);echo解密:$decrypted\n;性能测试纯 PHP 实现下SM2 签名 1 万次需要30 秒x86/50 秒ARM64而 OpenSSL 硬件加速只需0.3 秒。6.4 方式 2OpenSSL 扩展 GmSSL生产推荐6.4.1 编译支持国密的 OpenSSLGmSSL 3.1.1PHP 自带的 OpenSSL 扩展默认不支持国密需要重新编译一个支持 SM 算法的 OpenSSL 库GmSSL 是北京大学开源的国密实现。# 1. 下载 GmSSL 3.1.1兼容 OpenSSL 3.x APIcd/usr/local/srcwgethttps://github.com/guanzhi/GmSSL/archive/refs/tags/v3.1.1.tar.gz-Ogmssl-3.1.1.tar.gztar-zxfgmssl-3.1.1.tar.gzcdGmSSL-3.1.1# 2. 编译安装到 /usr/local/gmssl不覆盖系统自带的 OpenSSLmkdirbuildcdbuild cmake..-DCMAKE_INSTALL_PREFIX/usr/local/gmssl\-DENABLE_SM2ON-DENABLE_SM3ON-DENABLE_SM4ON\-DENABLE_SM9OFF# SM9 是国密标识密码一般用不上make-j$(nproc)sudomakeinstall# 3. 验证 GmSSL 是否支持国密/usr/local/gmssl/bin/gmssl version# 期望输出GmSSL 3.1.1/usr/local/gmssl/bin/gmssl list -digest-algorithms|grepSM3# 期望输出SM3/usr/local/gmssl/bin/gmssl list -cipher-algorithms|grepSM4# 期望输出SM4-CBC, SM4-ECB, SM4-CTR, SM4-GCM6.4.2 重新编译 PHP链接到 GmSSLcd/usr/local/src/php-8.3.10# 清理之前的编译结果makeclean# 配置时指定 GmSSL 路径./configure\--prefix/usr/local/php8\--with-config-file-path/usr/local/php8/etc\--with-openssl/usr/local/gmssl\--with-openssl-dir/usr/local/gmssl\--enable-fpm --with-fpm-systemd\--with-fpm-userwww --with-fpm-groupwww\--enable-mysqlnd --with-pdo-mysqlmysqlnd --with-mysqlimysqlnd\--with-curl --with-zlib\--enable-gd --with-jpeg --with-freetype\--enable-mbstring --enable-bcmath\--enable-opcache --enable-pcntl --enable-sockets\--with-zip --enable-soap --with-readline# 编译安装make-j$(nproc)sudomakeinstall# 验证 PHP 的 OpenSSL 扩展是否链接到 GmSSL/usr/local/php8/bin/php-i|grepOpenSSL Library Version# 期望输出OpenSSL Library Version GmSSL 3.1.1如果输出还是OpenSSL 1.1.1或3.0.x说明 configure 没识别到 GmSSL检查--with-openssl路径是否正确。6.4.3 PHP 调用 GmSSL 的国密函数编译完成后PHP 的openssl_*函数就能用国密算法了函数名不变只是算法参数改了。SM3 摘要?php// 替代 hash(sha256, $data)$dataHello GmSSL;$sm3_hashhash(sm3,$data);echoSM3:$sm3_hash\n;// 验证支持的哈希算法print_r(hash_algos());// 期望输出里有sm3SM4 对称加密?php$plaintext敏感数据银行卡号 6222000012345678;$keyrandom_bytes(16);// SM4 密钥长度 128 bit$ivrandom_bytes(16);// 加密CBC 模式$ciphertextopenssl_encrypt($plaintext,sm4-cbc,// 算法名$key,OPENSSL_RAW_DATA,$iv);echo密文: .base64_encode($ciphertext).\n;// 解密$decryptedopenssl_decrypt($ciphertext,sm4-cbc,$key,OPENSSL_RAW_DATA,$iv);echo明文:$decrypted\n;// GCM 模式带认证更安全$tag;$ciphertext_gcmopenssl_encrypt($plaintext,sm4-gcm,$key,OPENSSL_RAW_DATA,$iv,$tag// 输出参数认证标签);$decrypted_gcmopenssl_decrypt($ciphertext_gcm,sm4-gcm,$key,OPENSSL_RAW_DATA,$iv,$tag);echoGCM 解密:$decrypted_gcm\n;SM2 密钥对生成?php// 生成 SM2 密钥对椭圆曲线 sm2p256v1$config[private_key_typeOPENSSL_KEYTYPE_EC,curve_namesm2,// 国密椭圆曲线];$keypairopenssl_pkey_new($config);if(!$keypair){die(密钥生成失败: .openssl_error_string());}// 导出私钥openssl_pkey_export($keypair,$privateKey);echo私钥:\n$privateKey\n;// 导出公钥$publicKeyDetailsopenssl_pkey_get_details($keypair);$publicKey$publicKeyDetails[key];echo公钥:\n$publicKey\n;// 保存到文件file_put_contents(/tmp/sm2_private.pem,$privateKey);file_put_contents(/tmp/sm2_public.pem,$publicKey);SM2 签名与验签?php$datauserId123amount5000timestamp.time();// 加载私钥$privateKeyopenssl_pkey_get_private(file:///tmp/sm2_private.pem);// 签名使用 SM3 作为摘要算法openssl_sign($data,$signature,$privateKey,sm3);echo签名: .base64_encode($signature).\n;// 加载公钥$publicKeyopenssl_pkey_get_public(file:///tmp/sm2_public.pem);// 验签$isValidopenssl_verify($data,$signature,$publicKey,sm3);echo验签结果: .($isValid1?通过:失败).\n;SM2 加密与解密公钥加密私钥解密?php$plaintext银行卡密码 123456;// 用公钥加密$publicKeyopenssl_pkey_get_public(file:///tmp/sm2_public.pem);openssl_public_encrypt($plaintext,$ciphertext,$publicKey,OPENSSL_PKCS1_OAEP_PADDING);echo密文: .base64_encode($ciphertext).\n;// 用私钥解密$privateKeyopenssl_pkey_get_private(file:///tmp/sm2_private.pem);openssl_private_decrypt($ciphertext,$decrypted,$privateKey,OPENSSL_PKCS1_OAEP_PADDING);echo明文:$decrypted\n;6.5 HTTPS 改造SM2 证书 Nginx 配置等保测评会检查 Web 服务是否用了国密 HTTPSTLS 1.3 SM2 证书 SM4 加密套件。6.5.1 生成 SM2 自签名证书测试用cd/etc/ssl/private# 生成 SM2 私钥/usr/local/gmssl/bin/gmssl ecparam-genkey-namesm2-outsm2_server.key# 生成证书签名请求CSR/usr/local/gmssl/bin/gmssl req-new-keysm2_server.key-outsm2_server.csr\-subj/CCN/STBeijing/LBeijing/OMyOrg/OUIT/CNexample.gov.cn# 自签名证书有效期 10 年/usr/local/gmssl/bin/gmssl x509-req-days3650\-insm2_server.csr-signkeysm2_server.key-outsm2_server.crt# 查看证书内容/usr/local/gmssl/bin/gmssl x509-insm2_server.crt-text-noout|grepPublic Key Algorithm# 期望输出Public Key Algorithm: id-ecPublicKey (SM2)生产环境要去国密 CA 机构如 CFCA、上海 CA申请正式证书。6.5.2 Nginx 配置国密 HTTPSNginx 官方版本不支持国密要用国密改造版如 BabaSSL 或 Tengine。这里用BabaSSL Nginx# 1. 编译 BabaSSL支持国密的 OpenSSL fork阿里开源cd/usr/local/srcgitclone https://github.com/BabaSSL/BabaSSL.gitcdBabaSSL ./config--prefix/usr/local/babassl\enable-ntls enable-sm2 enable-sm3 enable-sm4make-j$(nproc)sudomakeinstall# 2. 下载 Nginx 源码cd/usr/local/srcwgethttp://nginx.org/download/nginx-1.24.0.tar.gztar-zxfnginx-1.24.0.tar.gzcdnginx-1.24.0# 3. 编译 Nginx链接到 BabaSSL./configure--prefix/usr/local/nginx\--with-http_ssl_module\--with-http_v2_module\--with-openssl/usr/local/src/BabaSSL\--with-openssl-optenable-ntls enable-sm2 enable-sm3 enable-sm4make-j$(nproc)sudomakeinstall# 4. 配置 Nginx 使用 SM2 证书sudotee/usr/local/nginx/conf/nginx.conf/dev/nullEOF user www; worker_processes auto; events { worker_connections 8192; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 443 ssl http2; server_name example.gov.cn; # 国密证书SM2 ssl_certificate /etc/ssl/private/sm2_server.crt; ssl_certificate_key /etc/ssl/private/sm2_server.key; # 强制使用国密加密套件 ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECC-SM4-SM3:ECDHE-SM4-SM3:ECDHE-SM4-GCM-SM3:HIGH:!aNULL:!MD5:!RC4; ssl_prefer_server_ciphers on; location / { root /var/www/html; index index.php index.html; } location ~ \.php$ { fastcgi_pass unix:/var/run/php-fpm.sock; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } } } EOF# 5. 启动 Nginxsudo/usr/local/nginx/sbin/nginx6.5.3 测试国密 HTTPS# 用 GmSSL 的 s_client 测试openssl s_client 不认 SM2/usr/local/gmssl/bin/gmssl s_client-connectexample.gov.cn:443-cipherECC-SM4-SM3# 期望输出包含Cipher: ECC-SM4-SM3浏览器测试用奇安信可信浏览器或红莲花浏览器国产支持国密的浏览器访问https://example.gov.cn点击地址栏锁图标查看证书应显示SM2。6.6 方式 3硬件加密卡金融级政务/金融核心系统要求密钥不能以明文形式存在于内存中必须用硬件加密卡如三未信安的 SJK1861、卫士通的 SJJ1528。6.6.1 加密卡工作原理[PHP 应用] ↓ 调用厂商 SDK如 libswsds.so [加密卡驱动] ↓ 通过 PCIe 总线 [硬件加密卡] → 内置 SM2/SM3/SM4 芯片 密钥存储不可导出优点签名速度快每秒 1 万次以上、密钥安全物理隔离。缺点贵一张卡 2-5 万、驱动只支持特定 OS 版本要问厂商是否支持麒麟 V10 ARM64。6.6.2 PHP 调用示例三未信安 SDK厂商会提供 C 语言的.so库和 PHP FFI 封装典型用法?php// 加载厂商 SDK通过 FFI$ffiFFI::cdef( int SDF_OpenDevice(void **phDeviceHandle); int SDF_OpenSession(void *hDeviceHandle, void **phSessionHandle); int SDF_ExternalSign_ECC(void *hSessionHandle, unsigned int uiAlgID, unsigned char *pucPublicKey, unsigned int uiPublicKeyLen, unsigned char *pucDataInput, unsigned int uiInputLength, unsigned char *pucSignature, unsigned int *puiSignatureLength); int SDF_CloseSession(void *hSessionHandle); int SDF_CloseDevice(void *hDeviceHandle); ,/usr/lib/libswsds.so);// 打开设备$hDeviceFFI::new(void*);$ret$ffi-SDF_OpenDevice(FFI::addr($hDevice));if($ret!0){die(打开加密卡失败错误码:$ret);}// 打开会话$hSessionFFI::new(void*);$ffi-SDF_OpenSession($hDevice,FFI::addr($hSession));// SM2 签名数据在加密卡内完成私钥不出卡$datauserId123amount5000;$signatureFFI::new(unsigned char[128]);$sigLenFFI::new(unsigned int);$sigLen-cdata128;$ffi-SDF_ExternalSign_ECC($hSession,0x00020200,// SM2 算法标识厂商文档会写null,null,// 公钥可选$data,strlen($data),$signature,FFI::addr($sigLen));echo签名: .bin2hex(FFI::string($signature,$sigLen-cdata)).\n;// 关闭$ffi-SDF_CloseSession($hSession);$ffi-SDF_CloseDevice($hDevice);注意不同厂商的 SDK 接口不同上面只是示例实际要看厂商文档。