接口签名方案(简化版)

2020-06-17 17:39:26

## 签名作用 1. **请求者身份验证**。通过访问者唯一 ID 和密钥确定请求者身份。 2. **防止传输数据篡改**。对数据签名并检验,保障传输内容完整性。 3. **防止签名被盗用**。对签名设置时效,避免签名盗用并重复使用。 ## 准备工作 1. SecretId 和 SecretKey。 2. 确定开发语言: 支持但不限于 Java、PHP、C#、C++、Node.js、Python,根据不同的开发语言,确定对应的 HMAC-SHA1、SHA1 和 UrlEncode 函数。 其中,HMAC-SHA1 和 SHA1 函数以 UTF-8 编码字符串为输入,以16进制小写字符串为输出。UrlEncode 基于 UTF-8 编码,此外对于 ASCII 范围内的可打印字符,下列特殊符号也应被编码: | 字符 | 十进制 | 十六进制 | 字符 | 十进制 | 十六进制 | | ------ | ------ | -------- | ---- | ------ | -------- | | (空格) | 32 | 20 | ; | 59 | 3B | | ! | 33 | 21 | < | 60 | 3C | | " | 34 | 22 | = | 61 | 3D | | # | 35 | 23 | > | 62 | 3E | | $ | 36 | 24 | ? | 63 | 3F | | % | 37 | 25 | @ | 64 | 40 | | & | 38 | 26 | [ | 91 | 5B | | ' | 39 | 27 | \ | 92 | 5C | | ( | 40 | 28 | ] | 93 | 5D | | ) | 41 | 29 | ^ | 94 | 5E | | * | 42 | 2A | \` | 96 | 60 | | + | 43 | 2B | { | 123 | 7B | | , | 44 | 2C | \| | 124 | 7C | | / | 47 | 2F | } | 125 | 7D | | : | 58 | 3A | 无 | 无 | 无 | ## 签名步骤 ### 步骤1:生成 KeyTime 1. 获取当前时间对应的 Unix 时间戳 StartTimestamp,Unix 时间戳是从 UTC(协调世界时,或 GMT 格林威治时间)1970年1月1日0时0分0秒(北京时间 1970年1月1日8时0分0秒)起至现在的总毫秒数。 2. 根据上述时间戳和期望的签名有效时长算出签名过期时间对应的 Unix 时间戳 EndTimestamp。 3. 拼接签名有效时间,格式为`StartTimestamp;EndTimestamp`,即为 KeyTime。 **示例:**`1592363963919;1593367993919` ### 步骤2:生成 SignKey 使用 `HMAC-SHA1` 以`SecretKey`为密钥,以 `KeyTime` 为消息,计算消息摘要(哈希值),即为 `SignKey`。 **示例:**`f48a7caaec408923b8ee49d802ab26d83591cfef` ### 步骤3:生成 UrlParamList 和 HttpParameters 1. 遍历 HTTP 请求参数,生成 key 到 value 的映射 Map 及 key 的列表 KeyList: - key 使用 `UrlEncode`编码。 - value 使用 `UrlEncode` 编码。若无 value 的参数,则认为 value 为空字符串。例如请求路径为`/?acl`,则认为是`/?acl=`。 >? HTTP 请求参数,即请求路径中`?`以后的部分,例如请求路径为`/?versions&prefix=example-folder%2F&delimiter=%2F&max-keys=10`,则请求参数为`versions&prefix=example-folder%2F&delimiter=%2F&max-keys=10`。 2. 将 KeyList 按照字典序排序。 3. 按照 KeyList 的顺序拼接 Map 中的每一个键值对,格式为`key1=value1&key2=value2&key3=value3`,即为 HttpParameters。 4. 按照 KeyList 的顺序拼接 KeyList 中的每一项,格式为`key1;key2;key3`,即为 UrlParamList。 #### 示例: - 示例一: 请求路径:`/?prefix=example-folder%2F&delimiter=%2F&max-keys=10` UrlParamList:`delimiter;max-keys;prefix` HttpParameters:`delimiter=%2F&max-keys=10&prefix=example-folder%2F` >!请求路径中的请求参数在实际发送请求时也会进行 UrlEncode,因此要注意不要重复执行 UrlEncode。 - 示例二: 请求路径:`/exampleobject?acl` UrlParamList:`acl` HttpParameters:`acl=` ### 步骤4:生成 StringToSign 根据 `KeyTime` 和 `HttpParameters` 生成 `StringToSign`,格式为`sha1\nKeyTime\nSHA1(HttpParameters)\n`。 其中: - sha1 为固定字符串。 - `\n`为换行符。 - SHA1(HttpParameters) 为使用 `SHA1` 对 `HttpParameters` 计算的消息摘要。 ### 步骤5:生成 Signature 使用`HMAC-SHA1`以 `SignKey`为密钥,以`StringToSign`为消息,计算消息摘要,即为 `Signature`。 ### 步骤6:生成签名 根据`SecretId`、`KeyTime`、`UrlParamList` 和 `Signature` 生成签名,格式为: ``` q-sign-time=KeyTime &q-url-param-list=UrlParamList &q-signature=Signature &q-ak=SecretId ``` >!上述格式中的换行仅用于更好的阅读,实际格式并不包含换行。 ## 签名使用 可以通过以下几种方式传递签名: 1. 通过标准的 HTTP Authorization 头,例如`Authorization: q-sign-time=1592...&q-url-param-list=a;b;c&q-signature=a40a5e...` 2. 作为 HTTP 请求参数,请注意 UrlEncode,例如`/exampleobject?q-sign-time=1592...&q-url-param-list=a;b;c&q-signature=a40a5e...` 3. 如需进一步防篡改,可将 Http 请求的更多元素进行签名,如请求方法(HttpMethod)、请求路径(UriPathname)、请求头(HttpHeaders),可参考 AWS S3 协议的完整签名方案。 >?上述示例中使用`...`省略了部分具体签名内容。 ## 代码示例 ### 伪代码 ```plaintext KeyTime = [Now];[Expires] SignKey = HMAC-SHA1([SecretKey], KeyTime) StringToSign = sha1\nKeyTime\nSHA1(HttpParameters)\n Signature = HMAC-SHA1(SignKey, StringToSign) ``` ### 消息摘要算法示例 不同语言如何调用 HMAC-SHA1 可以参考下面的示例: #### PHP ```php $sha1HttpString = sha1('ExampleHttpString'); $signKey = hash_hmac('sha1', 'ExampleKeyTime', 'YourSecretKey'); ``` #### Java ```java import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.HmacUtils; String sha1HttpString = DigestUtils.sha1Hex("ExampleHttpString"); String signKey = HmacUtils.hmacSha1Hex("YourSecretKey", "ExampleKeyTime"); ``` #### Python ```python import hmac import hashlib sha1_http_string = hashlib.sha1('ExampleHttpString'.encode('utf-8')).hexdigest() sign_key = hmac.new('YourSecretKey'.encode('utf-8'), 'ExampleKeyTime'.encode('utf-8'), hashlib.sha1).hexdigest() ``` #### Node.js ```node var crypto = require('crypto'); var sha1HttpString = crypto.createHash('sha1').update('ExampleHttpString').digest('hex'); var signKey = crypto.createHmac('sha1', 'YourSecretKey').update('ExampleKeyTime').digest('hex'); ``` #### Go ```go import ( "crypto/hmac" "crypto/sha1" ) h := sha1.New() h.Write([]byte("ExampleHttpString")) sha1HttpString := h.Sum(nil) var hashFunc = sha1.New h = hmac.New(hashFunc, []byte("YourSecretKey")) h.Write([]byte("ExampleKeyTime")) signKey := h.Sum(nil) ``` ## 实际案例 ### 请求示例 #### 原始请求 - **SecretId** = `12345` - **SecretKey** = `BQYIM75p8x0iWVFSIgqEKwFprpRSVHlz` ``` GET /demo?a=1&b=2&c=3 HTTP/1.1 Date: Thu, 16 May 2019 06:45:51 GMT Content-Type: text/plain ``` #### 中间变量 - **KeyTime** = `1592363963919;1593367993919` - **SignKey** = `f48a7caaec408923b8ee49d802ab26d83591cfef` - **UrlParamList** = `a;b;c` - **HttpParameters** = `a=1&b=2&c=3` - **StringToSign** = `sha1\n1592363963919;1593367993919\n147cb5937edc2fa8cb06a802bf0d64e0419a0fb1\n` - **Signature** = `a4086a5ef76ccea81b0e65642446441f74326e0f` 其中,`\n`代表换行符。 #### 签名后的请求 ``` GET /demo?a=1&b=2&c=3 HTTP/1.1 Date: Thu, 16 May 2019 06:45:51 GMT Content-Type: text/plain Authorization: q-sign-time=1592363963919;1593367993919&q-url-param-list=a;b;c&q-signature=a4086a5ef76ccea81b0e65642446441f74326e0f&q-ak=12345 ``` > 本文档摘取自 https://cloud.tencent.com/document/product/436/7778 ## 代码示例 ``` package com.fiebug.test; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.HmacUtils; import org.junit.Test; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.*; /** * 接口签名测试类 */ public class SignTest { // 使用 HMAC-SHA1 以 SecretKey 为密钥,以 KeyTime 为消息,计算消息摘要(哈希值),即为 SignKey。 // 请求头 Authorization // Ref. https://cloud.tencent.com/document/product/436/7778 /* KeyTime = [Now];[Expires] SignKey = HMAC-SHA1([SecretKey], KeyTime) Signature = HMAC-SHA1(SignKey, UrlEncode([HttpParameters])) q-sign-time=KeyTime &q-url-param-list=UrlEncode(UrlParamList) &q-signature=Signature &q-ak=SecretId */ @Test public void testSign() { String secretId = "12345678"; String secretKey = "BQYIM75p8x0iWVFSIgqEKwFprpRSVHlz"; // String keyTime = "1593394537348;1593494537348"; String keyTime = System.currentTimeMillis() + ";" + (System.currentTimeMillis() + 5 * 60 * 1000); // 构造参数 Map parameters = new LinkedHashMap<>(); parameters.put("a", "1"); parameters.put("b", "2"); parameters.put("c", "3"); parameters.put("特;殊", "4-特殊"); parameters.put("a&b", "5-a&b"); parameters.put("a=b", "6-a=b"); parameters.put("888", "88888"); parameters.put("null", null); parameters.put("empty", ""); // 签名测试 String authorization = signature(secretId, secretKey, keyTime, parameters); System.out.println(authorization); System.out.println(getSecretId(authorization)); System.out.println(checkSignature(secretKey, authorization, parameters)); System.out.println("done"); } /** * 生成签名 * * @param secretId 密钥 ID * @param secretKey 密钥 * @param keyTime 密钥时间 * @param parameters 参数 * @return 签名串 */ private String signature(String secretId, String secretKey, String keyTime, Map parameters) { String SignKey = hmacSha1Hex(secretKey, keyTime); StringBuilder urlParamListBuilder = new StringBuilder(); StringBuilder httpParametersBuilder = new StringBuilder(); if (parameters != null) { List keyListOriginal = new ArrayList<>(parameters.keySet()); for (String key : keyListOriginal) { urlParamListBuilder.append(urlEncode(key)).append(";"); httpParametersBuilder.append(urlEncode(key)).append("=").append(urlEncode(parameters.get(key))).append("&"); } if (httpParametersBuilder.length() > 0) { urlParamListBuilder.deleteCharAt(urlParamListBuilder.length() - 1); httpParametersBuilder.deleteCharAt(httpParametersBuilder.length() - 1); } } String HttpString = httpParametersBuilder.toString(); String StringToSign = "sha1\n" + keyTime + "\n" + sha1Hex(HttpString) + "\n"; String signature = hmacSha1Hex(SignKey, StringToSign); // 组装签名串 return "q-sign-time=" + keyTime + "&q-url-param-list=" + urlParamListBuilder + "&q-signature=" + signature + "&q-ak=" + secretId; } /** * 校验签名 * * @param secretKey 密钥 * @param authorization 签名串 * @param parameters 参数 * @return 校验结果 */ private boolean checkSignature(String secretKey, String authorization, Map parameters) { Map authorizationMap = new HashMap<>(); String[] qArr = authorization.split("&"); for (String qStr : qArr) { String[] split = qStr.split("="); if (split.length > 1) { authorizationMap.put(split[0], split[1]); } else { authorizationMap.put(split[0], null); } } String keyTime = authorizationMap.get("q-sign-time"); String urlParamStr = authorizationMap.get("q-url-param-list"); String[] urlParamArr = urlParamStr != null ? urlParamStr.split(";") : new String[0]; String signature = authorizationMap.get("q-signature"); // 校验时效 String[] keyTimeArr = keyTime.split(";"); if (System.currentTimeMillis() > Long.valueOf(keyTimeArr[1])) { return false; } StringBuilder httpParametersBuilder = new StringBuilder(); if (parameters != null) { for (String urlParam : urlParamArr) { String key = urlDecoder(urlParam); String value = parameters.get(key); httpParametersBuilder.append(urlEncode(key)).append("=").append(urlEncode(value)).append("&"); } if (httpParametersBuilder.length() > 0) { httpParametersBuilder.deleteCharAt(httpParametersBuilder.length() - 1); } } String SignKey = hmacSha1Hex(secretKey, keyTime); String HttpString = httpParametersBuilder.toString(); String StringToSign = "sha1\n" + keyTime + "\n" + sha1Hex(HttpString) + "\n"; String signatureCheck = hmacSha1Hex(SignKey, StringToSign); return signatureCheck.equals(signature); } private String getSecretId(String authorization) { String[] qArr = authorization.split("&"); for (String qStr : qArr) { String[] split = qStr.split("="); if ("q-ak".equals(split[0])) { if (split.length > 1) { return split[1]; } else { return null; } } } return null; } private String urlEncode(String str) { try { if (str == null) { return URLEncoder.encode("", "UTF-8"); } else { return URLEncoder.encode(str, "UTF-8"); } } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } private String urlDecoder(String str) { try { if (str == null) { return URLDecoder.decode("", "UTF-8"); } else { return URLDecoder.decode(str, "UTF-8"); } } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } private String sha1Hex(String data) { return DigestUtils.sha1Hex(data); } private String hmacSha1Hex(String key, String valueToDigest) { return HmacUtils.hmacSha1Hex(key, valueToDigest); // return new HmacUtils(HmacAlgorithms.HMAC_SHA_1, key).hmacHex(valueToDigest); } } ```

分类:编程     关键词:接口,签名

评论(0) 浏览(2722)