Created
April 23, 2015 09:40
-
-
Save SyuTingSong/e42b0c8b1f677d9ebe0f to your computer and use it in GitHub Desktop.
Another WeiXin Message Decryptor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace wechat; | |
class AESErrorCode { | |
const OK = 0; | |
const ValidateSignatureError = -40001; | |
const ParseXmlError = -40002; | |
const ComputeSignatureError = -40003; | |
const IllegalAesKey = -40004; | |
const ValidateAppidError = -40005; | |
const EncryptAESError = -40006; | |
const DecryptAESError = -40007; | |
const IllegalBuffer = -40008; | |
const EncodeBase64Error = -40009; | |
const DecodeBase64Error = -40010; | |
const GenReturnXmlError = -40011; | |
} | |
class WxAES { | |
/** | |
* @var string | |
*/ | |
private $key; | |
/** | |
* @var string | |
*/ | |
private $appId; | |
/** | |
* @var string | |
*/ | |
private $token; | |
/** | |
* @var string | |
*/ | |
private $iv; | |
public function __construct($key, $appId, $token) { | |
$this->key = base64_decode($key.'='); | |
$this->iv = substr($this->key, 0, 16); | |
$this->appId = $appId; | |
$this->token = $token; | |
} | |
public function encode($xmlString) { | |
$toEncodeData = openssl_random_pseudo_bytes(16) . pack('N', strlen($xmlString)) . $xmlString . $this->appId; | |
$toEncodeData = $this->pkcs7pad($toEncodeData, 32); | |
$encoded = openssl_encrypt($toEncodeData, 'AES-256-CBC', $this->key, OPENSSL_ZERO_PADDING, substr($this->key, 0, 16)); | |
$timestamp = time(); | |
$nonce = rand(100000000, 999999999); | |
$signature = $this->signature($timestamp, $nonce, $encoded); | |
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<xml><Encrypt><![CDATA[$encoded]]></Encrypt><MsgSignature><![CDATA[$signature]]></MsgSignature><TimeStamp>$timestamp</TimeStamp><Nonce><![CDATA[$nonce]]></Nonce></xml>\r\n"; | |
} | |
public function decode($encryptXml) { | |
$stack = new \SplStack(); | |
$arr = array(); | |
$x = xml_parser_create('UTF-8'); | |
xml_set_element_handler($x, function($x, $name) use ($stack) { | |
$stack->push($name); | |
}, function() use ($stack) { | |
$stack->pop(); | |
}); | |
xml_set_character_data_handler($x, function($x, $data) use (&$arr, $stack) { | |
$name = strtolower($stack->top()); | |
$arr[$name] = $data; | |
}); | |
if (!xml_parse($x, $encryptXml, true)) { | |
throw new \Exception(xml_error_string(xml_get_error_code($x)), AESErrorCode::ParseXmlError); | |
} | |
xml_parser_free($x); | |
if (!isset($arr['encrypt'])) { | |
return $encryptXml; | |
} | |
$encrypt = $arr['encrypt']; | |
$nonce = isset($arr['nonce'])?$arr['nonce']:$_GET['nonce']; | |
$timestamp = isset($arr['timestamp'])?$arr['timestamp']:$_GET['timestamp']; | |
$msgSignature = isset($arr['msgsignature'])?$arr['msgsignature']:$_GET['msg_signature']; | |
$signature = $this->signature($timestamp, $nonce, $encrypt); | |
if ($msgSignature !== $signature) { | |
throw new \Exception('Validate Signature Error', AESErrorCode::ValidateSignatureError); | |
} | |
$decrypt = openssl_decrypt($encrypt, 'AES-256-CBC', $this->key, OPENSSL_ZERO_PADDING, substr($this->key, 0, 16)); | |
if ($decrypt === false) { | |
throw new \Exception('decrypt failed', AESErrorCode::DecryptAESError); | |
} | |
$decrypt = $this->pkcs7unpad($decrypt, 32); | |
$content = substr($decrypt, 16); | |
$len = unpack("N", substr($content, 0, 4)); | |
$len = $len[1]; | |
$xml = substr($content, 4, $len); | |
$fromAppId = substr($content, $len + 4); | |
if ($fromAppId !== $this->appId) { | |
throw new \Exception('', AESErrorCode::ValidateAppidError); | |
} | |
return $xml; | |
} | |
private function pkcs7pad($str, $blockSize=16) { | |
$padLen = $blockSize - strlen($str) % $blockSize; | |
return $str . str_repeat(chr($padLen), $padLen); | |
} | |
private function pkcs7unpad($str, $blockSize=16) { | |
$padLen = ord(substr($str, -1)); | |
if ($padLen < 1 || $padLen > $blockSize) { | |
return $str; | |
} | |
return substr($str, 0, -$padLen); | |
} | |
private function signature($timestamp, $nonce, $data) { | |
$a = [$this->token, $timestamp, $nonce, $data]; | |
sort($a, SORT_STRING); | |
return sha1(implode('', $a)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment