Skip to content

Instantly share code, notes, and snippets.

@SyuTingSong
Created April 23, 2015 09:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SyuTingSong/e42b0c8b1f677d9ebe0f to your computer and use it in GitHub Desktop.
Save SyuTingSong/e42b0c8b1f677d9ebe0f to your computer and use it in GitHub Desktop.
Another WeiXin Message Decryptor
<?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