WEB API 接口签名sign验证入门与实战

目录
  • 参考
  • 什么是加解密
  • 加密方式分类
  • * 对称加密技术
    
    • 非对称加密技术(RSA加密算法)(数字证书)
    •   * 场景1:公钥加密,私钥解密
      
      • 场景2:秘钥加密:数字签名,公钥解密:验证签名
    • MD5(完全不考虑解密,也叫哈希算法,散列算法)
    •   * Postman
      
      • Jmeter
  • 接口签名sign原理
  • * 什么是接口签名
    
    • 为什么做接口签名
  • 如何做接口签名
  • * 对所有参数按key按ASCⅡ码做升序排序
    
    • 把参数名和参数值连接成字符串
    • 把申请到的appKey和appSecret连接到字符串的头部
    • 用时间戳连接到字符串的尾部,可以增加失效检测
    • 增加随机数nonstr防止重放攻击
    • 把上述字符串进行md5加密
  • 常见签名算法示例
  • * 示例1
    

参考

B站讲的最好的接口加密解密以及接口签名sign原理

什么是加解密

加密:在网络上传输的原始数据(明文)经过加密算法加密后形成(密文)传输,防止被窃取
解密:将密文还原成原始数据

加密方式分类

对称式加密:对加密和解密使用同一个秘钥
非对称加密:非对称式加密需要两个秘钥(双钥),分别叫公钥和秘钥,这两把秘钥可以相互加解密。公钥公开的,不需要保密,而私钥是保密的。

对称加密技术

  • DES加密算法
  • AES加密算法
  • Base64算法

可以在https://www.bejson.com/enc/aesdes/体验
des和aes每次加密之后密文不一样,而Base64加密之后密文固定

非对称加密技术(RSA加密算法)(数字证书)

通过网站https://www.bejson.com/enc/rsa/在线生成公钥和私钥,并且可以进行公钥加密和私钥解密测试
在这里插入图片描述

场景1:公钥加密,私钥解密

两个用户:A和B,B有双钥;A想把一个数据报文通过加密方式发给B
在这里插入图片描述

场景2:秘钥加密:数字签名,公钥解密:验证签名

数字证书由来:由于公钥是公开的不安全,所以需要第三方的CA(数字证书颁发机构),对公钥加密,加密后的东西就叫 数字证书
数字证书包括:B用户基本信息及B的公钥的信息。X509的标准

CA:双钥,通过私钥加密

在这里插入图片描述
Fiddler不能直接抓取https协议的数据报文,需要安装一个数字证书
https = http + ssl安全传输协议

ssl安全传输协议:安全套接层,NetScape研发

在这里插入图片描述

MD5(完全不考虑解密,也叫哈希算法,散列算法)

Postman

在这里插入图片描述

Jmeter

在这里插入图片描述
${__digest(MD5,admin,)}
在这里插入图片描述

接口签名sign原理

什么是接口签名

接口签名:使用用户名,密码,时间戳和所有的排过序之后的参数组合起来,再加密得到的字符串,字符串是唯一的有权访问接口的鉴权码
用户名:appKey
密码:appSecret

为什么做接口签名

  • 防伪装攻击
  • 防篡改攻击
  • 防重放攻击
  • 防数据泄露

保证访问者的合法性。保证参数不被修改,确保请求的唯一性

如何做接口签名

对所有参数按key按ASCⅡ码做升序排序

比如参数是?c=1&b=2&a=3
排序之后是a,b,c

把参数名和参数值连接成字符串

a=1&b=2&c=3

把申请到的appKey和appSecret连接到字符串的头部

appKey=admin&appSecret=123&a=1&b=2&c=3

用时间戳连接到字符串的尾部,可以增加失效检测

比如1分钟内有效

appKey=admin&appSecret=123&a=1&b=2&c=3&timestamp=1666757432136

增加随机数nonstr防止重放攻击

nonstr可以是随机数,最好是uuid

appKey=admin&appSecret=123&a=1&b=2&c=3&timestamp=1666757432136&nonstr=123123

把上述字符串进行md5加密

String str = "appKey=admin&appSecret=123&a=1&b=2&c=3&timestamp=1666757432136&nonstr=123123";
String sign = md5(str);

常见签名算法示例

示例1

签名算法
签名算法描述如下:
1.将请求参数按参数名升序排序;
2.按请求参数名及参数值相互连接组成一个字符串:…;
3.将应用密钥分别添加到以上请求参数串的头部和尾部:<请求参数字符串>;
4.对该字符串进行MD5(全部大写),MD5后的字符串即是这些请求参数对应的签名;
5.该签名值使用sign参数一起和其它请求参数一起发送给服务开放平台。

伪代码:

Map<String,Object> paramsMap = new ...; // 参数

Set<String> keySet = paramsMap.keySet();
List<String> paramNames = new ArrayList<String>(keySet);
// 1.
Collections.sort(paramNames);

StringBuilder paramNameValue = new StringBuilder();
// 2.
for (String paramName : paramNames) {
    paramNameValue.append(paramName).append(paramsMap.get(paramName));
}
// 3.
String source = secret + paramNameValue.toString() + secret;
// 4.
String sign = md5(source);
// 5.
paramsMap.put("sign",sign);

代码示例

import java.io.IOException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.junit.Test;

import com.alibaba.fastjson.JSON;

import junit.framework.TestCase;

public class PostTest extends TestCase {

    @Test
    public void testPost() throws IOException {
      String appKey = "xxx";
        String secret = "xxx";
        // 业务参数
        Map jsonMap = new HashMap();
        jsonMap.put("dicCode", "terminalType");

        String json = JSON.toJSONString(jsonMap);
        json = URLEncoder.encode(json, "utf-8");

        // 系统参数
        Map param = new HashMap();
        param.put("name", "dictionaryItem.list");
        param.put("version", "1.0");
        param.put("app_key", appKey);
        param.put("data", json);
        param.put("timestamp", getTime());
        param.put("format", "json");

        String sign = buildSign(param, secret);
        param.put("sign", sign);

        /*
        // 最终请求数据
       {
           "name":"dictionaryItem.list",
           "version":"1.0",
           "app_key":"test",
           "data":"%7B%22dicCode%22%3A%22terminalType%22%7D",
           "timestamp":"2021-12-15 10:25:02",
           "format":"json",
           "sign":"4B291FFFFDD6F0E3FB9708AC0F7AC334"
       }

        */
        System.out.println("=====请求数据=====");
        String postJson = JSON.toJSONString(param);
        System.out.println(postJson);
        // contentType:application/json
        // postJson放到请求体中
        // 发送请求
        String resp = HttpRequest.post("https://xxx.net/api").body(postJson).execute().body();
        System.out.println(resp);
        /*
        响应结果:
        {
            "code":"0",
            "data":[
                {"dicCode":"terminalType","isplay":1,"itemId":120,"itemName":"音柱/音箱","itemValue":"1","sort":1},
                {"dicCode":"terminalType","isplay":1,"itemId":121,"itemName":"AIO报警箱","itemValue":"2","sort":2},
                {"dicCode":"terminalType","isplay":1,"itemId":122,"itemName":"融媒体客服主机","itemValue":"3","sort":3},
                {"dicCode":"terminalType","isplay":1,"itemId":123,"itemName":"网络调音台","itemValue":"4","sort":4},
                {"dicCode":"terminalType","isplay":1,"itemId":124,"itemName":"云广播适配器","itemValue":"5","sort":5},
                {"dicCode":"terminalType","isplay":1,"itemId":125,"itemName":"音频编码器","itemValue":"6","sort":6},
                {"dicCode":"terminalType","isplay":1,"itemId":126,"itemName":"村级播控主机","itemValue":"7","sort":7},
                {"dicCode":"terminalType","isplay":1,"itemId":127,"itemName":"云话筒","itemValue":"8"},
                {"dicCode":"terminalType","isplay":1,"itemId":128,"itemName":"安卓手机客户端","itemValue":"9"},
                {"dicCode":"terminalType","isplay":1,"itemId":129,"itemName":"收扩机","itemValue":"10","sort":8}
            ]
        }
        */
    }

    /**
     * 构建签名
     *
     * @param paramsMap
     *            参数
     * @param secret
     *            密钥
     * @return
     * @throws IOException
     */
    public static String buildSign(Map<String, ?> paramsMap, String secret) throws IOException {
        Set<String> keySet = paramsMap.keySet();
        List<String> paramNames = new ArrayList<String>(keySet);

        Collections.sort(paramNames);

        StringBuilder paramNameValue = new StringBuilder();

        for (String paramName : paramNames) {
            paramNameValue.append(paramName).append(paramsMap.get(paramName));
        }

        String source = secret + paramNameValue.toString() + secret;

        return md5(source);
    }

    /**
     * 生成md5,全部大写
     *
     * @param message
     * @return
     */
    public static String md5(String message) {
        try {
            // 1 创建一个提供信息摘要算法的对象,初始化为md5算法对象
            MessageDigest md = MessageDigest.getInstance("MD5");

            // 2 将消息变成byte数组
            byte[] input = message.getBytes();

            // 3 计算后获得字节数组,这就是那128位了
            byte[] buff = md.digest(input);

            // 4 把数组每一字节(一个字节占八位)换成16进制连成md5字符串
            return byte2hex(buff);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 二进制转十六进制字符串
     *
     * @param bytes
     * @return
     */
    private static String byte2hex(byte[] bytes) {
        StringBuilder sign = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(bytes[i] & 0xFF);
            if (hex.length() == 1) {
                sign.append("0");
            }
            sign.append(hex.toUpperCase());
        }
        return sign.toString();
    }

    /**
     * 十六进制字符串转二进制
     *
     * @param hexString
     * @return
     */
    private static byte[] hexStringToBytes(String hexString) {
        if (hexString == null || hexString.equals("")) {
            return null;
        }
        hexString = hexString.toUpperCase();
        int length = hexString.length() / 2;
        char[] hexChars = hexString.toCharArray();
        byte[] d = new byte[length];
        for (int i = 0; i < length; i++) {
            int pos = i * 2;
            d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
        }
        return d;
    }

    /**
     * 二进制数据生成mp3音频文件
     *
     * @param data     二进制数据
     * @param filePath 文件目录
     * @return
     */
    public static String bytesToFile(byte[] data, String filePath) throws IOException {
        //方法一:直接生成文件
        String url = filePath + ".mp3";
        File file = new File(filePath);
        if (!file.exists()) {
            file.createNewFile();
        }
        FileOutputStream os = new FileOutputStream(file);
        os.write(data);
        //方法二:将二进制数据上传阿里云oss,生成文件
        //String url = "http://" + OSSFactory.build().uploadSuffix(data, ".mp3");
        return url;
    }


    public String getTime() {
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
    }
}

网络安全工程师(白帽子)企业级学习路线

第一阶段:安全基础(入门)

img

第二阶段:Web渗透(初级网安工程师)

img

第三阶段:进阶部分(中级网络安全工程师)

img

如果你对网络安全入门感兴趣,那么你需要的话可以点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

学习资源分享

  • 29
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
API接口签名验证可以确保请求的来源是可信的,并且请求参数没有被篡改。以下是一个PHP实现API接口签名验证的示例: 1. 客户端发送请求时,将参数按照参数名进行字典排序,并将参数名和参数值用等号连接起来,每个键值对之间用&符号连接起来,得到待签名字符串。 2. 将待签名字符串和应用程序的appsecret进行拼接,然后通过哈希算法(如SHA1或MD5)计算得到签名值。 3. 将签名值作为参数(通常是signsignature)添加到原有的请求参数中,然后发送请求。 4. 服务器接收到请求后,按照同样的方式计算签名,将计算出的签名值与请求中的签名值进行比对,如果一致则认为请求合法,否则认为请求不合法。 以下是一个简单的示例代码: ```php <?php // 定义应用程序的appsecret $appsecret = "your_app_secret"; // 获取请求参数 $params = $_POST; // 按照参数名进行字典排序 ksort($params); // 将参数名和参数值用等号连接起来,每个键值对之间用&符号连接起来,得到待签名字符串 $sign_str = ""; foreach ($params as $key => $value) { $sign_str .= $key . "=" . $value . "&"; } $sign_str .= "appsecret=" . $appsecret; // 计算签名值 $sign = md5($sign_str); // 验证签名 if ($sign != $params['sign']) { // 签名验证失败 echo "Invalid Signature"; } else { // 签名验证成功 // 执行业务逻辑 } ?> ``` 在实际应用中,为了增加安全性,可以使用更加复杂的签名算法,如RSA数字签名等。另外,还可以限制请求的时间戳和nonce值,防止重放攻击。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值