# openssl-ext-sm2 **Repository Path**: HouseMen/openssl-ext-sm2 ## Basic Information - **Project Name**: openssl-ext-sm2 - **Description**: 基于openssl密码库编写的SM2椭圆曲线公钥密码算法PHP扩展 - **Primary Language**: C - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: https://github.com/House-Men/openssl-ext-sm2.git - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-10-25 - **Last Updated**: 2026-02-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # openssl-ext-sm2 #### 介绍 基于openssl密码库编写的SM2椭圆曲线公钥密码算法PHP扩展 特性:非对称加密 原作者git地址: #### 软件架构 zend 常规PHP扩展结构 #### 国密SM2小知识,便于你更快的适配Java包或其他语言SM2处理 - 私钥为固定32字节数据,在Java中通常为大数参数D,而公钥为固定64字节数据,由参数X(32字节)+Y(32字节)组成,在Java中通常为大数参数X和Y,但本扩展所需的公钥为65字节,即高位需填充任意1字节标识,而由本扩展生成的公钥标识固定为0x04代表未压缩点标识 - 在使用其他来源的私钥或公钥时需要注意,如果拆分成了参数D,X,Y三元素,则D=私钥,X+Y=公钥,以及遇到数据不满足私钥32字节,公钥两组32字节合计64字节时,需要高位填充0x00字节补齐或低位32字节截取处理,最后为了满足本扩展公钥65字节,高位需追加一个任意标识字节即可,标准的情况下标识应为0x04 - 私钥公钥数据在某些情况下可能被封装成ASN.1编码或X509证书编码中,皆可通过ASN.1解析工具提取实际数据,某些特殊情况可能被KDF算法加密处理,需采用对应的KDF算法解密后才能获得 - 签名值为固定64字节,由签名R值(32字节)+签名S值(32字节)组成,在Java中通常为ASN.1包装的两个大数 - 加密返回的密文与解密传入的密文皆是以 C1 C2 C3 三元素组成 - 三元素具体的排列顺序是由采用的模式决定,其中旧标准排列为 C1C2C3 而新标准为 C1C3C2 - C1为固定65字节数据,其中第1个字节为标识,解密传入时可以是任意字节并不影响解密运算,而加密固定返回标准的0x04代表未压缩点标识,剩下的64字节则为曲线算法关键参数X1(32字节)+Y1(32字节),在Java中通常为ASN.1包装的两个大数 - 某些其他实现的密文结果中,C1部分可能并不包含1字节标识,在互相适配兼容时需要注意填充对齐为本扩展C1所需的65字节数据解密或删除标识仅取C1关键的64字节数据供对方解密 - C2无固定字节数量,为实际的裸密文 - C3为固定32字节基于SM3运算的摘要数据 - 因为算法涉及到随机数因此每次相同的参数签名或加密运算结果都是不同的 - 在Java中由于签名R值和S值以及密文C1的X1值和Y1值通常为大数对象类型转换的十六进制数据,而Java大数对象的转换特性原因他的字节长度不是固定的,因此在小于32字节时需要高位0x00字节补齐,在大于32字节时需要截断仅取低位32字节数据,而要将PHP固定的32字节数据转换为Java大数格式十六进制数据可参考后面附赠的PHP函数:JavaBigIntToByteArray - 在Java中通常会将签名或密文的多个关键元素数据以ASN.1数据格式封装,因此需要适配本扩展的数据格式实现快速编码解码的互相转换可参考后面附赠的PHP函数:sm2_asn1_encode,sm2_asn1_decode - SM4对称加解密以及SM3摘要计算无需本扩展,PHP常用扩展openssl本身就支持且无特别的跨语言差异直接使用即可,本扩展仅针对复杂且标准混乱的SM2非对称处理专用 - 某些银行往往仅提供Java包接口,且开发人员也可能并不清楚内部具体数据编码以及转换等特殊处理行为,仅知道可以调用Java某些类满足功能需求,无法做出详细数据处理过程的解释,因此跨语言脱离对方Java包形式的对接往往需要逆向分析Java包源码,实在搞不定还是找个懂Java开发的包装成服务代理中间件来实现跨语言接口调用 #### 依赖要求 1,liunx :openssl/lib必须包含 libcrypto.so和libssl.so 动态库 2,mac :openssl/lib必须包含 libcrypto.dylib和libssl.dylib 动态库 3,windows :openssl/lib必须包含 libcrypto.lib和libssl.lib 动态库 如出现:: undefined symbol: BN_CTX_secure_new 或其他类似的情况,就是没有动态库,openssl 不必升级以免影响正常的openssl扩展受影响,可在新目录安装openssl 教程: 例:liunx ![liunx-openssl位置](doc/image/liunx-openssl.png) 例:mac ![mac-openssl位置](doc/image/mac-openssl.png) #### 编译安装教程 Linux命令行环境编译示例 ```bash cd openssl-ext-sm2 phpize ./configure --with-openssl=/usr/local/openssl make make install ``` Windows命令行环境编译示例 相关所需资源可在如下网址去下载 - - - ```bat cd openssl-ext-sm2 set PHPDIR=E:\OpenSource\php set PHPSDK=%PHPDIR%\php-sdk-binary-tools-php-sdk-2.3.0 set PHPSRC=%PHPDIR%\php-7.2.18-devel-VC15-x64 set PHPDEPS=%PHPSRC%\deps set PATH=%PHPSRC%;%PATH% call %PHPSDK%\phpsdk-vc15-x64 phpize configure --enable-sm2 --with-extra-includes="%PHPDEPS%\openssl-1.1.1s-74-vc15-x64\include" --with-extra-libs="%PHPDEPS%\openssl-1.1.1s-74-vc15-x64\lib" --with-prefix="%PHPDIR%\install" nmake nmake install ``` #### 使用说明 1. 创建公钥和私钥 ```php $pub_key 取地址 结果为二进制 $pri_key 取地址 结果为二进制 sm2_key_pair($pub_key, $pri_key); 返回值int 0 成功 其他状态失败 ``` 2. 根据私钥生成公钥 ```php $pri_key 私钥 二进制 $pub_key 取地址 结果为二进制 sm2_gen_pubic_key($pri_key, $pub_key); 返回值int 0 成功 其他状态失败 ``` 3. 私钥签名 ```php $msg 信息 $signature 输出签名结果 注意签名结果的左边32字节与右边32字节也被叫做签名R值与签名S值 $pri_key 私钥 二进制 $iv userid 没有设置默认为空的操作:如需为空请设置1234567812345678 $mode 没有设置默认为0标准模式,可选 0=标准模式 2=某些银行Java变种模式 sm2_sign($msg, $signature, $pri_key, $iv, $mode) 返回值int 0 成功 其他状态失败 ``` 4. 公钥验签 ```php $msg 信息 $signature 输入签名结果 注意签名结果的左边32字节与右边32字节也被叫做签名R值与签名S值 $pub_key 公钥 二进制 $iv userid 没有设置默认为空的操作:如需为空请设置1234567812345678 $mode 没有设置默认为0标准模式,可选 0=标准模式 2=某些银行Java变种模式 sm2_sign_verify($msg, $signature, $pub_key, $iv, $mode); 返回值int 0 成功 其他状态失败 ``` 5. 公钥加密 ```php $msg 信息 $encrypt 输出加密结果 二进制 $pub_key 公钥 二进制 $mode 没有设置默认为0新标准模式,可选 0=新标准/C1C3C2模式 1=旧标准/C1C2C3模式 2=某些银行Java旧标准变种模式 sm2_encrypt($msg, $encrypt, $pub_key, $mode) 返回值int 0 成功 其他状态失败 ``` 6. 私钥解密 ```php $encrypt 加密信息 二进制 $string 输出结果 明文 $pri_key 私钥 $mode 没有设置默认为0新标准模式,可选 0=新标准/C1C3C2模式 1=旧标准/C1C2C3模式 2=某些银行Java旧标准变种模式 sm2_decrypt($encrypt, $string, $pri_key, $mode) 返回值int 0 成功 其他状态失败 ``` #### 附赠PHP兼容某些Java包SM2的ASN.1数据编码关键函数 ```php //mode=0(两个INTEGER编码的签名),mode=1(2个INTEGER编码C1+1个OCTET_STRING编码C3+1个OCTET_STRING编码C2) function sm2_asn1_decode($base64_str, $mode = 0) { $bin = base64_decode($base64_str); $r_len = hexdec(bin2hex(substr($bin, 3, 1))); $r_val = substr($bin, 4, $r_len); $r_val = str_pad($r_val, 32, "\x00", STR_PAD_LEFT); $r_val = substr($r_val, -32); $s_len = hexdec(bin2hex(substr($bin, 4 + $r_len + 1, 1))); $s_val = substr($bin, 4 + $r_len + 2, $s_len); $s_val = str_pad($s_val, 32, "\x00", STR_PAD_LEFT); $s_val = substr($s_val, -32); if ($mode == 1) { $c3_len = hexdec(bin2hex(substr($bin, 4 + $r_len + 2 + $s_len + 1, 1))); $c3_val = substr($bin, 4 + $r_len + 2 + $s_len + 2, $c3_len); $c3_val = str_pad($c3_val, 32, "\x00", STR_PAD_LEFT); $c3_val = substr($c3_val, -32); $c2_len = hexdec(bin2hex(substr($bin, 4 + $r_len + 2 + $s_len + 2 + $c3_len + 1, 1))); $c2_val = substr($bin, 4 + $r_len + 2 + $s_len + 2 + $c3_len + 2, $c2_len); return "\x04" . $r_val . $s_val . $c3_val . $c2_val; } return $r_val . $s_val; } //mode=0(两个INTEGER编码的签名),mode=1(2个INTEGER编码C1+1个OCTET_STRING编码C3+1个OCTET_STRING编码C2) function sm2_asn1_encode($data, $mode = 0) { if ($mode == 0) { if (strlen($data) != 64) return ''; $r_len = 0; $r_val = JavaBigIntToByteArray(substr($data, 0, 32), $r_len); $s_len = 0; $s_val = JavaBigIntToByteArray(substr($data, 32, 32), $s_len); $len = 0x04 + $r_len + $s_len; return base64_encode("\x30" . hex2bin(dechex($len)) . "\x02" . hex2bin(dechex($r_len)) . $r_val . "\x02" . hex2bin(dechex($s_len)) . $s_val ); } if ($mode == 1) { $msg_len = strlen($data) - 97; if ($msg_len < 1) return ''; $r_len = 0; $r_val = JavaBigIntToByteArray(substr($data, 1, 32), $r_len); $s_len = 0; $s_val = JavaBigIntToByteArray(substr($data, 33, 32), $s_len); $len = 0x08 + $r_len + $s_len + $msg_len + 32; return base64_encode("\x30" . hex2bin(dechex($len)) . "\x02" . hex2bin(dechex($r_len)) . $r_val . "\x02" . hex2bin(dechex($s_len)) . $s_val . "\x04\x20" . substr($data, 65, 32) . "\x04" . hex2bin(dechex($msg_len)) . substr($data, 97) ); } return ''; } //兼容Java大数 function JavaBigIntToByteArray($bigint_bin, &$len) { $len = $raw_len = strlen($bigint_bin); for ($i = 0; $i < $len; ++$i) { $r_flag = hexdec(bin2hex($bigint_bin[$i])); if ($r_flag >= 0x80) { ++$len; $bigint_bin = "\x00" . $bigint_bin; break; } if ($r_flag == 0x00) { --$len; continue; } break; } if ($len < $raw_len) return substr($bigint_bin, -$len); return $bigint_bin; } ``` #### 演示 ```php $msg = '这是测试'; $iv = '1234567812345678'; //没有设置默认为空的操作:如需为空请设置1234567812345678 sm2_key_pair($pub_key, $pri_key); base64_encode($pub_key); base64_encode($pri_key); #公钥:BHSAPGXtrHNxqJ3/b0+eNu2mdO0mpDfTGNJUMoEWpNpSL53Dw+YM/B/QT5OoLm4xQtw0hZY5wlWTR+cD629Grek= #私钥:++BuzKd1mPa0RXAJcY6DHDq9SUzo3T6/engbKReQRqI= $pub_key = ''; sm2_gen_pubic_key($pri_key, $pub_key); base64_encode($pub_key); #公钥:BHSAPGXtrHNxqJ3/b0+eNu2mdO0mpDfTGNJUMoEWpNpSL53Dw+YM/B/QT5OoLm4xQtw0hZY5wlWTR+cD629Grek= sm2_sign($msg, $signature, $pri_key, $iv); base64_encode($signature); #私钥签名:+YHNtKkXbsRSs2nk5amd/YNqsiH8Kyr+oyLVVzuvRl+lqb40uzPxjsRo9QTYw7kZdWSfvM5lbxDMfF0cugQNfQ== sm2_sign_verify($msg, $signature, $pub_key, $iv); #公钥验签:0 sm2_encrypt($msg, $encrypt, $pub_key); base64_encode($encrypt); #公钥加密:BBdm04Uh5EgzYKG3Ff8rBFJQZxRSXnrh9/WDZxS6PmzfnTDz0O0C115BPxMDfBNnOK5Ixs9kHTJPNSDoiHoiEmrnuotKN53rxnJtNd3MTbRjJOQ0sas9Kdktl1eHzj2/eseNaGh0LHZIOrBxAQ== sm2_decrypt($encrypt, $string, $pri_key); #私钥解密:这是测试 ``` #### 性能测试 纯php代码实现国密算法: 注意事项:此sm2扩展加密结果额外增加了04开头,如两方加解密不互通请查看此处 ```asm 04c30f5ef97396dc63273cb4fc70a3a0695ad8041b8019d1f0e74236d4d014842a6f1c5318ce0aaa33d360252640b850cd6a59e4cf33477076d79948e90cf664227675aa024a904fecdffc2b5ade4af0a02365e0812c0359ae38ede53d72ebd5e7b95c2560bcb7 ``` 服务器参数 ``` 物理cpu:2个 逻辑cpu:8个 cpu核数:4个 运行内存:8G ``` 使用php框架:lumen 参与加密数据 ```asm {"request":{"body":{"ntbusmody":[{"busmod":"00001"}],"ntdumaddx1":[{"bbknbr":"75","dyanam":"招商 测试","dyanbr":"11111111111","eftdat":"20220602","inbacc":"755936020410404","ovrctl":"N","yurref":"596620626253316098"}]},"head":{"funcode":"NTDUMADD","reqid":"202206021511010000001","userid":"B000001631"}},"signature":{"sigdat":"__signature_sigdat__","sigtim":"20220602161503"}} ``` userid ```asm 1234567812345678 ``` 性能分析 关于执行次数:1000/100次是因为使用纯php服务器跑不了10000次。 ![](doc/image/bat.jpg) 使用xhorf栈跟踪分析: 1,纯PHP代码性能图 ![](doc/image/php-sm2-xhorf.png) 2,PHP-sm2扩展代码性能图 ![](doc/image/callgraph.png)