API 验证签名 方案


钢铁侠缥缈
钢铁侠缥缈 2022-09-15 14:25:09 52877
分类专栏: 资讯

涉及到知识点

1.AES 非加密

2.RSA对称

3.LUA 包含中文字符串切割成table

4.JS LUA PHP 对字符串排序

接口安全设计
一、使用token进行用户身份认证
二、使用sign 防止传入参数被篡改
1、理解对称加密与非对称加密
加称加密:加密与解密用的是同样的密钥,特点:快速,简单、效率高、密钥越大,加密越强。常用:AES
非对称加密: 使用了一对密钥,公钥和私钥。私钥只能由一方安全保管,不能外泄,而公钥则可以发给任何请求它的人。非对称加密使用这对密钥中的一个进行加密,而解密则需要另一个密钥。常用:RSA
2、加解密思路
客户端的加密思路须要3步:
i. 将AES密钥使用RAS公钥进行加密
ii. 把参数数据进行AES 加密
iii. 将第i与第ii生成的内容传给服务端。
服务端的解密思路需3步:
i. 获取到client传过来的AES密钥密文和内容密文;
ii. 使用RSA私钥解密从client拿到的AES密钥密文。
iii. 再使用第ii步解密出来的明文密钥。通过AES解密内容的密文。
三、用时间戳防止暴力请求
具体操作:客户端在形成sign值时,除了使用所有参数和token外,再加一个发起请求时的时间戳。即
sign值 = 所有非空参数升序排序+token+timestamp
后端根据当前时间和sign值的时间戳进行比较,差值超过一段时间则不予放行。

JS 方案

import {JSEncrypt} from 'jsencrypt';
import CryptoJS from 'crypto-js';
import md5 from 'js-md5'

// 公钥
const KEY = `-----BEGIN PUBLIC KEY-----
*******************
-----END PUBLIC KEY-----`;

export const encode = (params: any) => {
  const isEmptyObj = Object.prototype.toString.call(params) === '[object Object]' && Object.keys(params).length === 0;
  const isEmptyAry = Array.isArray(params) && params.length === 0;

  if (!params || isEmptyObj || isEmptyAry) {
    params = {'dft':'GlGGJ8HNtxlE27JLPqSHRf'};
  }

  // 1、对参数排序
  const str = JSON.stringify(params).replace(/"/g, '');

  // 2、对排序后的参数去掉数据类型 如果参数为空 对 null 进行stringify
  const hashString = Array.from(str).sort().join('');
  console.log('hash排序后', hashString);
  const hashMd5 = md5(hashString);
  console.log('hash md5后', hashMd5);
  // 3、随机生成Aes加密数据的key,用秒级时间戳,同时服务器对时间戳做校验
  const key = new Date().getTime() + 'abc'; // 必须16位 才是aes-128-cbc
  // 4、用公钥对AesKey加密
  const jsEncrypt = new JSEncrypt();
  jsEncrypt.setPublicKey(KEY as string);
  const keyEncode = jsEncrypt.encrypt(key);
  // console.log(hashArray, '用公钥对AesKey加密');
  // 5、用AesKey对数据加密
  const dataEncode = CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(hashMd5), CryptoJS.enc.Utf8.parse(key), {
      // mode: CryptoJS.mode.ECB,
      iv: CryptoJS.enc.Utf8.parse(key),
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7
  }).toString();
  console.log(dataEncode, '用AesKey对数据加密');
  // 调试用、对加密数据的解密
  // const cipherData1 = CryptoJS.DES.decrypt(cipherData, CryptoJS.enc.Utf8.parse(key), {
  //     mode: CryptoJS.mode.ECB,
  // }).toString(CryptoJS.enc.Utf8);
  // 6、 数据给到后端时uriEncode一下,base64会导致数据对不上
  // 7、 在请求后端API时统一放到header中即可
  return {sign: encodeURIComponent(keyEncode), hash: encodeURIComponent(dataEncode),beforeMd5:hashString};
};

php 方案

  /**
     * Api 签名校验
     * @param \EasySwoole\Http\Request $request
     * @param \EasySwoole\Http\Response $response
     * @return bool
     * @throws \ReflectionException
     */
    public function decode(Request $request, Response $response): bool
    {


        /** 组合参数 */
        $params = $request->getRequestParam();
        $raw = $request->getBody()->__toString();
        $rawParams = json_decode($raw, true);
        if ($rawParams) {
            $params = $rawParams + $params; //重点前面数组覆盖后面数组
        }

        /** 组合参数 */
        $signHeader = urldecode($request->getHeader(AppConst::HEADER_SIGN_SIGN)[0] ?? null);
        $hashHeader = urldecode($request->getHeader(AppConst::HEADER_SIGN_HASH)[0] ?? null);


        if (superEmpty($signHeader) || superEmpty($hashHeader)) {
            return false;
        }

        /** 解析header签名 */
        $rasConf = config("sign.ras", true);
        $private = $rasConf['private'] ?? null;

        /** 获取不到参数后直接报错 */
        if (superEmpty($private)) {
            return false;
        }

        $aesKey = Ras::decode($signHeader, $private);
        if (superEmpty($aesKey)) {
            return false;
        }

        /** 解析参数中的签名 */
        $result = AesUtil::decode($hashHeader, 'AES-128-CBC', $aesKey, 1, $aesKey);
        $string = preg_replace('/\"/', '', json_encode(superEmpty($params) ? null : $params, JSON_UNESCAPED_UNICODE));

	/**这里是php的重点 先对字符串进行中文分割 一般涉及到中文字符串的用mb相关函数**/
        $stringParts = mb_str_split($string);
        sort($stringParts); //根据ASSIC进行排序
        $string = implode($stringParts); // 拼接成字符串
        $md5 = md5($string);

        if ($md5 != $result) {
            return false;
        }

        // 解析失败 报错
        if (superEmpty($result)) {
            return false;
        }

        return true;
    }
<?php

namespace App\Util;

class AesUtil
{

    /**
     * 加密
     *
     * @param string $data 加密明文
     * @param string $method 加密方法 (DES-ECB,DES-CBC,DES-CTR,DES-OFB,DES-CFB)
     * @param string $aesKey 加密密钥
     * @param int $options 数据格式  (0、OPENSSL_RAW_DATA=1 、OPENSSL_ZERO_PADDING=2 、OPENSSL_NO_PADDING=3)
     * @param string $vi 密初始化向量(可选)
     * @return string
     */
    public static function encode(string $data, string $method, string $aesKey, int $options = OPENSSL_RAW_DATA, string $vi = ""): string
    {
        return base64_encode(openssl_encrypt($data, $method, $aesKey, $options, $vi));
    }

    /**
     * 解密
     *
     * @param string $data 加密明文
     * @param string $method 加密方法 (DES-ECB,DES-CBC,DES-CTR,DES-OFB,DES-CFB)
     * @param string $aesKey 加密密钥
     * @param int $options 数据格式  (0、OPENSSL_RAW_DATA=1 、OPENSSL_ZERO_PADDING=2 、OPENSSL_NO_PADDING=3)
     * @param string $vi 密初始化向量(可选)
     * @return string
     */
    public static function decode(string $data, string $method, string $aesKey, int $options = OPENSSL_RAW_DATA, string $vi = ""): string
    {
        return openssl_decrypt(base64_decode($data), $method, $aesKey, $options, $vi);
    }
}

class RasUtil
{
    /**
     * 加密
     */
    public static function encode(string $data, string $key, $padding = OPENSSL_PKCS1_PADDING): string
    {
        $result = '';
        openssl_public_encrypt($data, $result, $key, $padding);
        return $result;
    }

    /**
     * 解密
     */
    public static function decode(string $data, string $key, $padding = OPENSSL_PKCS1_PADDING): string
    {
        $result = '';
        openssl_private_decrypt(base64_decode($data), $result, $key, $padding);
        return $result;
    }
}

lua 方案



/**这里是LUA的重点 先对字符串进行中文分割 一般涉及到中文字符串的用mb相关函数**/
--将字符串转为table
function GetWordTable(str)
  local temp = {}
  for uchar in string.gmatch(str, "[%z\1-\127\194-\244][\128-\191]*") do
      temp[#temp+1] = uchar
  end
  return temp
end

local testT = GetWordTable(newStr) --%z:匹配0 *:表示0个至任意多个

网站声明:如果转载,请联系本站管理员。否则一切后果自行承担。

本文链接:https://www.xckfsq.com/news/show.html?id=64
赞同 0
评论 0 条
钢铁侠缥缈L0
粉丝 0 发表 11 + 关注 私信
上周热门
如何使用 StarRocks 管理和优化数据湖中的数据?  2950
【软件正版化】软件正版化工作要点  2872
统信UOS试玩黑神话:悟空  2833
信刻光盘安全隔离与信息交换系统  2728
镜舟科技与中启乘数科技达成战略合作,共筑数据服务新生态  1261
grub引导程序无法找到指定设备和分区  1225
华为全联接大会2024丨软通动力分论坛精彩议程抢先看!  165
2024海洋能源产业融合发展论坛暨博览会同期活动-海洋能源与数字化智能化论坛成功举办  163
点击报名 | 京东2025校招进校行程预告  163
华为纯血鸿蒙正式版9月底见!但Mate 70的内情还得接着挖...  158
本周热议
我的信创开放社区兼职赚钱历程 40
今天你签到了吗? 27
如何玩转信创开放社区—从小白进阶到专家 15
信创开放社区邀请他人注册的具体步骤如下 15
方德桌面操作系统 14
用抖音玩法闯信创开放社区——用平台宣传企业产品服务 13
我有15积分有什么用? 13
如何让你先人一步获得悬赏问题信息?(创作者必看) 12
2024中国信创产业发展大会暨中国信息科技创新与应用博览会 9
中央国家机关政府采购中心:应当将CPU、操作系统符合安全可靠测评要求纳入采购需求 8

加入交流群

请使用微信扫一扫!