开发者服务器接受消息推送回包报错?
$reply = generate_reply_message("success", $msg); // 生成回复消息
$encryptMsg = '';
$errCode = $pc->encryptMsg($reply, $nonce, $encryptMsg, $timeStamp); // 加密并生成 XML
// 生成回复消息
function generate_reply_message($content, $receivedMsg) {
$xml = simplexml_load_string($receivedMsg, 'SimpleXMLElement', LIBXML_NOCDATA);
$toUserName = $xml->FromUserName;
$fromUserName = $xml->ToUserName;
$content = htmlspecialchars($content, ENT_QUOTES, 'UTF-8'); // 转义内容
return "<xml>
<ToUserName><![CDATA[$toUserName]]></ToUserName>
<FromUserName><![CDATA[$fromUserName]]></FromUserName>
<CreateTime>" . time() . "</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[$content]]></Content>
</xml>";
}
public function encryptMsg($replyMsg, $nonce, &$encryptMsg, $timeStamp)
{
$logFilePath = __DIR__ . ''; // 日志文件路径
//error_log("Starting message encryption...\n", 3, $logFilePath);
// 记录加密前的关键参数
//error_log("Using EncodingAESKey: " . substr($this->encodingAesKey, 0, 10) . "...\n", 3, $logFilePath);
//error_log("Using AppId: $this->appId\n", 3,$logFilePath);
//error_log("Reply message to encrypt: $replyMsg\n", 3, $logFilePath);
$pc = new Prpcrypt($this->encodingAesKey);
// 调用加密函数
$array = $pc->encrypt($replyMsg, $this->appId);
$ret = $array[0];
if ($ret != 0) {
error_log("Encryption failed. Error Code: $ret\n", 3, $logFilePath);
return $ret;
}
// 获取加密后的密文
$encrypt = base64_encode($array[1]);
//error_log("Encrypted message: $encrypt\n", 3, $logFilePath);
// 加密完成后处理 Encrypt
//$encrypt = str_replace(['+', '/'], ['-', '_'], $encrypt);
//error_log("Encrypted message (URL Safe): $encrypt\n", 3, $logFilePath);
// 如果时间戳为 null,则生成当前时间戳
if ($timeStamp == null) {
$timeStamp = time();
}
//error_log("TimeStamp used for signing: $timeStamp\n", 3, $logFilePath);
// 生成安全签名
$sha1 = new SHA1;
// 记录参与签名的参数
//error_log("Signing parameters:\n", 3, $logFilePath);
//error_log("Token: $this->token\n", 3, $logFilePath);
//error_log("TimeStamp: $timeStamp\n", 3, $logFilePath);
//error_log("Nonce: $nonce\n", 3, $logFilePath);
//error_log("Encrypt: $encrypt\n", 3, $logFilePath);
// 调用签名生成
$array = $sha1->getSHA1($this->token, $timeStamp, $nonce, $encrypt);
$ret = $array[0];
if ($ret != 0) {
error_log("Signature generation failed. Error Code: $ret\n", 3, $logFilePath);
return $ret;
}
$signature = $array[1];
//error_log("Generated signature: $signature\n", 3, $logFilePath);
// 生成发送的 XML
$xmlparse = new XMLParse;
$encryptMsg = $xmlparse->generate($encrypt, $signature, $timeStamp, $nonce);
//error_log("Generated reply XML: $encryptMsg\n", 3, $logFilePath);
//error_log(str_repeat("*", 20) . "\n", 3, $logFilePath);
return ErrorCode::$OK;
}
public function encrypt($text, $appid)
{
$logFilePath = __DIR__ . ''; // 日志文件路径
try {
//error_log("Encrypt function called.\n", 3, $logFilePath);
// 检查 AppId 是否为空
if (empty($appid)) {
error_log("AppId is empty. Please check the configuration or input.\n", 3, $logFilePath);
throw new Exception("AppId cannot be empty.");
}
//error_log("AppId for Encryption: $appid\n", 3, $logFilePath);
// 1. 生成 16 字节随机字符串,填充到明文之前
$random = openssl_random_pseudo_bytes(16);
$text = $random . pack("N", strlen($text)) . $text . $appid;
error_log("FullStr: " . bin2hex($text) . "\n", 3, $logFilePath);
// 2. 验证 AES 密钥长度
if (strlen($this->key) != 32) {
error_log("Invalid AES Key: Length is not 32.\n", 3, $logFilePath);
throw new Exception("Invalid AES Key: Length must be 32 bytes.");
}
// 3. 使用自定义的填充方式对明文进行补位填充
$pkc_encoder = new PKCS7Encoder();
$text = $pkc_encoder->encode($text);
//error_log("Padded text length: " . strlen($text) . "\n", 3, $logFilePath);
// 4. 使用密钥的前 16 字节作为 IV
//$iv = substr($this->key, 0, 16);
$iv = $random; //openssl_random_pseudo_bytes(16);
//error_log("Generated IV(Hex): " . bin2hex($iv) . "\n", 3, $logFilePath);
// 5. 使用 openssl_encrypt 进行 AES 加密
$encrypted = openssl_encrypt($text, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $iv);
// 记录加密前的关键参数
//error_log("AESKey for Encrytion: " . bin2hex(substr($this->key, 0, 16)) . " Length: " . strlen($this->key) . "\n", 3, $this->logFilePath);
if ($encrypted === false) {
error_log("Error: openssl_encrypt failed.\n", 3, $logFilePath);
throw new Exception("Encryption failed");
}
// 6. 输出日志
//error_log("Final Encrypted Text: " . base64_encode($iv . $encrypted) . "\n", 3, $logFilePath);
// 7. 返回加密结果(密文和 IV)
return array(ErrorCode::$OK, $iv . $encrypted);
} catch (Exception $e) {
// 捕获异常并记录日志
error_log("Encryption Exception: " . $e->getMessage() . "\n", 3, $logFilePath);
return array(ErrorCode::$EncryptAESError, null);
}
}
public function generate($encrypt, $signature, $timestamp, $nonce)
{
// 使用 htmlspecialchars 进行 XML 转义
$format = "<xml>
<Encrypt><![CDATA[%s]]></Encrypt>
<MsgSignature><![CDATA[%s]]></MsgSignature>
<TimeStamp>%s</TimeStamp>
<Nonce><![CDATA[%s]]></Nonce>
</xml>";
return sprintf(
$format,
htmlspecialchars($encrypt, ENT_QUOTES, 'UTF-8'),
htmlspecialchars($signature, ENT_QUOTES, 'UTF-8'),
$timestamp,
htmlspecialchars($nonce, ENT_QUOTES, 'UTF-8')
);
// 记录日志
//error_log("XMLParse::generate called. Resulting XML:\n$result\n", 3, __DIR__ . '/wechat_received.log');
}
}
public function getSHA1($token, $timestamp, $nonce, $encrypt_msg)
{
$logFilePath = __DIR__ . ''; // 日志文件路径
// 参数校验
if (empty($token) || empty($timestamp) || empty($nonce) || empty($encrypt_msg)) {
error_log("Error: Invalid parameters for SHA1 generation.\n", 3, $logFilePath);
return array(ErrorCode::$InvalidParameter, null);
}
try {
// 强制转为字符串(即使输入是数字)
$timestamp = (string)$timestamp;
$nonce = (string)$nonce;
$token = (string)$token;
$encrypt_msg = (string)$encrypt_msg;
// 排序
//error_log("Signature parameters: \n Token: $token, TimeStamp: $timestamp, Nonce: $nonce, Encrypt: $encrypt_msg\n", 3, $logFilePath);
$array = [$token, $timestamp, $nonce, $encrypt_msg];
sort($array, SORT_STRING);
//error_log("Sorted Parameters: " . print_r($array, true), 3, $logFilePath);
// 拼接字符串
$str = implode('', $array);
//error_log("String for SHA1: " . $str . "\n", 3, $logFilePath);
// 计算 SHA1
$signature = sha1($str);
//error_log("Expected Signature: 097a5aa62b20586aa87daecd54e5659c477c2232", 3, $logFilePath);
//error_log("Actual Signature: $signature" . "\n", 3, $logFilePath);
return array(ErrorCode::$OK, $signature);
} catch (Exception $e) {
// 异常处理
error_log("SHA1 generation failed: " . $e->getMessage() . "\n", 3, $logFilePath);
return array(ErrorCode::$ComputeSignatureError, null);
}
}
//以上是关键代码,基于官方文档修改,部分方法配合PHP版本有升级。接口测试时显示解码错误。根据官方文档推测服务器解码方式,用以下python模拟测试可以正确解码,但服务器上不能通过,因为无法知晓微信服务器的解码机制,所以实在看不出从哪里修改。
import base64
import hashlib
from Crypto.Cipher import AES
import struct
def verify_signature(token, encrypt, timestamp, nonce, msg_signature):
"""
验证消息签名是否正确
"""
# 按字典序排序参数值
params = sorted([encrypt, nonce, timestamp, token])
# 拼接字符串
sorted_str = ''.join(params)
# 计算SHA1
sha1 = hashlib.sha1()
sha1.update(sorted_str.encode('utf-8'))
calculated_signature = sha1.hexdigest()
print(f"[签名验证] 计算签名: {calculated_signature}")
print(f"[签名验证] 收到签名: {msg_signature}")
return calculated_signature == msg_signature
def decrypt_message(encoding_aes_key, encrypt):
"""
执行AES解密并解析明文
"""
try:
# 1. 补全并解码AESKey
aes_key = base64.b64decode(encoding_aes_key + "==")
if len(aes_key) != 32:
print(f"[错误] AESKey长度不正确: {len(aes_key)}字节,应为32字节")
return None
# 2. Base64解码加密数据(处理URL安全字符)
encrypt = encrypt.replace('-', '+').replace('_', '/')
encrypted_data = base64.b64decode(encrypt)
print(f"[解密] Base64解码后数据长度: {len(encrypted_data)}字节")
# 3. 提取IV(前16字节)
iv = encrypted_data[:16]
ciphertext = encrypted_data[16:]
print(f"[解密] IV(HEX): {iv.hex()}")
# 4. AES解密
cipher = AES.new(aes_key, AES.MODE_CBC, iv=iv)
decrypted = cipher.decrypt(ciphertext)
# 5. 去除PKCS7填充
pad = decrypted[-1]
decrypted = decrypted[:-pad]
print(f"[解密] 去除填充后长度: {len(decrypted)}字节")
# 6. 解析明文结构
random = decrypted[:16]
msg_len = struct.unpack('>I', decrypted[16:20])[0] # 大端序解包
msg = decrypted[20:20+msg_len].decode('utf-8')
appid = decrypted[20+msg_len:].decode('utf-8')
print("\n======= 解密结果 =======")
print(f"Random(HEX): {random.hex()}")
print(f"消息长度: {msg_len}字节")
print(f"消息内容: {msg}")
print(f"AppID: {appid}")
print("=======================")
return msg
except Exception as e:
print(f"[错误] 解密过程中发生异常: {str(e)}")
return None
# 测试用例(替换为你的实际数据)
if __name__ == "__main__":
# 输入参数(从加密消息中获取)
encrypt = ""
timestamp = ""
nonce = ""
msg_signature = ""
# 服务器存储的凭证
token = ""
encoding_aes_key = "" # 你的EncodingAESKey
print("===== 开始签名验证 =====")
if verify_signature(token, encrypt, timestamp, nonce, msg_signature):
print("\n===== 签名验证通过,开始解密 =====")
decrypt_message(encoding_aes_key, encrypt)
else:
print("\n[错误] 签名验证失败,终止处理")