|
@@ -7,6 +7,7 @@
|
|
|
* usage:
|
|
|
* $options = array(
|
|
|
* 'token'=>'tokenaccesskey', //填写你设定的key
|
|
|
+ * 'encodingaeskey'=>'encodingaeskey', //填写加密用的EncodingAESKey
|
|
|
* 'appid'=>'wxdk1234567890', //填写高级调用功能的app id
|
|
|
* 'appsecret'=>'xxxxxxxxxxxxxxxxxxx', //填写高级调用功能的密钥
|
|
|
* 'partnerid'=>'88888888', //财付通商户身份标识
|
|
@@ -96,6 +97,8 @@ class Wechat
|
|
|
const SEMANTIC_API_URL= 'https://api.weixin.qq.com/semantic/semproxy/search?';
|
|
|
|
|
|
private $token;
|
|
|
+ private $encodingAesKey;
|
|
|
+ private $encrypt_type;
|
|
|
private $appid;
|
|
|
private $appsecret;
|
|
|
private $access_token;
|
|
@@ -103,6 +106,7 @@ class Wechat
|
|
|
private $partnerid;
|
|
|
private $partnerkey;
|
|
|
private $paysignkey;
|
|
|
+ private $postxml;
|
|
|
private $_msg;
|
|
|
private $_funcflag = false;
|
|
|
private $_receive;
|
|
@@ -115,6 +119,7 @@ class Wechat
|
|
|
public function __construct($options)
|
|
|
{
|
|
|
$this->token = isset($options['token'])?$options['token']:'';
|
|
|
+ $this->encodingAesKey = isset($options['encodingaeskey'])?$options['encodingaeskey']:'';
|
|
|
$this->appid = isset($options['appid'])?$options['appid']:'';
|
|
|
$this->appsecret = isset($options['appsecret'])?$options['appsecret']:'';
|
|
|
$this->partnerid = isset($options['partnerid'])?$options['partnerid']:'';
|
|
@@ -127,14 +132,15 @@ class Wechat
|
|
|
/**
|
|
|
* For weixin server validation
|
|
|
*/
|
|
|
- private function checkSignature()
|
|
|
+ private function checkSignature($str='')
|
|
|
{
|
|
|
$signature = isset($_GET["signature"])?$_GET["signature"]:'';
|
|
|
+ $signature = isset($_GET["msg_signature"])?$_GET["msg_signature"]:$signature; //如果存在加密验证则用加密验证段
|
|
|
$timestamp = isset($_GET["timestamp"])?$_GET["timestamp"]:'';
|
|
|
$nonce = isset($_GET["nonce"])?$_GET["nonce"]:'';
|
|
|
|
|
|
$token = $this->token;
|
|
|
- $tmpArr = array($token, $timestamp, $nonce);
|
|
|
+ $tmpArr = array($token, $timestamp, $nonce,$str);
|
|
|
sort($tmpArr, SORT_STRING);
|
|
|
$tmpStr = implode( $tmpArr );
|
|
|
$tmpStr = sha1( $tmpStr );
|
|
@@ -152,23 +158,46 @@ class Wechat
|
|
|
*/
|
|
|
public function valid($return=false)
|
|
|
{
|
|
|
- $echoStr = isset($_GET["echostr"]) ? $_GET["echostr"]: '';
|
|
|
+ $encryptStr="";
|
|
|
+ if ($_SERVER['REQUEST_METHOD'] == "POST") {
|
|
|
+ $postStr = file_get_contents("php://input");
|
|
|
+ $array = (array)simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
|
|
|
+ $this->encrypt_type = isset($_GET["encrypt_type"]) ? $_GET["encrypt_type"]: '';
|
|
|
+ if ($this->encrypt_type == 'aes') { //aes加密
|
|
|
+ $this->log($postStr);
|
|
|
+ $encryptStr = $array['Encrypt'];
|
|
|
+ $pc = new Prpcrypt($this->encodingAesKey);
|
|
|
+ $array = $pc->decrypt($encryptStr,$this->appid);
|
|
|
+ if (!isset($array[0]) || ($array[0] != 0)) {
|
|
|
+ if (!$return) {
|
|
|
+ die('解密失败!');
|
|
|
+ } else {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $this->postxml = $array[1];
|
|
|
+ } else {
|
|
|
+ $this->postxml = $postStr;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ $echoStr = isset($_GET["echostr"]) ? $_GET["echostr"]: '';
|
|
|
+ }
|
|
|
if ($return) {
|
|
|
if ($echoStr) {
|
|
|
- if ($this->checkSignature())
|
|
|
+ if ($this->checkSignature($encryptStr))
|
|
|
return $echoStr;
|
|
|
else
|
|
|
return false;
|
|
|
} else
|
|
|
- return $this->checkSignature();
|
|
|
+ return $this->checkSignature($encryptStr);
|
|
|
} else {
|
|
|
if ($echoStr) {
|
|
|
- if ($this->checkSignature())
|
|
|
+ if ($this->checkSignature($encryptStr))
|
|
|
die($echoStr);
|
|
|
else
|
|
|
die('no access');
|
|
|
} else {
|
|
|
- if ($this->checkSignature())
|
|
|
+ if ($this->checkSignature($encryptStr))
|
|
|
return true;
|
|
|
else
|
|
|
die('no access');
|
|
@@ -219,7 +248,8 @@ class Wechat
|
|
|
public function getRev()
|
|
|
{
|
|
|
if ($this->_receive) return $this;
|
|
|
- $postStr = file_get_contents("php://input");
|
|
|
+ $postStr = !empty($this->postxml)?$this->postxml:file_get_contents("php://input");
|
|
|
+ //兼顾使用明文又不想调用valid()方法的情况
|
|
|
$this->log($postStr);
|
|
|
if (!empty($postStr)) {
|
|
|
$this->_receive = (array)simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
|
|
@@ -410,19 +440,11 @@ class Wechat
|
|
|
* @return: array | false
|
|
|
* array (
|
|
|
* 'Count' => '2',
|
|
|
- * 'PicList' =>
|
|
|
- * array (
|
|
|
- * 'item' =>
|
|
|
- * array (
|
|
|
- * 0 =>
|
|
|
- * array (
|
|
|
- * 'PicMd5Sum' => 'aaae42617cf2a14342d96005af53624c',
|
|
|
- * ),
|
|
|
- * 1 =>
|
|
|
- * array (
|
|
|
- * 'PicMd5Sum' => '149bd39e296860a2adc2f1bb81616ff8',
|
|
|
- * ),
|
|
|
- * ),
|
|
|
+ * 'PicList' =>array (
|
|
|
+ * 'item' =>array (
|
|
|
+ * 0 =>array ('PicMd5Sum' => 'aaae42617cf2a14342d96005af53624c'),
|
|
|
+ * 1 =>array ('PicMd5Sum' => '149bd39e296860a2adc2f1bb81616ff8'),
|
|
|
+ * ),
|
|
|
* ),
|
|
|
* )
|
|
|
*
|
|
@@ -653,6 +675,7 @@ class Wechat
|
|
|
$this->Message($msg);
|
|
|
return $this;
|
|
|
}
|
|
|
+
|
|
|
/**
|
|
|
* 设置回复消息
|
|
|
* Examle: $obj->voice('media_id')->reply();
|
|
@@ -672,6 +695,7 @@ class Wechat
|
|
|
$this->Message($msg);
|
|
|
return $this;
|
|
|
}
|
|
|
+
|
|
|
/**
|
|
|
* 设置回复消息
|
|
|
* Examle: $obj->video('media_id','title','description')->reply();
|
|
@@ -767,11 +791,44 @@ class Wechat
|
|
|
$msg = $this->_msg;
|
|
|
$xmldata= $this->xml_encode($msg);
|
|
|
$this->log($xmldata);
|
|
|
+ if ($this->encrypt_type == 'aes') { //如果来源消息为加密方式
|
|
|
+ $pc = new Prpcrypt($this->encodingAesKey);
|
|
|
+ $array = $pc->encrypt($xmldata, $this->appid);
|
|
|
+ $ret = $array[0];
|
|
|
+ if ($ret != 0) {
|
|
|
+ $this->log('encrypt err!');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ $timestamp = time();
|
|
|
+ $nonce = rand(77,999)*rand(605,888)*rand(11,99);
|
|
|
+ $encrypt = $array[1];
|
|
|
+ $tmpArr = array($this->token, $timestamp, $nonce,$encrypt);//比普通公众平台多了一个加密的密文
|
|
|
+ sort($tmpArr, SORT_STRING);
|
|
|
+ $signature = implode($tmpArr);
|
|
|
+ $signature = sha1($signature);
|
|
|
+ $xmldata = $this->generate($encrypt, $signature, $timestamp, $nonce);
|
|
|
+ $this->log($xmldata);
|
|
|
+ }
|
|
|
if ($return)
|
|
|
return $xmldata;
|
|
|
else
|
|
|
echo $xmldata;
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * xml格式加密,仅请求为加密方式时再用
|
|
|
+ */
|
|
|
+ private function generate($encrypt, $signature, $timestamp, $nonce)
|
|
|
+ {
|
|
|
+ //格式化加密信息
|
|
|
+ $format = "<xml>
|
|
|
+<Encrypt><![CDATA[%s]]></Encrypt>
|
|
|
+<MsgSignature><![CDATA[%s]]></MsgSignature>
|
|
|
+<TimeStamp>%s</TimeStamp>
|
|
|
+<Nonce><![CDATA[%s]]></Nonce>
|
|
|
+</xml>";
|
|
|
+ return sprintf($format, $encrypt, $signature, $timestamp, $nonce);
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
* GET 请求
|
|
@@ -940,13 +997,11 @@ class Wechat
|
|
|
* 'type' => 'scancode_waitmsg',
|
|
|
* 'name' => '扫码带提示',
|
|
|
* 'key' => 'rselfmenu_0_0',
|
|
|
- * 'sub_button' => ''
|
|
|
* ),
|
|
|
* 1 => array (
|
|
|
* 'type' => 'scancode_push',
|
|
|
* 'name' => '扫码推事件',
|
|
|
* 'key' => 'rselfmenu_0_1',
|
|
|
- * 'sub_button' => ''
|
|
|
* ),
|
|
|
* ),
|
|
|
* ),
|
|
@@ -957,21 +1012,18 @@ class Wechat
|
|
|
* 'type' => 'pic_sysphoto',
|
|
|
* 'name' => '系统拍照发图',
|
|
|
* 'key' => 'rselfmenu_1_0',
|
|
|
- * 'sub_button' => ''
|
|
|
* ),
|
|
|
* 1 => array (
|
|
|
* 'type' => 'pic_photo_or_album',
|
|
|
* 'name' => '拍照或者相册发图',
|
|
|
* 'key' => 'rselfmenu_1_1',
|
|
|
- * 'sub_button' => ''
|
|
|
* )
|
|
|
* ),
|
|
|
* ),
|
|
|
* 2 => array (
|
|
|
* 'type' => 'location_select',
|
|
|
* 'name' => '发送位置',
|
|
|
- * 'key' => 'rselfmenu_2_0',
|
|
|
- * 'sub_button' => ''
|
|
|
+ * 'key' => 'rselfmenu_2_0'
|
|
|
* ),
|
|
|
* ),
|
|
|
* )
|
|
@@ -1925,11 +1977,12 @@ class Wechat
|
|
|
'uid' => ''
|
|
|
);
|
|
|
//地理坐标或城市名称二选一
|
|
|
- if (isset($latitude)) {
|
|
|
+ if ($latitude) {
|
|
|
$data['latitude'] = $latitude;
|
|
|
$data['longitude'] = $longitude;
|
|
|
- } elseif (isset($city)) {
|
|
|
+ } elseif ($city) {
|
|
|
$data['city'] = $city;
|
|
|
+ } elseif ($region) {
|
|
|
$data['region'] = $region;
|
|
|
}
|
|
|
$result = $this->http_post(self::SEMANTIC_API_URL.'access_token='.$this->access_token,self::json_encode($data));
|
|
@@ -1946,3 +1999,207 @@ class Wechat
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * PKCS7Encoder class
|
|
|
+ *
|
|
|
+ * 提供基于PKCS7算法的加解密接口.
|
|
|
+ */
|
|
|
+class PKCS7Encoder
|
|
|
+{
|
|
|
+ public static $block_size = 32;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 对需要加密的明文进行填充补位
|
|
|
+ * @param $text 需要进行填充补位操作的明文
|
|
|
+ * @return 补齐明文字符串
|
|
|
+ */
|
|
|
+ function encode($text)
|
|
|
+ {
|
|
|
+ $block_size = PKCS7Encoder::$block_size;
|
|
|
+ $text_length = strlen($text);
|
|
|
+ //计算需要填充的位数
|
|
|
+ $amount_to_pad = PKCS7Encoder::$block_size - ($text_length % PKCS7Encoder::$block_size);
|
|
|
+ if ($amount_to_pad == 0) {
|
|
|
+ $amount_to_pad = PKCS7Encoder::block_size;
|
|
|
+ }
|
|
|
+ //获得补位所用的字符
|
|
|
+ $pad_chr = chr($amount_to_pad);
|
|
|
+ $tmp = "";
|
|
|
+ for ($index = 0; $index < $amount_to_pad; $index++) {
|
|
|
+ $tmp .= $pad_chr;
|
|
|
+ }
|
|
|
+ return $text . $tmp;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 对解密后的明文进行补位删除
|
|
|
+ * @param decrypted 解密后的明文
|
|
|
+ * @return 删除填充补位后的明文
|
|
|
+ */
|
|
|
+ function decode($text)
|
|
|
+ {
|
|
|
+
|
|
|
+ $pad = ord(substr($text, -1));
|
|
|
+ if ($pad < 1 || $pad > PKCS7Encoder::$block_size) {
|
|
|
+ $pad = 0;
|
|
|
+ }
|
|
|
+ return substr($text, 0, (strlen($text) - $pad));
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Prpcrypt class
|
|
|
+ *
|
|
|
+ * 提供接收和推送给公众平台消息的加解密接口.
|
|
|
+ */
|
|
|
+class Prpcrypt
|
|
|
+{
|
|
|
+ public $key;
|
|
|
+
|
|
|
+ function Prpcrypt($k)
|
|
|
+ {
|
|
|
+ $this->key = base64_decode($k . "=");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 对明文进行加密
|
|
|
+ * @param string $text 需要加密的明文
|
|
|
+ * @return string 加密后的密文
|
|
|
+ */
|
|
|
+ public function encrypt($text, $appid)
|
|
|
+ {
|
|
|
+
|
|
|
+ try {
|
|
|
+ //获得16位随机字符串,填充到明文之前
|
|
|
+ $random = "aaaabbbbccccdddd"; //$this->getRandomStr();
|
|
|
+ $text = $random . pack("N", strlen($text)) . $text . $appid;
|
|
|
+ // 网络字节序
|
|
|
+ $size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
|
|
|
+ $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
|
|
|
+ $iv = substr($this->key, 0, 16);
|
|
|
+ //使用自定义的填充方式对明文进行补位填充
|
|
|
+ $pkc_encoder = new PKCS7Encoder;
|
|
|
+ $text = $pkc_encoder->encode($text);
|
|
|
+ mcrypt_generic_init($module, $this->key, $iv);
|
|
|
+ //加密
|
|
|
+ $encrypted = mcrypt_generic($module, $text);
|
|
|
+ mcrypt_generic_deinit($module);
|
|
|
+ mcrypt_module_close($module);
|
|
|
+
|
|
|
+ // print(base64_encode($encrypted));
|
|
|
+ //使用BASE64对加密后的字符串进行编码
|
|
|
+ return array(ErrorCode::$OK, base64_encode($encrypted));
|
|
|
+ } catch (Exception $e) {
|
|
|
+ print $e;
|
|
|
+ return array(ErrorCode::$EncryptAESError, null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 对密文进行解密
|
|
|
+ * @param string $encrypted 需要解密的密文
|
|
|
+ * @return string 解密得到的明文
|
|
|
+ */
|
|
|
+ public function decrypt($encrypted, $appid)
|
|
|
+ {
|
|
|
+
|
|
|
+ try {
|
|
|
+ //使用BASE64对需要解密的字符串进行解码
|
|
|
+ $ciphertext_dec = base64_decode($encrypted);
|
|
|
+ $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
|
|
|
+ $iv = substr($this->key, 0, 16);
|
|
|
+ mcrypt_generic_init($module, $this->key, $iv);
|
|
|
+ //解密
|
|
|
+ $decrypted = mdecrypt_generic($module, $ciphertext_dec);
|
|
|
+ mcrypt_generic_deinit($module);
|
|
|
+ mcrypt_module_close($module);
|
|
|
+ } catch (Exception $e) {
|
|
|
+ return array(ErrorCode::$DecryptAESError, null);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ try {
|
|
|
+ //去除补位字符
|
|
|
+ $pkc_encoder = new PKCS7Encoder;
|
|
|
+ $result = $pkc_encoder->decode($decrypted);
|
|
|
+ //去除16位随机字符串,网络字节序和AppId
|
|
|
+ if (strlen($result) < 16)
|
|
|
+ return "";
|
|
|
+ $content = substr($result, 16, strlen($result));
|
|
|
+ $len_list = unpack("N", substr($content, 0, 4));
|
|
|
+ $xml_len = $len_list[1];
|
|
|
+ $xml_content = substr($content, 4, $xml_len);
|
|
|
+ $from_appid = substr($content, $xml_len + 4);
|
|
|
+ } catch (Exception $e) {
|
|
|
+ print $e;
|
|
|
+ return array(ErrorCode::$IllegalBuffer, null);
|
|
|
+ }
|
|
|
+ if ($from_appid != $appid)
|
|
|
+ return array(ErrorCode::$ValidateAppidError, null);
|
|
|
+ return array(0, $xml_content);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 随机生成16位字符串
|
|
|
+ * @return string 生成的字符串
|
|
|
+ */
|
|
|
+ function getRandomStr()
|
|
|
+ {
|
|
|
+
|
|
|
+ $str = "";
|
|
|
+ $str_pol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
|
|
|
+ $max = strlen($str_pol) - 1;
|
|
|
+ for ($i = 0; $i < 16; $i++) {
|
|
|
+ $str .= $str_pol[mt_rand(0, $max)];
|
|
|
+ }
|
|
|
+ return $str;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * error code
|
|
|
+ * 仅用作类内部使用,不用于官方API接口的errCode码
|
|
|
+ */
|
|
|
+class ErrorCode
|
|
|
+{
|
|
|
+ public static $OK = 0;
|
|
|
+ public static $ValidateSignatureError = 40001;
|
|
|
+ public static $ParseXmlError = 40002;
|
|
|
+ public static $ComputeSignatureError = 40003;
|
|
|
+ public static $IllegalAesKey = 40004;
|
|
|
+ public static $ValidateAppidError = 40005;
|
|
|
+ public static $EncryptAESError = 40006;
|
|
|
+ public static $DecryptAESError = 40007;
|
|
|
+ public static $IllegalBuffer = 40008;
|
|
|
+ public static $EncodeBase64Error = 40009;
|
|
|
+ public static $DecodeBase64Error = 40010;
|
|
|
+ public static $GenReturnXmlError = 40011;
|
|
|
+ public static $errCode=array(
|
|
|
+ '0' => '处理成功',
|
|
|
+ '40001' => '校验签名失败',
|
|
|
+ '40002' => '解析xml失败',
|
|
|
+ '40003' => '计算签名失败',
|
|
|
+ '40004' => '不合法的AESKey',
|
|
|
+ '40005' => '校验AppID失败',
|
|
|
+ '40006' => 'AES加密失败',
|
|
|
+ '40007' => 'AES解密失败',
|
|
|
+ '40008' => '公众平台发送的xml不合法',
|
|
|
+ '40009' => 'Base64编码失败',
|
|
|
+ '40010' => 'Base64解码失败',
|
|
|
+ '40011' => '公众帐号生成回包xml失败'
|
|
|
+ );
|
|
|
+ public static function getErrText($err) {
|
|
|
+ if (isset(self::$errCode[$err])) {
|
|
|
+ return self::$errCode[$err];
|
|
|
+ }else {
|
|
|
+ return false;
|
|
|
+ };
|
|
|
+ }
|
|
|
+}
|