<?php namespace app\service\util; use app\exception\LogicException; use app\util\sm\cmb\Sm; use app\util\StringUtil; class CmbLifeUtils { protected static string $cmbLifeProtocolPrefix = 'cmblife://'; /** * 生成掌上生活协议,带有签名 * @param string $funcName * @param array $params * @return string * @throws LogicException */ public static function genProtocol(string $funcName, array $params): string { if (empty($funcName)) { throw new LogicException('funcName不能为空'); } // 处理公共参数 $cmbConfig = config('cmb'); $params['mid'] = $cmbConfig['mid']; $params['aid'] = $cmbConfig['aid']; $params['date'] = date('YmdHis'); $params['random'] = StringUtil::generateRandomString(32);// 随机数 $params['keyAlias'] = 'CO_PUB_KEY_SM2'; $params['cmbKeyAlias'] = 'SM2_CMBLIFE'; $signBody = self::assembleProtocol($funcName, $params, false); // 不需要urlEncode $signKey = $cmbConfig['merchant_sm2_pri_key']; $sign = Sm::sign($signKey, $signBody); //sm2 $params['sign'] = $sign; return self::assembleProtocol($funcName, $params, true); } /*** * 拼接掌上生活协议 * @param String $funcName * @param array $params * @param bool $isUrlEncode * @return string */ public static function assembleProtocol(string $funcName, array $params, bool $isUrlEncode) { $prefix = self::$cmbLifeProtocolPrefix . $funcName; return self::getAssembleUrl($prefix, $params, $isUrlEncode); } /** * 拼接签名字符串 * @param String $prefix * @param String $queryString * @return String */ public static function assembleUrl(string $prefix, string $queryString): string { if (empty($prefix)) { return $queryString; } else { $char = strpos($prefix, '?') !== false ? '&' : '?'; return $prefix . $char . $queryString; } } /** * 拼接签名字符串 * @param string $prefix * @param array $params * @param bool $isUrlEncode * @return String */ public static function getAssembleUrl(string $prefix, array $params, bool $isUrlEncode): string { return self::assembleUrl($prefix, self::mapToQueryString($params, true, $isUrlEncode)); } /** * 参数转为queryString * @param array $params * @param bool $isSort 是否排序 * @param bool $isUrlEncode 是否需要urlEncode * @return string */ public static function mapToQueryString(array $params, bool $isSort, bool $isUrlEncode = true): string { if ($isSort) { ksort($params); } $queryString = ''; foreach ($params as $key => $value) { if (empty($value)) { continue; } if ($isUrlEncode) { $value = urlencode($value); } $queryString .= $key . '=' . $value . '&'; } return trim($queryString, '&'); } /** * 加密 * @param string $encryptBody * @param string $encryptKey 加密使用的key SM2公钥 * @return string * @throws LogicException */ public static function encrypt(string $encryptBody, string $encryptKey) { if (empty($encryptBody)) { throw new LogicException('报文不能为空'); } if (empty($encryptKey)) { throw new LogicException('公钥不能为空'); } return Sm::sm4Encrypt($encryptKey, $encryptBody); } /** * 解密 * @param string $encryptBody * @param string $decryptKey 解密使用的Key SM2私钥 * @throws LogicException */ public static function decrypt(string $encryptBody, string $decryptKey) { if (empty($encryptBody)) { throw new LogicException('报文不能为空!'); } if (empty($decryptKey)) { throw new LogicException('秘钥不能为空!'); } return json_decode(Sm::sm4decrypt($decryptKey, $encryptBody), true); } /** * 签名 * @param string $signBody * @param string $signKey * @param string $algorithmEnum * @return string * @throws LogicException */ public static function sign(string $signBody, string $signKey) { if (empty($signBody)) { throw new LogicException('待签名数据不能为空!'); } if (empty($signKey)) { throw new LogicException('私钥不能为空'); } return Sm::sign($signKey, $signBody); } /** * 验签,国密算法SM3WithSM2 * @param string $verifyBody * @param string $sign * @param string $verifyKey sm2公钥 * @return bool * @throws LogicException */ public static function verify(string $verifyBody, string $sign, string $verifyKey) { if (empty($verifyBody)) { throw new LogicException('验签数据不能为空'); } if (empty($sign)) { throw new LogicException("签名不能为空"); } if (empty($verifyKey)) { throw new LogicException("公钥不能为空"); } return Sm::verify($verifyKey, $sign, $verifyBody); } /** * 对请求签名 * @param string $funcName * @param array $params * @param string $signKey sm2 私钥 * @param string $signAlgorithm SM3WithSM2 * @return string */ public static function signForRequest(string $funcName, array $params, string $signKey, string $signAlgorithm = 'SM3WithSM2') { if (!empty($funcName) && !strpos($funcName, '.json')) { $funcName = $funcName . '.json'; } $assembleUrl = self::getAssembleUrl($funcName, $params, false); return self::sign($assembleUrl, $signKey); } /** * 对响应签名 * @param array $params * @return string * @throws LogicException */ public static function signForResponse(array $params) { if (empty($params)) { throw new LogicException('参数不能为空'); } $cmbConfig = config('cmb'); $signKey = $cmbConfig['merchant_sm2_pri_key']; $assembleUrl = self::getAssembleUrl('', $params, false); unset($params['sign']); return self::sign($assembleUrl, $signKey); } /** * 对响应验签 * @param array $data * @return bool true 验签成功 false 验签失败 * @throws LogicException */ public static function verifyForResponse(array $data): bool { if (empty($data)) { throw new LogicException('参数不能为空'); } $cmbConfig = config('cmb'); $verifyKey = $cmbConfig['merchant_sm2_pub_key']; $sign = $data['sign']; unset($data['sign']); return self::verify(self::getAssembleUrl('', $data, false), $sign, $verifyKey); } /** * @param array $params * @param string $verifyKey 验签所使用的Key,为掌上生活公钥 * @return bool * @throws LogicException */ public static function verifyForRequest(array $params, string $verifyKey): bool { if (empty($params)) { throw new LogicException('参数不能为空'); } $sign = $params['sign']; unset($params['sign']); $assembleUrl = self::getAssembleUrl('', $params, false); return self::verify($assembleUrl, $sign, $verifyKey); } }