GmService.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. <?php
  2. /**
  3. * @Author: Marte
  4. * @Date: 2017-07-31 15:26:02
  5. * @Last Modified by: Marte
  6. * 1、getPostData 获取对方发过来的请求使用此方法对获取到的数据进行解密解签后得到数据
  7. * 2、runJson 返回数据给对方使用此方法对数据进行拼接加签加密后返回
  8. * 3、getToken 获取token方法,可以自行储存在文件内重复使用,过期后再次获取
  9. * 4、postJry 业务请求数据拼接,发送请求接口
  10. * 5、cityQuery 业务请求封装
  11. * 6、decryptByPublicKey rsa解密方法
  12. * 7、encryptByPrivateKey rsa加密方法
  13. * 8、decrypt aes解密方法
  14. * 9、encrypt aes加密方法
  15. */
  16. namespace crmeb\services;
  17. use function halt;
  18. use function input;
  19. use function json;
  20. class GmService
  21. {
  22. //以下参数都需要金融云提供
  23. //AES偏移量
  24. protected static $iv = 'abcdefghABCDEFGH';
  25. protected static $appID = "3f1a51c1-39d4-48e1-8119-a37b63c0ed8c";
  26. protected static $appSecretKey = "66a81e7a-dea3-4870-9c63-58712e13aa9b";
  27. protected static $tokenUrl = "https://mouldai.com/api/HOGENDY";//回归环境
  28. protected static $token;
  29. //合作机构自有公钥
  30. const PUBLIC_KEY = "DE3w4DgjV+np0oHQzSDUgvlxGKPhi/gHBRp8dtjun+z6uqrRJE6B1qswZpaSCs3tp0tm98ZjjL9RTuNh4dyUuA==";
  31. //合作机构自有私钥
  32. const PRIVATE_KEY = "778NKKZgdS9IGm/crvajNPoq5CHJNEKXptciF/1SU3I=";
  33. //泰隆银行公钥
  34. const CGB_PUBLIC_KEY = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE6mJz31IQpqtv42a67pfUe6q6UKUa/Lxf2rzJC4iAK0p3dOooG/d+N2fs6qmzK+7smovqcP7VhA8D+OIvaVjMsw==";
  35. public static function _initialize()
  36. {
  37. // 初始化加密扩展
  38. // echo "--------php_crypto_init 初始化开始--------\n";
  39. $path = "/www/server/php/73/libcryptAPIsm_lnx64.so";
  40. php_crypto_init($path);
  41. // echo "--------php_crypto_init 初始化结束-------\n";
  42. }
  43. public static function string2hex($string)
  44. {
  45. $hex = '';
  46. for ($i = 0; $i < strlen($string); $i++) {
  47. $ch = dechex(ord($string[$i]));
  48. if (strlen($ch) == 1) {
  49. $ch = "0" . $ch;
  50. }
  51. $hex .= $ch;
  52. }
  53. return $hex;
  54. }
  55. //获取token
  56. public static function getToken()
  57. {
  58. //获取6位数字符串
  59. $seqNO = (string)rand(100000, 999999);
  60. //获取16位随机字符串并md5 转大写 得到
  61. $key = strtoupper(md5(self::getKey()));
  62. //echo $this->string2hex(base64_decode(self::PRIVATE_KEY));
  63. // echo self::string2hex(base64_decode(self::PUBLIC_KEY));
  64. //初始化转换密钥信息
  65. php_HextoAsc(self::string2hex(base64_decode(self::PRIVATE_KEY)), $k1);
  66. php_HextoAsc(self::string2hex(base64_decode(self::PUBLIC_KEY)), $k2);
  67. php_HextoAsc(self::string2hex(base64_decode(self::CGB_PUBLIC_KEY)), $gf_k);
  68. //拼接参数数组
  69. $data = [
  70. 'appID' => self::$appID,
  71. 'seqNO' => $seqNO,
  72. 'random' => strtoupper(md5($seqNO)),
  73. 'sm2EncryptData' => self::SM2Encrypt($key, $gf_k), //sm2加密key
  74. 'sm2Sign' => self::SM2Sign($key, $k2, $k1), //sm2加密key
  75. ];
  76. dump($data);
  77. //拼接签名参数,md5转大写
  78. $data['sign'] = self::SM3Crypt($data['random'] . $data['seqNO'] . self::$appSecretKey . $key);
  79. dump($data);
  80. exit;
  81. //发送post接口请求
  82. $res = self::https_post(self::$tokenUrl, $data);
  83. halt($res);
  84. exit();
  85. //解密返回token数据
  86. $token = self::SM2Decrypt(base64_decode($res['sm2EncryptData']), $k1);
  87. self::$token = $token;
  88. //返回token
  89. return $token;
  90. }
  91. //业务请求数据
  92. public static function postJry()
  93. {
  94. //创建模拟请求数据,必须包含head,body
  95. $data = [
  96. 'head' => [
  97. 'id' => 8989
  98. ],
  99. 'body' => [
  100. 'code' => "000000",
  101. 'msg' => "测试请求"
  102. ]
  103. ];
  104. $res = self::scanPaymentCode($data);
  105. dump("返回结果");
  106. halt($res);
  107. }
  108. //业务请求封装
  109. public static function scanPaymentCode($array = [])
  110. {
  111. //初始化转换密钥信息
  112. php_HextoAsc(self::string2hex(base64_decode(self::PRIVATE_KEY)), $k1);
  113. php_HextoAsc(self::string2hex(base64_decode(self::PUBLIC_KEY)), $k2);
  114. php_HextoAsc(self::string2hex(base64_decode(self::CGB_PUBLIC_KEY)), $gf_k);
  115. //数据转json
  116. $json = json_encode($array, JSON_UNESCAPED_UNICODE);
  117. //获取随机6位字符串数字
  118. $seqNO = (string)rand(100000, 999999);
  119. //获取随机字符串秘钥,md5 并转大写
  120. $key = strtoupper(md5(self::getKey()));
  121. //拼接请求数据
  122. $data = [
  123. 'appID' => self::$appID,
  124. 'seqNO' => $seqNO,
  125. 'signMethod' => "SM3",
  126. 'encryptMethod' => "SM4",
  127. 'appAccessToken' => self::getToken(),//获取token
  128. 'sm2EncryptData' => self::SM2Encrypt($key, $gf_k), //sm2加密key
  129. 'sm2Sign' => self::SM2Sign($key, $k2, $k1), //sm2加密key
  130. ];
  131. //拼接签名参数,md5转大写
  132. $data['sign'] = self::SM3Crypt($json . $data['seqNO'] . self::$appSecretKey . $key);
  133. //AES加密业务数据
  134. $data['reqData'] = self::SM4Encrypt($json, $data['seqNO'] . $data['appAccessToken'] . self::$appSecretKey . $key);
  135. dump("业务数据json");
  136. dump($json);
  137. dump("请求数据");
  138. dump($data);
  139. $res = self::https_post("https://mouldai.com/apiSIT/tzyj/scanPaymentCode", $data);
  140. //解密sm2密钥
  141. $sm2Key = self::SM2Decrypt(base64_decode($res['sm2EncryptData']), $k1);
  142. dump("sm2解密数据:" . $sm2Key);
  143. //SM4解密数据
  144. $rspData = self::SM4Decrypt($res['rspData'], $res['seqNO'] . $data['appAccessToken'] . self::$appSecretKey . $sm2Key);
  145. dump("sm4解密报文数据:" . $rspData);
  146. //sm3验签数据校验
  147. $sm3SignData = self::SM3Crypt($rspData . $res['seqNO'] . self::$appSecretKey . $sm2Key);
  148. dump("sm3响应报文拼接加签:" . $sm3SignData);
  149. $signData = $res['sign'];
  150. dump("sm3响应报文签名:" . $signData);
  151. if ($sm3SignData == $signData) {
  152. dump("验签成功!");
  153. }
  154. dump($res);
  155. return $res;
  156. }
  157. //获取post数据并解密解签获得业务数据
  158. public static function getPostData()
  159. {
  160. //获取post数据
  161. $arr = input("post.");
  162. //模拟post获取数据
  163. $arr = '{"sign":"1CA0926A5C8BC2E2347E57016650E731","signMethod":"MD5","rsaEncryptData":"xs8lNVCj4ZCsAbXoJHI+AmRPqDJk01dlt3q7Jb9Vox4SNvvx6F+DclM2v1FdnDxAsNgXBERLXCc3SOFnqtrcMdYxc2GKyF+YTLJKjX0NmlGkXlO2oipIzhIL94la\/NGEzlT+JL4I8KF5vZvE+4gudkg0mZ+jC6jPWc+qyM6RROdwbgYPCiOpVTaQ+jtIxwz4rCY\/a2z\/fAFazzApUbyRxsCTVRESJ+3dU8V9zYxE7VzrV+IlOCwJdAIAfGPcwjq5wd3p\/3yEeFDBbZz0N6jnIEIlkCH99NsNFWIYY2mH0K2z+ccpJlBejoNq+FaxusS2DvZfJhp8xa3dav8itVyiSw==","encryptMethod":"AES","reqData":"QbYx9PwqKwy30K1SSgiorq9Sg9taSxImEw6qwY93N0QhsRb1UHqBghS+WmbBRZxrBTCMYNGBsG1GFvUdJxIrdXaQ4qBU\/PTpIjLlD+bvRyE27OYgHrSpQ6umylxWXSTYUIO0qbdTjNTR8UwRKPlHjuUJ149E5eH+s\/oyE6zZi9KNzx8BwjeZ\/Qeo\/CQF7fMuu3uJF7XOOIDqLidXvevQEr8hHLUyT40a\/NZsOAoqQSoNppazy+tDuttATH7gFWjFKNzRtt89wDdbjWNtK95tc\/uUBojfjg5HKcctSmqQ7jW6HvA2J5k3WnC6mxsTdaU9WPjtKbnqGUTkoM9YbJs1VghcnnQLSH49wAY7kB5SwWwTaPkHZy5kvytfMvLjwgjUioW0qYSpZyaBww4dSXJ0bQ1Mb+TArunrCpIzl2T4ZkzWT3\/j0m4bJoq7le4l03NVAn8iA3ju2asrBbrySTWzQ9vXDQbq1q+S5uh9YTXrVq0dM6CMNV8KquOCEC0UyaTKxCK2+cAS9LDQD4APXRTuNhkR57LKox2CfvN+CTDlWw8QfFfYKdNpmt\/OHklrUez0LcfMluc08ce1fvoDCuOqMN51y5qhV+d3utWa5rBjvzhE8de5qi54l3qW4EV03sQRdvN7R433fxOVt33N1mZ3NOwx1xjnJ4ivdVuhZ1XbGHd\/B7NGGafQbx3RChEIT250Kl3vih\/P2yF1ozQC8MY6sAHgOxAufmos8DoOwwWy9L59lNqN2sAnVpPalvjM4UCa8pkSrToDaoU4Sc44JOBghQ==","appAccessToken":"","seqNO":"127600","appID":"a539d3d7-3d4b-454b-9c49-7fb83fb8b611"}';//正式获取数据删除这里
  164. $arr = json_decode($arr, true); //正式获取数据删除这里
  165. //检验字段是否缺失
  166. if (!isset($arr['rsaEncryptData']) || !isset($arr['seqNO']) || !isset($arr['signMethod']) || !isset($arr['sign']) || !isset($arr['encryptMethod']) || !isset($arr['reqData']) || !isset($arr['appID'])) {
  167. return self::runJson("1000001", "缺少参数");
  168. }
  169. dump("获取到的post数据");
  170. dump($arr);
  171. //rsa解密秘钥字段rsaEncryptData
  172. $rasKey = self::decryptByPublicKey($arr['rsaEncryptData']);
  173. dump("解密后的秘钥");
  174. dump($rasKey);
  175. //拼接秘钥
  176. $key = $arr['seqNO'] . self::$token . self::$appSecretKey . $rasKey;
  177. //秘钥转md5 转大写
  178. $key = strtoupper(md5($key));
  179. //aes解密
  180. $res = $this->decrypt($arr['reqData'], $key);
  181. $res = json_decode($res, true);
  182. dump("数据结果");
  183. halt($res);
  184. }
  185. //SM2Encrypt
  186. public static function SM2Encrypt($data, $pubkey)
  187. {
  188. dump($data);
  189. dump($pubkey);
  190. exit;
  191. //sm2加密
  192. $recode = php_SM2Encrypt($data, $cipher, $pubkey);
  193. echo "<br/>recode:$recode \n";
  194. //php_SM2Encrypt接口返回的密文值是c1c3c2格式,以下示例转DER编码
  195. $recode = php_SM2FormatConvert(102, $cipher, $der);
  196. echo "<br/>recode:$recode \n";
  197. //SM2加密字符串转base64编码
  198. $sm2encrypted = base64_encode($der);
  199. echo "<br/>sm2加密字符串base64:$sm2encrypted \n";
  200. return $sm2encrypted;
  201. }
  202. //SM2Decrypt
  203. public static function SM2Decrypt($data, $privkey)
  204. {
  205. //php_SM2Decrypt 接收密文值是c1c3c2格式,若对方给的密文值是DER格式 则通过如下方式转换,然后再传入接口解密
  206. $recode = php_SM2FormatConvert(101, $data, $c1c3c2);
  207. echo "<br/>recode:$recode \n";
  208. //解密
  209. $recode = php_SM2Decrypt($c1c3c2, $plain, $privkey);
  210. echo "<br/>recode:$recode \n";
  211. echo "<br/>解密数据plain: $plain\n";
  212. return $plain;
  213. }
  214. //SM2Sign
  215. public static function SM2Sign($data, $pubkey, $privkey)
  216. {
  217. //签名
  218. $recode = php_SM2Sign($data, $redata, $privkey, $pubkey);
  219. echo "<br/>recode:$recode \n";
  220. //php_SM2Sign接口返回的签名值是RS格式,以下示例转DER编码
  221. $php_func = 'php_SM2FormatConvert';
  222. $recode = $php_func(202, $redata, $der);
  223. echo "<br/>recode:$recode \n";
  224. echo "<br/>data: " . $data . " len:" . strlen($data) . " \n";
  225. echo "<br/>signValue DER: " . base64_encode($der) . " len:" . strlen($der) . " \n";
  226. //加签数据base64返回
  227. $sm2SignData = base64_encode($der);
  228. return $sm2SignData;
  229. }
  230. //SM3Crypt
  231. public static function SM3Crypt($data)
  232. {
  233. $recode = php_SM3Crypt($data, $sm3hash);
  234. // echo "<br/>recode:$recode \n";
  235. //加签数据base64返回
  236. return strtoupper(self::string2hex($sm3hash));
  237. }
  238. //16进制转换为二进制
  239. public static function hex2String($hexdata)
  240. {
  241. $bindata = "";
  242. for ($i = 0; $i < strlen($hexdata); $i += 2) {
  243. $bindata .= chr(hexdec(substr($hexdata, $i, 2)));
  244. }
  245. return $bindata;
  246. }
  247. //SM4Encrypt
  248. public static function SM4Encrypt($data, $password)
  249. {
  250. //加密密码做特殊处理 先MD5转换大写 从第8位开始截取16个字符串
  251. $password = substr(strtoupper(md5($password)), 8, 16);
  252. echo "<br/>【SM4】MD5加密密码=======" . $password;
  253. //接口是密文key,将明文key加密后再传入php_SM4CBCCrypt进行数据加密
  254. //不必要每次都加密key,建议手工加密一次,将key密文保存使用,key明文保存至安全的地方
  255. //若是会话级别的key则自行定策略
  256. $recode = php_CryptKey(0, $password, $key);
  257. echo "<br/>recode:$recode \n";
  258. //加密
  259. $recode = php_SM4CBCCrypt(0, $data, $redata, $key, self::$iv);
  260. echo "<br/>recode:$recode \n";
  261. echo "<br/>redata: " . base64_encode($redata) . " \n";
  262. $SM4Encryptdata = base64_encode($redata);
  263. return $SM4Encryptdata;
  264. }
  265. //SM4Encrypt
  266. public static function SM4Decrypt($data, $password)
  267. {
  268. echo "<br/>SM4解密数据=======" . $data;
  269. //加密密码做特殊处理
  270. $password = substr(strtoupper(md5($password)), 8, 16);
  271. echo "<br/>MD5密码=======" . $password;
  272. //接口是密文key,将明文key加密后再传入php_SM4CBCCrypt进行数据加密
  273. //不必要每次都加密key,建议手工加密一次,将key密文保存使用,key明文保存至安全的地方
  274. //若是会话级别的key则自行定策略
  275. $recode = php_CryptKey(0, $password, $key);
  276. echo "<br/>recode:$recode \n";
  277. //解密
  278. $recode = php_SM4CBCCrypt(1, base64_decode($data), $dedata, $key, self::$iv);
  279. echo "<br/>sm4[解密]recode:$recode \n";
  280. return $dedata;
  281. }
  282. /**
  283. * 公钥解密
  284. * @param mixed $data 密文数据
  285. * @return string 原文结果
  286. */
  287. private static function decryptByPublicKey($data)
  288. {
  289. $data = base64_decode($data);
  290. openssl_public_decrypt($data, $decrypted, self::PUBLIC_KEY, OPENSSL_PKCS1_PADDING);//公钥解密
  291. return $decrypted;
  292. }
  293. /**
  294. * 私钥加密
  295. * @param mixed $data 原始数据
  296. * @return string 密文结果
  297. */
  298. private static function encryptByPrivateKey($data)
  299. {
  300. openssl_private_encrypt($data, $encrypted, self::PRIVATE_KEY, OPENSSL_PKCS1_PADDING);//私钥加密
  301. //加密后的内容通常含有特殊字符,需要编码转换下,在网络间通过url传输时要注意base64编码是否是url安全的
  302. return base64_encode($encrypted);
  303. }
  304. //封装返回数据
  305. private static function runJson($code = "000000", $msg = "完成")
  306. {
  307. return json(['code' => $code, 'message' => $msg]);
  308. //以下加密信息返回,暂时不用使用
  309. // $data = [
  310. // 'signMethod' => 'MD5',
  311. // 'encryptMethod' => 'AES',
  312. // 'appID' => $this->appID,
  313. // 'seqNO' => (string)rand(100000,999999),
  314. // 'appAccessToken' => ''
  315. // ];
  316. // $json = json_encode(['code'=>$code, 'message'=>$msg]);
  317. // $key = strtoupper(md5(getKey()));//随机秘钥
  318. // $data['rsaEncryptData'] = $this->encryptByPrivateKey($key);
  319. // $data['reqData'] = $this->encrypt($json, $key);
  320. // $data['sign'] = strtoupper(md5($data['reqData'] . $data['seqNO'] . $this->appSecretKey . $key ));
  321. // return json($data);
  322. }
  323. // curl post请求
  324. private static function https_post($url, $data = null)
  325. {
  326. $data = json_encode($data, JSON_UNESCAPED_SLASHES);
  327. $header [] = 'Content-Type:application/x-www-form-urlencoded';
  328. $ch = curl_init();
  329. curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
  330. curl_setopt($ch, CURLOPT_URL, $url);
  331. curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
  332. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
  333. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
  334. curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; MSIE 5.01; Windows NT 5.0)');
  335. @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
  336. curl_setopt($ch, CURLOPT_AUTOREFERER, 1);
  337. curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
  338. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  339. $tmpInfo = curl_exec($ch);
  340. curl_close($ch);
  341. return json_decode($tmpInfo, true);
  342. }
  343. /**
  344. * 获得随机字符串
  345. **/
  346. private static function getKey($length = 16)
  347. {
  348. // 密码字符集,可任意添加你需要的字符
  349. $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ2345678';
  350. $key = '';
  351. for ($i = 0; $i < $length; $i++) {
  352. $key .= $chars[mt_rand(0, strlen($chars) - 1)];
  353. }
  354. return $key;
  355. }
  356. }