WeChat.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. <?php
  2. namespace App\Channels\Components;
  3. use Exception;
  4. class WeChat
  5. {
  6. public function VerifyURL($sMsgSignature, $sTimeStamp, $sNonce, $sEchoStr, &$sReplyEchoStr)
  7. { // 验证URL
  8. //verify msg_signature
  9. $array = $this->getSHA1($sTimeStamp, $sNonce, $sEchoStr);
  10. $ret = $array[0];
  11. if ($ret !== 0) {
  12. return $ret;
  13. }
  14. $signature = $array[1];
  15. if ($signature !== $sMsgSignature) {
  16. return -40001; // ValidateSignatureError
  17. }
  18. $result = (new Prpcrypt())->decrypt($sEchoStr);
  19. if ($result[0] !== 0) {
  20. return $result[0];
  21. }
  22. $sReplyEchoStr = $result[1];
  23. return 0;
  24. }
  25. public function getSHA1($timestamp, $nonce, $encrypt_msg)
  26. {
  27. //排序
  28. try {
  29. $array = [$encrypt_msg, sysConfig('wechat_token'), $timestamp, $nonce];
  30. sort($array, SORT_STRING);
  31. return [0, sha1(implode($array))];
  32. } catch (Exception $e) {
  33. echo $e->__toString()."\n";
  34. return [-40003, null]; // ComputeSignatureError
  35. }
  36. }
  37. public function EncryptMsg($sReplyMsg, $sTimeStamp, $sNonce, &$sEncryptMsg)
  38. { //将公众平台回复用户的消息加密打包.
  39. //加密
  40. $array = (new Prpcrypt())->encrypt($sReplyMsg);
  41. $ret = $array[0];
  42. if ($ret !== 0) {
  43. return $ret;
  44. }
  45. if ($sTimeStamp === null) {
  46. $sTimeStamp = time();
  47. }
  48. $encrypt = $array[1];
  49. //生成安全签名
  50. $array = $this->getSHA1($sTimeStamp, $sNonce, $encrypt);
  51. $ret = $array[0];
  52. if ($ret !== 0) {
  53. return $ret;
  54. }
  55. $signature = $array[1];
  56. //生成发送的xml
  57. $sEncryptMsg = $this->generate($encrypt, $signature, $sTimeStamp, $sNonce);
  58. return 0;
  59. }
  60. /**
  61. * 生成xml消息.
  62. *
  63. * @param string $encrypt 加密后的消息密文
  64. * @param string $signature 安全签名
  65. * @param string $timestamp 时间戳
  66. * @param string $nonce 随机字符串
  67. */
  68. public function generate($encrypt, $signature, $timestamp, $nonce)
  69. {
  70. $format = '<xml>
  71. <Encrypt><![CDATA[%s]]></Encrypt>
  72. <MsgSignature><![CDATA[%s]]></MsgSignature>
  73. <TimeStamp>%s</TimeStamp>
  74. <Nonce><![CDATA[%s]]></Nonce>
  75. </xml>';
  76. return sprintf($format, $encrypt, $signature, $timestamp, $nonce);
  77. }
  78. public function DecryptMsg($sMsgSignature, $sTimeStamp = null, $sNonce, $sPostData, &$sMsg)
  79. { // 检验消息的真实性,并且获取解密后的明文.
  80. //提取密文
  81. $array = $this->extract($sPostData);
  82. $ret = $array[0];
  83. if ($ret !== 0) {
  84. return $ret;
  85. }
  86. if ($sTimeStamp === null) {
  87. $sTimeStamp = time();
  88. }
  89. $encrypt = $array[1];
  90. //验证安全签名
  91. $array = $this->getSHA1($sTimeStamp, $sNonce, $encrypt);
  92. $ret = $array[0];
  93. if ($ret !== 0) {
  94. return $ret;
  95. }
  96. $signature = $array[1];
  97. if ($signature !== $sMsgSignature) {
  98. return -40001; // ValidateSignatureError
  99. }
  100. $result = (new Prpcrypt())->decrypt($encrypt);
  101. if ($result[0] !== 0) {
  102. return $result[0];
  103. }
  104. $sMsg = $result[1];
  105. return 0;
  106. }
  107. /**
  108. * 提取出xml数据包中的加密消息.
  109. *
  110. * @param string $xmltext 待提取的xml字符串
  111. * @return array 提取出的加密消息字符串
  112. */
  113. public function extract($xmltext)
  114. {
  115. try {
  116. $xml = new DOMDocument();
  117. $xml->loadXML($xmltext);
  118. $array_e = $xml->getElementsByTagName('Encrypt');
  119. $encrypt = $array_e->item(0)->nodeValue;
  120. return [0, $encrypt];
  121. } catch (Exception $e) {
  122. echo $e."\n";
  123. return [-40002, null]; // ParseXmlError
  124. }
  125. }
  126. }
  127. /**
  128. * PKCS7Encoder class.
  129. *
  130. * 提供基于PKCS7算法的加解密接口.
  131. */
  132. class PKCS7Encoder
  133. {
  134. public static $block_size = 32;
  135. public function encode($text)
  136. { // 对需要加密的明文进行填充补位
  137. //计算需要填充的位数
  138. $amount_to_pad = self::$block_size - (strlen($text) % self::$block_size);
  139. if ($amount_to_pad === 0) {
  140. $amount_to_pad = self::$block_size;
  141. }
  142. return $text.str_repeat(chr($amount_to_pad), $amount_to_pad); // 获得补位所用的字符
  143. }
  144. public function decode($text)
  145. { // 对解密后的明文进行补位删除
  146. $pad = ord(substr($text, -1));
  147. if ($pad < 1 || $pad > self::$block_size) {
  148. $pad = 0;
  149. }
  150. return substr($text, 0, (strlen($text) - $pad));
  151. }
  152. }
  153. /**
  154. * Prpcrypt class.
  155. *
  156. * 提供接收和推送给公众平台消息的加解密接口.
  157. */
  158. class Prpcrypt
  159. {
  160. public $key = null;
  161. public $iv = null;
  162. public function __construct()
  163. {
  164. $this->key = base64_decode(sysConfig('wechat_encodingAESKey').'=');
  165. $this->iv = substr($this->key, 0, 16);
  166. }
  167. /**
  168. * 加密.
  169. *
  170. * @param $text
  171. * @return array
  172. */
  173. public function encrypt($text)
  174. {
  175. try {
  176. //拼接
  177. $text = Str::random().pack('N', strlen($text)).$text.sysConfig('wechat_cid');
  178. //添加PKCS#7填充
  179. $text = (new PKCS7Encoder)->encode($text);
  180. //加密
  181. $encrypted = openssl_encrypt($text, 'AES-256-CBC', $this->key, OPENSSL_ZERO_PADDING, $this->iv);
  182. return [0, $encrypted];
  183. } catch (Exception $e) {
  184. echo $e->__toString();
  185. return [-40006, null]; // EncryptAESError
  186. }
  187. }
  188. public function decrypt($encrypted): array
  189. { // 解密
  190. try {
  191. //解密
  192. $decrypted = openssl_decrypt($encrypted, 'AES-256-CBC', $this->key, OPENSSL_ZERO_PADDING, $this->iv);
  193. } catch (Exception $e) {
  194. return [-40007, null]; // DecryptAESError
  195. }
  196. try {
  197. //删除PKCS#7填充
  198. $result = (new PKCS7Encoder)->decode($decrypted);
  199. if (strlen($result) < 16) {
  200. return [];
  201. }
  202. //拆分
  203. $content = substr($result, 16, strlen($result));
  204. $len_list = unpack('N', substr($content, 0, 4));
  205. $xml_len = $len_list[1];
  206. $xml_content = substr($content, 4, $xml_len);
  207. $from_receiveId = substr($content, $xml_len + 4);
  208. } catch (Exception $e) {
  209. echo $e->__toString();
  210. return [-40008, null]; // IllegalBuffer
  211. }
  212. if ($from_receiveId !== sysConfig('wechat_cid')) {
  213. return [-40005, null]; // ValidateCorpidError
  214. }
  215. return [0, $xml_content];
  216. }
  217. }