联通国密对接

AI摘要
本文分享了联通国密证书的PHP实现方案。由于无法直接读取PKCS#12文件,需通过以下步骤处理:安装phpseclib和guomi包,修改guomi源码补位返回值,实现SM2公钥验签、ASN1格式转换、SM4解密加密及签名生成功能。核心是绕过证书读取问题,直接使用明文密钥完成国密算法操作。

联通使用的证书是国密PKCS#12格式文件,但是使用openssl_pkcs12_read一直没读取出来,后面使用联通demo直接获取的明文来使用,老项目安装扩展和composer包这条路走不通
1.安装composer包,需要启用gmp扩展

composer require phpseclib/phpseclib
composer require lpilp/guomi

2.需要修改guomi源代码,返回值时补上 04

public function initEncipher($userPoint, $foreignKey = null)
{
    if (empty($foreignKey)) {
        $sm2 = new RtSm2();
        $foreignKey = $sm2->generatekey();
    }
    $foreignPriKey = $foreignKey[0];
    $foreignPubKey = $foreignKey[1];
    $this->p2 = $userPoint->mul(gmp_init($foreignPriKey, 16));
    $this->reset();
    return '04' .substr($foreignPubKey, -128);
}

3.使用公钥验签

/**
* sm2验签
* @param $signStr
* @param $sign
* @return bool
*/
public function sm2VerifySign($signStr, $sign)
{
    $publicKey = unpack("H*", base64_decode("使用java读取出来的公钥"))[1];
    $sm2 = new RtSm2('base64');
    return $sm2->verifySign($signStr,$sign,$publicKey);
}

4.解密,首先需要把待解密数据做格式转换

use phpseclib3\File\ASN1;

private function base64url_decode($data) {
    return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_BOTH));//STR_PAD_RIGHT
}
private function sm2ASN1ToC1C3C2($data)
{
    $der = ASN1::decodeBER(base64_decode($data));
    $x = "";
    $y = "";
    if (isset($der[0]["content"][0]["content"])) {
        $x = $der[0]["content"][0]["content"]->toHex();
    }
    if (isset($der[0]["content"][1]["content"])) {
        $y = $der[0]["content"][1]["content"]->toHex();
    }
    $hash = bin2hex($der[0]["content"][2]["content"] ?? "");
    $ct = bin2hex($der[0]["content"][3]["content"] ?? "");

    $x = str_pad($x, 64, "0", STR_PAD_LEFT);
    $y = str_pad($y, 64, "0", STR_PAD_LEFT);
    return $x . $y . $hash . $ct;
}

public function decrypt($reqMsg,$appkey,$type='')
{
    $appkey = $this->sm2ASN1ToC1C3C2($appkey);
    $sm2 = new RtSm2();
    $res = $sm2->doDecrypt($appkey, "解密密钥");
    if($type){
        $smk = bin2hex($res);
    }else{
        $smk = base64_encode($res);
    }
    $reqMsg = $this->base64url_decode($reqMsg);
    $data = openssl_decrypt($reqMsg, 'SM4-ECB', hex2bin($smk),OPENSSL_RAW_DATA);
    if(!$json){
        return [];
    }
    return json_decode($data,true);
}

5.加密

function base64url_encode($data) {
    return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}

$sm4Key = random_bytes(16);
//加密得到appkey
$sm2 = new RtSm2('base64');
$publicKey = unpack("H*", base64_decode("公钥"))[1];
$appRspKey = $sm2->doEncrypt($sm4Key, $publicKey);
$appKey = base64_encode(hex2bin($appRspKey));
//加密业务数据
$ciphertext = openssl_encrypt("业务数据", 'SM4-ECB', $sm4Key, OPENSSL_RAW_DATA);
$msg = base64url_encode($ciphertext);

6.生成签名

public function sm2Sign($signStr)
{
    $sm2  = new RtSm2("base64");
    $sign = $sm2->doSign($signStr, "签名私钥");
    $sign   = base64_decode($sign);
    $point  = \FG\ASN1\ASNObject::fromBinary($sign)->getChildren();
    $pointX = $this->formatHex($point[0]->getContent());
    $pointY = $this->formatHex($point[1]->getContent());
    $sign   = $pointX . $pointY;
    return base64_encode(hex2bin($sign));
}
private function formatHex($dec)
    {
        $hex = gmp_strval(gmp_init($dec, 10), 16);
        $len = strlen($hex);
        if ($len == 64) {
            return $hex;
        }
        if ($len < 64) {
            $hex = str_pad($hex, 64, "0", STR_PAD_LEFT);
        } else {
            $hex = substr($hex, $len - 64, 64);
        }
        return $hex;
    }
本作品采用《CC 协议》,转载必须注明作者和本文链接
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 10

最近正好在用,请问formatHex函数是如何实现的~

2周前 评论
Angle (楼主) 2周前
Imuyu (作者) 2周前
Angle (楼主) 2周前
Imuyu (作者) 1周前
Angle (楼主) 1周前

直接用JAVA搭一个,AI时代,不要纠结与国密这中PHP难搞的东西,别人对接方JAR包都是祖传留下来的,没人说的清楚里面的过程

2周前 评论
aszx0413 2周前
Imuyu 2周前

java使用的hutool-crypto

SM2 sm2 = SmUtil.sm2();
PublicKey publicKey = sm2.getPublicKey();
// X.509
String publicKeyStr = Base64.encode(publicKey.getEncoded());
PrivateKey privateKey = sm2.getPrivateKey();
// PKCS#8
String privateKeyStr = Base64.encode(privateKey.getEncoded())

生成的私钥,使用sm2签名,java无法验证


String msgSm3Hash = SmUtil.sm3(msg);
SM2 sm2 = SmUtil.sm2(Base64.decode(sm2_priv_key), Base64.decode(sm2_pub_key));
String msgSm3HashSm2sign = sm2.signHex(msgSm3Hash);

sm2 = SmUtil.sm2(null, Base64.decode(sm2_pub_key));
boolean verify = sm2.verifyHex(msgSm3Hash, msgSm3HashSm2sign);
6天前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!