Gmdemo.php 18 KB

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