人生的意志和劳动将创造奇迹般的奇迹。 --涅克拉索夫
继续昨天,将微信加密类库剩下的两个类分析完。
Prpcrypt.php
这个文件主要功能为:根据给出的AESKey,和公司应用ID,对明文数据加密,对密文数据解密。这儿需要注意的是,想要使用微信的加密类库,那么PHP的 Mcrypt 扩展是必不可少的。这个类,核心函数就两个 加密 、 解密。所以就不整个代码复制了,直接按函数分析好了。
函数一:encrypt,很明显,这是一个加密函数。接下来,我们从函数中不难看出,微信在对数据进行了多次扰码添加,多次加入杂项,进一步加强数据安全性。本类大量使用到了Mcrypt的函数,想要明白具体实现还是需要去看函数的具体定义http://php.net/manual/zh/ref.mcrypt.php
public function encrypt($text, $corpid){
try {
//获得16位随机字符串,填充到明文之前
$random = $this->getRandomStr();
//这里核心需要理解的是pack函数,Pack函数是PHP中一个将指定数据[strlen($text)]按照特定的格式[N]打包成二进制数据的函数。
$text = $random . pack("N", strlen($text)) . $text . $corpid;
//获取加密算法“MCRYPT_RIJNDAEL_128”的分组大小
$size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128);
//用“CBC”模式,“MCRYPT_RIJNDAEL_128”算法开启一个模块
$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);
//使用BASE64对加密后的字符串进行编码
return array(ErrorCode::OK, base64_encode($encrypted));
} catch (\Exception $e) {
print $e;
return array(ErrorCode::EncryptAESError, null);
}
}
函数二:decrypt,解密函数,这个函数正好是和加密函数是反着运行的。同样的需要对二进制和Mcypt函数有一定的认识。
public function decrypt($encrypted, $corpid){
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_corpid = substr($content, $xml_len + 4);
} catch (\Exception $e) {
print $e;
return array(ErrorCode::IllegalBuffer, null);
}
if ($from_corpid != $corpid)
return array(ErrorCode::ValidateCorpidError, null);
return array(0, $xml_content);
}
WxCrypt.php
这个文件主要是各个功能的封装,将昨天说及的各个类库给联系起来,用以实现各个具体的场景。由于类太长,这里将以函数为最小单元进行剖析。
函数一:VerifyURL,从名字就可以看出这是一个验证URL的函数,也就是身份认证。
/**
* @param string $sMsgSignature 签名串,对应URL参数的msg_signature
* @param string $sTimeStamp 时间戳,对应URL参数的timestamp
* @param string $sNonce 随机串,对应URL参数的nonce
* @param string $sEchoStr 随机串,对应URL参数的echostr
* @param string $sReplyEchoStr 解密之后的echostr,当return返回0时有效
* @return int 成功0,失败返回对应的错误码
*/
public function VerifyURL($sMsgSignature, $sTimeStamp, $sNonce, $sEchoStr, &$sReplyEchoStr){
//从微信文档上也可以看出来他的AESKey必须是一个长度为43的字符串。
if (strlen($this->m_sEncodingAesKey) != 43) {
return ErrorCode::IllegalAesKey;
}
//初始化类,传入关键Key
$pc = new Prpcrypt($this->m_sEncodingAesKey);
//根据接口传来的相关数据,本地计算生成
$sha1 = new Sha1();
$array = $sha1->getSHA1($this->m_sToken, $sTimeStamp, $sNonce, $sEchoStr);
$ret = $array[0];
if ($ret != 0) {
return $ret;
}
$signature = $array[1];
//比较接口给出的令牌和计算出的令牌是否一致,不一致则为非法
if ($signature != $sMsgSignature) {
return ErrorCode::ValidateSignatureError;
}
//调用解密函数,解密数据。
$result = $pc->decrypt($sEchoStr, $this->m_sCorpid);
if ($result[0] != 0) {
return $result[0];
}
$sReplyEchoStr = $result[1];
return ErrorCode::OK;
}
函数二:EncryptMsg,这个函数主要是将用户拼接后的明文xml字符串,进行加密。
public function EncryptMsg($sReplyMsg, $sTimeStamp, $sNonce, &$sEncryptMsg) {
$pc = new Prpcrypt($this->m_sEncodingAesKey);
//加密数据
$array = $pc->encrypt($sReplyMsg, $this->m_sCorpid);
$ret = $array[0];
if ($ret != 0) {
return $ret;
}
if ($sTimeStamp == null) {
$sTimeStamp = time();
}
$encrypt = $array[1];
//生成安全签名
$sha1 = new Sha1();
$array = $sha1->getSHA1($this->m_sToken, $sTimeStamp, $sNonce, $encrypt);
$ret = $array[0];
if ($ret != 0) {
return $ret;
}
$signature = $array[1];
//生成发送的xml
$sEncryptMsg = $this->generate($encrypt, $signature, $sTimeStamp, $sNonce);
return ErrorCode::OK;
}
函数三:generate, 这个函数的主要作用就是,根据参数组合特定的XML格式字符串。
public 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);
}
函数四:DecryptMsg,解密函数,这个函数主要是验证用户身份,同时解密微信服务器给出的密文数据。
public function DecryptMsg($sMsgSignature, $sTimeStamp = null, $sNonce, $sPostData, &$sMsg) {
if (strlen($this->m_sEncodingAesKey) != 43) {
return ErrorCode::IllegalAesKey;
}
$pc = new Prpcrypt($this->m_sEncodingAesKey);
//提取密文
if ($sTimeStamp == null) {
$sTimeStamp = time();
}
$encrypt = $sPostData['Encrypt'];
//验证安全签名
$sha1 = new Sha1();
$array = $sha1->getSHA1($this->m_sToken, $sTimeStamp, $sNonce, $encrypt);
$ret = $array[0];
if ($ret != 0) {
return $ret;
}
$signature = $array[1];
if ($signature != $sMsgSignature) {
return ErrorCode::ValidateSignatureError;
}
//消息解密
$result = $pc->decrypt($encrypt, $this->m_sCorpid);
if ($result[0] != 0) {
return $result[0];
}
$sMsg = $result[1];
return ErrorCode::OK;
}
最后说两句,我认为加密就是干扰和运算,我们从上面的加密就可以看出,其中大量使用的PHP的Mcrypt类库函数,以及进行二进制相关运算。在后面的博客中我将单独聊聊这一块。上面的加密重在会用,没有计算机基础很难理解!
本文由 陌上花开 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Jul 1, 2016 at 06:15 am