php后端aes加密前端解密
目录
php后端aes加密前端解密
项目场景:
领导要求公司后台的全部接口与前端交互时,所有参数以及返回值需要进行加密。后端语言 PHP,使用框架 tp5.1,前端vus.js,使用CryptoJS加解密
效果如下:
前端传参:
后端返回:
前端代码:
前端使用CryptoJS来加密。说明:为了数据安全性,前端需要额外传递一个参数sign给到后端,后端按前端的sign生成规则重新生成一个sign并与前端传递的sign进行对比校验,前端的sign生成代码如下:
function objKeySort(obj) { //排序的函数
// obj:原始的请求参数对象
// obj:{"username":"111111","password":"123456","terminal":"PC"};
var word_key =Object.assign({},obj)
// console.log('cd='+JSON.stringify(obj))
var newkey = Object.keys(word_key).sort();
//先用Object内置类的keys方法获取要排序对象的属性名,再利用Array原型上的sort方法对获取的属性名进行排序,newkey是一个数组
var newObj = {}; //创建一个新的对象,用于存放排好序的键值对
for (var i = 0; i < newkey.length; i++) { //遍历newkey数组
newObj[newkey[i]] = obj[newkey[i]]; //向新创建的对象中按照排好的顺序依次增加键值对
}
var str = '';
for (var key in newObj) {
// if (newObj[key]===undefined) {
// newObj[key] = '';
// }
if(typeof newObj[key]!='object'&& typeof newObj[key]!='boolean'&& typeof newObj[key]!='undefined' ){
str += `${key}=${newObj[key]}&`;
}
}
str = str.substr(0, str.length - 1);
str+='&'+key_str+'&'+iv_str;
// console.log(str)
var md5_str = md5(str);
return md5_str; //返回排好序的新对象
}
说明:前后端的签名sign在生成时,前端的参数包含数组、对象、bool值时,这些值后端接收的值与前端会不一致,导致验证签名失败,所以前端在生成sign时,过滤掉数组、对象以及bool值。
let str = {"username":"111111","password":"123456","terminal":"PC","sign":"前面生成的sign"};
let srcs = CryptoJS.enc.Utf8.parse(JSON.stringify(str));
var encrypted = CryptoJS.AES.encrypt(srcs, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
var res = CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
// 注意,这里最后使用了base64加密,不用的话,后端无法正确解密。
console.log( res );
// d99HVdbHhtW1HAsD53hYSjPcSB5pN4fDpXzJHpBY2gw0MBOq8rPZFKDKyOkOccBWOZjFwE9Mqe2TUNDEn6Vk9Q==
前端解密代码:
//解密
key = CryptoJS.enc.Utf8.parse("1572329129539WZH");//十六位十六进制数作为密钥,可自行设置
iv = CryptoJS.enc.Utf8.parse("ZZWBKJ_ZHIHUAWEI");//十六位十六进制数作为密钥偏移量,可自行设置
let str = "d99HVdbHhtW1HAsD53hYSjPcSB5pN4fDpXzJHpBY2gw0MBOq8rPZFKDKyOkOccBWOZjFwE9Mqe2TUNDEn6Vk9Q=="; // 前面加密生成的数据
var decrypted = CryptoJS.AES.decrypt(str, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
let res = JSON.parse(CryptoJS.enc.Utf8.stringify(decrypted));
console.log(res);
后端代码:
后端加密解密放在中间件里面实现
加密代码如下:
namespace app\http\middleware;
use think\Response;
class Encrypt
{
/**
* 加密中间件
*/
public function handle($request, \Closure $next)
{
$response = $next($request);
$content = $response->getContent();
$content = json_decode($content,true);
$data = $content['data'] ?? [];
if (!empty($data)) {
// 添加中间件执行代码
$mini_secret = '1572329129539WZH';
$mini_iv = 'ZZWBKJ_ZHIHUAWEI';
$str = json_encode($data);
$mini_sign = openssl_encrypt($str, 'AES-128-CBC',$mini_secret,0, $mini_iv);
$content['data'] = ['result' => $mini_sign];
$response->content(json_encode($content));
}
return $response;
}
}
解密代码如下:
namespace app\http\middleware;
use app\common\exception\JsonException;
use app\common\enums\ErrorCode;
use think\facade\Response;
use think\Request;
class Decrypt
{
/**
* 解密中间件
*/
public function handle($request, \Closure $next)
{
// 这里是解决跨域问题
if(!$request->isCli())
{
header('Access-Control-Allow-Origin:*');
header('Access-Control-Allow-Methods:GET,POST,PUT,DELETE,PATCH,OPTIONS');
header('Access-Control-Allow-Headers:Content-Type, X-ELEME-USERID, X-Eleme-RequestID, X-Shard,X-Shard, X-Eleme-RequestID,X-Companyid,X-Adminid,X-Token');
}
if ($request->isOptions())
{
return Response::create()->send();
die();
}
// 添加中间件执行代码
$mini_sign = $request->param('mini_sign'); // 前端传的加密后的参数
if (!empty($mini_sign)) {
// 参数解密
// 获取加密密钥和向量(跟前端保持一致,可写在配置里)
$mini_secret = '1572329129539WZH';
$mini_iv = 'ZZWBKJ_ZHIHUAWEI';
//2:通过request获取前端传入的 $mini_sign数据
$mini_sign = str_replace(' ','+',$mini_sign);
//3:对$str_one数据进行解密
$str = openssl_decrypt($mini_sign, 'AES-128-CBC',$mini_secret,0, $mini_iv);
//4:对解密后的数据进行 json_decode() 处理
$arr_one = json_decode($str, JSON_UNESCAPED_UNICODE);
//5:获取签名 sign
$sign = $arr_one['sign'];
//6:去除$arr_one数组中的sign键值对
unset($arr_one['sign']);
$signData = $arr_one;
//7:对数组$arr_one,依据键值,做升序排序(去掉数组,对象)
foreach ($signData as $key => $val) {
if (is_array($val) || is_object($val) || is_bool($val)) {
unset($signData[$key]);
}
}
ksort($signData);
//8:对排序后的数组 $arr_one,构造成字符串$str_one,构建的字符串格式类似于 A=a&B=b&C=c
$str_one = http_build_query($signData);
//9:构建字符串
$str_two = $str_one . '&'. $mini_secret . '&' .$mini_iv;
$str_two = urldecode($str_two);
//10:生成验证签名字符串 $check_sign
$check_sign = md5($str_two);
//11:判断 $sign 是否与 $check_sign 相同;
if ($check_sign != $sign) {
throw new JsonException(ErrorCode::VALIDATION_FAILED, "签名不合法。");
}
// 这里将原始的请求参数加到请求对象里面去,这样控制器里面通过request对象就可以拿到对应参数
if ($request->isGet()) {
$request->withGet($arr_one);
}
if ($request->isPost()) {
$request->withPost($arr_one);
}
// 这里的代码是为了解决,部分控制器使用request()->param()方法获取不到参数的问题
foreach ($arr_one as $key => $val) {
$request->__set($key,$val);
}
}
return $next($request);
}
}