WeChat.php 7.1 KB

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