php后端aes加密前端解密

本文介绍了在PHP tp5.1框架和Vue.js环境下,如何实现前后端接口参数及返回值的AES加密,并进行了签名验证。前端使用CryptoJS进行加密和解密,后端在中间件中实现加密和解密,确保数据传输安全。同时,针对数组、对象和bool值的处理进行了特殊说明,以避免签名验证失败。
摘要由CSDN通过智能技术生成

项目场景:

领导要求公司后台的全部接口与前端交互时,所有参数以及返回值需要进行加密。后端语言 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);
    }
}

结束

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值