应用场景
在微信小程序的开发中,我们经常需要从微信端获取一些处理,以方便我们的程序操作处理,如“从微信端获取走步数据”,“从微信端获取手机号”等,而这些数据,考虑到安全的问题,一般需要根据微信的相关文档,进行数据获取、解密,然后,才能供我们使用!但是,微信的文档写的真是不尽人意,所以,今天,就“走步数据”的获取解密,就关键代码,简单地说说!
相关知识点
对称加密: 在加密和解密过程中使用相同的密钥, 或是两个可以简单地相互推算的密钥的加密算法.(微信步数,采用的就是【对称加密】)
非对称加密: 也称为公开加密, 它需要一个密钥对, 一个是公钥, 一个是私钥, 一个负责加密, 一个负责解密.
对称加密在性能上要优于非对称加密, 但是安全性低于非对称加密.
关键函数
1、对称加密函数
openssl_encrypt($data, $method, $key, $options = 0, $iv = '') : 以指定方式 method 和密钥 key 加密 data, 返回 false 或加密后的数据.
2、对称解密函数
openssl_decrypt($data, $method, $key, $options = 0, $iv = '') : 解密数据.
参数注解
$data : 明文
$method : 加密算法
$key : 密钥
$options :
- 0 : 自动对明文进行 padding, 返回的数据经过 base64 编码.
- 1 : OPENSSL_RAW_DATA, 自动对明文进行 padding, 但返回的结果未经过 base64 编码.
- 2 : OPENSSL_ZERO_PADDING, 自动对明文进行 0 填充, 返回的结果经过 base64 编码. 但是, openssl 不推荐 0 填充的方式, 即使选择此项也不会自动进行 padding, 仍需手动 padding.
$iv : 非空的初始化向量, 不使用此项会抛出一个警告. 如果未进行手动填充, 则返回加密失败.
其他函数
1、openssl_cipher_iv_length($method) : 获取 method 要求的初始化向量的长度.
2、openssl_random_pseudo_bytes($length) : 生成指定长度的伪随机字符串.
3、hash_mac($method, $data, $key, $raw_out) : 生成带有密钥的哈希值.
参数注解:
- method : 加密算法
- data : 明文
- key : 密钥
- raw_output :
- TRUE : 输出原始二进制数据
- FALSE : 输出长度固定的小写 16 进制字符串
加密方式
主流的对称加密方式有DES, AES。这两种加密方式都属于分组加密,具体操作是,先将明文分成多个等长的模块,然后,进行加密。
DES加密
DES加密的秘钥长度为64位(bit),但实际应用中,有效的使用时56位(bit),剩余的8位(bit)作为奇偶校验位。如果秘钥长度不足8个字节(Byte, 1Byte=8bit),
将会用“\0”补充到8个字节,比如,秘钥是“abcde”,补充完整,就是“abcde/0/0/0”,也就是说,“abcde”加密后的密文与“abcde/0/0/0”加密后的密文是一样的。
明文按照64位(bit)分组,64位在UTF-8编码下为8个字节长度,最后一组,不足64位需要填充数据,分组后的明文组和秘钥,按位(bit)替换,或交换,形成密文组。
AES加密
AES加密的分组长度为128位(bit),即每个分组为16个字节Byte(每个字节8位bit)。秘钥的长度,根据加密方式的不同,可以是128位,192位,256位。与DES加密一样,秘钥的长度,超过指定的长度时,超出会无效,不足时,以“\0”,补充到指定的长度。
AES加密的长度表
AEC |
密钥长度 ( 位 ) |
分组长度 ( 位 ) |
AES-128 |
128 |
128 |
AES-192 |
192 |
128 |
AES-256 |
256 |
128 |
代码部分
代码一 :此部分代码,负责,接收参数,规避、检测一些特殊情况,为后续解密数据做准备(加密数据,由前端返回,这里不作获取操作)
/**
* 每次进入小程序,把前端推送的走步数据,存入到【组成员表】
*/
public function save_member_steps()
{
$request = $this->get;
//$request['step_data'] = "M4OKIk0kX8Zmx5E/y0xfzyVD+wWgGhcLcd+7GrMyXSmhnXTljZxLqpkNB8Smut1nj440Amdrc3HlI/g5VqY";
//$request['iv'] = "QUzF4rs6Khat27nZE8Px9w==";
//$request['openid'] = "oqahZ5LbByVHYBrH5PldKoruXtPI";
// 获取相关的参数,并判断
$userid = isset($request['userid']) ? $request['userid'] : 0;
if(!$userid){
return $this->export->get_export($this->errors[506]['error_code'], $this->errors, []);
}
// 从微信获取步数
$step_data_str = isset($request['step_data']) && !empty($request['step_data']) ? strval($request['step_data']) : '';
if(!$step_data_str){
return $this->export->get_export($this->errors[512]['error_code'], $this->errors, []);
}
// 获取步数微信端参数
$iv = !empty($request['iv']) ? strval($request['iv']) : '';
// 获取步数微信端参数
$openid = !empty($request['openid']) ? strval($request['openid']) : '';
$this->load->dao('active_step_dao');
// 解密微信端获取步数数据
$step_datas = $this->process_wx_step_data($openid, $iv, $step_data_str);
// 步数数据更新到user_step_everyday_log表
$update_everyday_log = $this->update_step_log_data($userid, $step_datas);
return $update_everyday_log ? true : false;
}
/**
* 获取用户的走步数据,更新到成员表
* @param $openid
* @param $iv
* @param $step_data_str
* @return array
*/
public function process_wx_step_data($openid, $iv, $step_data_str)
{
// 从微信记录里获取数据,比对一下,是否正确,如果不正确,更新【组成员步数】,团表里的总步数累加
$this->load->service('user_wechat_service');
$step_data = $this->user_wechat_service->auth_step_data($openid, $iv, $step_data_str, 1);
$stepInfoList = [];
if(isset($step_data['stepInfoList']) && !empty($step_data['stepInfoList'])){
$stepInfoList = $step_data['stepInfoList'];
foreach ($stepInfoList as $key=>$val) {
$stepInfoList[$key]['time'] = date('Y-m-d', $val['timestamp']);
}
$stepInfoList = array_column($stepInfoList, null, 'time');
}
return $stepInfoList;
}
代码二 此部分代码,接上代码user_wechat_service->auth_step_data,主要负责,对微信数据进行解密,返回解密后的数据
/**
* 获取微信端用户【走步】数据
* @param $openid 用户小程序openid
* @param $iv 小程序返回的iv字段
* @param $step_data_str 小程序返回的走步加密字段
* @param $plat_type 1 用户端 2 教练端
* @return array
*/
public function auth_step_data($openid, $iv, $step_data_str, $plat_type)
{
if (empty($openid) || empty($iv) || empty($step_data_str) || empty($plat_type)) {
return [];
}
$decrypt_data = $this->_get_encrypted_data($openid, $step_data_str, $iv, $plat_type);
return $decrypt_data;
}
/**
* 对微信加密的数据进行解密
* @param $openid
* @param $encrypted_data
* @param $iv
* @return string
*/
private function _get_encrypted_data($openid, $encrypted_data, $iv, $plat_type)
{
$result = '';
if (empty($openid) || empty($encrypted_data) || empty($iv) || empty($plat_type)) {
return $result;
}
$this->load->dao('user_wechat_dao');
$user = $this->user_wechat_dao->get_userid($openid, $plat_type);
if (empty($user)) {
return $result;
}
$this->load->config('dict/wechat_config');
$dict_name = $plat_type == self::PLAT_TYPE_USER ? 'dict_wechat_user' : 'dict_wechat_coach';
$app_config = $this->config->item($dict_name);
$result = $this->_decrypt_data($app_config['app_id'], $user['sessionkey'], $encrypted_data, $iv);
return $result;
}
/**
* 最终的步骤 - 解密数据
* @param $appid
* @param $session_key
* @param $encrypted_data
* @param $iv
* @desc VALIDATE_LEN_NUM = 24 (长度限制)
* @return mixed|string
*/
private function _decrypt_data($appid, $session_key, $encrypted_data, $iv)
{
$result = '';
if (strlen($session_key) != self::VALIDATE_LEN_NUM || strlen($iv) != self::VALIDATE_LEN_NUM) {
return $result;
}
$aes_key = base64_decode(str_replace(' ','+',$session_key));
$aes_iv = base64_decode(str_replace(' ','+',$iv));
$aes_cipher = base64_decode(str_replace(' ','+',$encrypted_data));
$data = openssl_decrypt($aes_cipher, "AES-128-CBC", $aes_key, 1, $aes_iv);
$data = json_decode($data, TRUE);
if (empty($data)) {
return $result;
}
if (!isset($data['watermark']['appid']) || $data['watermark']['appid'] != $appid) {
return $result;
}
return $data;
}
总结
微信数据的解密,关键的部分,就在于_decrypt_data方法,这里,对没用的字符进行了替换、解码,以及关键部分,使用解密函数openssl_decrypt()解密数据,这个函数对应的就是加密函数 openssl_encrypt(),它俩的参数都一样,分别是[明文,加密算法,秘钥,操作类型(0,1,2),非空的初始化向量],解密,加密,就靠它们了。因此,处理微信加密数据,关键在于“对称加密”的理解和操作实践!