HTTP通信加密算法

优质
小牛编辑
125浏览
2023-12-01

当需要和 Live API系统进行 HTTP 通信时,需要将原始的 Query String 转换为和请求时刻相关的 Hashed Query String 后再通过 GET 方法请求 Live API。为了描述的方便,我们将 Query String 转换为 Hashed Query String 的算法称为 THQS 算法。在描述详细的算法流程之前,我们先介绍一下 Unix 时间戳的概念。

Unix 时间戳,即该时间到 1970 年 1 月 1 日(UTC/GMT 的午夜)之间的秒数。 例如,北京时间 2010 年 12 月 9 日 15 点 23 分 12 秒的 Unix 时间戳为 1291879392。

THQS 算法

假设原来的 QueryString 为 q, 通过以下 4 个步骤,即可得到最终用于通信的 HashedQueryString:

1. 对于q中的每个键值对按照键的字母顺序升序排序,得到排序后的请求字符串qs;
2. 加入当前时间的 Unix 时间戳和直播平台帐号对应的 API Key 值,得到散列前的字符串 qf:
 qf ← qs&time=12345&salt=aSdF1234
3. 计算得到 qf 的 md5 值,假设为 abcdefg
 hash ← md5(qf)
4. 最终的 HashedQueryString 为:
 hqs ← qs&time=12345&hash=abcdefg
用 hqs 代替 q 进行 Http 通信。

下⾯举一个例⼦子说明计算过程。假设用户从直播平台获取到的API Key值是aSdF1234,当前时间为2010年12月9日15点23分12秒,原始的QueryString 是: name=harry&level=top&salary=1000&datetime=2010-03-05 12:00:00

第⼀步,将上述QueryString 按照字⺟顺序进行升序排序,对value进行URLencode转义处理,结果是: datetime=2010-03-05+12%3A00%3A00&level=top&name=harry&salary=1000

第⼆步,附加time值和salt值,得到取hash前的字符串 加密后的字符串:datetime=2010-03-05+12%3A00%3A00&level=top&name=harry&salary=1000&time=1291879392&salt=aSdF1234

第三步,对上述字符串取 md5 值 hash=96CDEE621BBA8617F5EE7465F17F8398!

因此,最终进⾏行Http通信的字符串为datetime=2010-03-05+12%3A00%3A00&level=top&name=harry&salary=1000&time=1291879392&hash=96CDEE621BBA8617F5EE7465F17F8398

有些特殊字符会对URL中的参数进行分割,需要做转义处理。

对特殊字符进行URLencode后,测试下是否正常,可以参考以下对应编码:

字符URL编码值
空格%20
"%22
#%23
%%25
&%26
(%28
)%29
+%2B
,%2C
/%2F
:%3A
;%3B
<%3C
=%3D
>%3E
?%3F
@%40
\%5C
|%7C

使用java代码编写如下:

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
/**
 * 加密工具类
 */
public class SignUtils {

    /**
     * 请求url的所有参数拼接成字符串
     * @param map
     * @return
     */
    public static String createQueryString(Map<String, String> map) {
        if (map.isEmpty()) {
            return null;
        }
        StringBuilder res = new StringBuilder();
        for (String key : map.keySet()) {
            String value = map.get(key);
             if (null == key || "".equals(key)) {
                continue;
            }
            try {
                if (null != value && !"".equals(value)) {
                    res.append(key).append("=").append(URLEncoder.encode(value, "UTF-8")).append("&");
                } else {
                    res.append(key).append("=").append(value).append("&");
                }
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        if (res.length() > 1) {
            return res.substring(0, res.length() - 1);
        }
        return null;
    }

    /**
     * 通过md5进行加密
     * @param source 要加密的数据
     * @return
     * @throws NoSuchAlgorithmException
     */
    private static String getMd5(String source) throws NoSuchAlgorithmException {
        MessageDigest digest = MessageDigest.getInstance("md5");
        byte[] bytes = source.getBytes();
        byte[] targetBytes = digest.digest(bytes);
        char[] characters = new char[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        StringBuilder builder = new StringBuilder();
        for (byte b : targetBytes) {
            int high = (b >> 4) & 15;
            int low = b & 15;
            char highChar = characters[high];
            char lowChar = characters[low];
            builder.append(highChar).append(lowChar);
        }

        return builder.toString();
    }

    /**
     * 进行MD5加密
     * @param qs
     * @param time
     * @param salt
     * @return
     */
    public static String getSign(String qs, long time, String salt) {
        try {
            return getMd5(String.format("%s&time=%d&salt=%s", qs, time, salt));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 功能:将一个Map按照Key字母升序构成一个QueryString. 并且加入时间混淆的hash串
     * @param queryMap query内容
     * @param time     加密时候,为当前时间;解密时,为从querystring得到的时间;
     * @param salt     加密salt
     * @return
     */
    public static String createHashedQueryString(Map<String, String> queryMap, long time, String salt) {
        Map<String, String> map = new TreeMap<String, String>(queryMap);
        String qs = createQueryString(map); //生成queryString方法可自己编写
        if (qs == null) {
            return null;
        }
        time = time / 1000;
        String hash = getSign(qs, time, salt).toUpperCase();
        String thqs = String.format("%s&time=%d&hash=%s", qs, time, hash);
        return thqs;
    }

    public static void main(String[] args) {
        Map<String, String> params = new HashMap<String, String>();// 需要传递的参数
        params.put("userid", "A00000000001");
        params.put("name", "测试直播");
        params.put("desc", "每晚八点");
        params.put("authtype", "1");
        params.put("publisherpass", "admin123");
        params.put("assistantpass", "admin123");
        params.put("templatetype", "5");
        params.put("playpass", "admin123");
        params.put("livestarttime", "2020-03-05 20:00:00");

        String salt = "LASD8SD8SDN23K"; //秘钥
        long time = System.currentTimeMillis(); //当前时间戳
        String str = createHashedQueryString(params, time, salt);//生成http请求参数
        System.out.println("加密后的字符串:" + str);

    }

}

使用python语言编写如下:

class thqs(object):

'生成thqs请求url'

def my_urlencode(self, q):

    '对请求的字段进行urlencode,返回值是包含所有字段的list'

    l = []

    #遍历字典,进行quote_plus操作,并把所有字段拼成list

    for k in q:

        k = urllib.quote_plus(str(k))

        v = urllib.quote_plus(str(q[k]))

        url_param = '%s=%s' % (k, v)

        l.append(url_param)

    l.sort()

    return '&'.join(l)

def get_thqs(self, q):

    ‘按照thqs算法对所有的字段进行处理'

    qftime = 'time=%d' % int(time.time())

    salt = 'salt=%s' % API_KEY

    qftail = '&%s&%s' % (qftime, salt)



    qs = self.my_urlencode(q)

    qf = qs + qftail

    hashqf = 'hash=%s' % (hashlib.new('md5', qf).hexdigest().upper())

    thqs = '&'.join((qs, qftime, hashqf))

    return thqs