WeChat.php 7.1 KB

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