HTTP通信加密算法
当需要和 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