正如 Henning Makholm 指出的那样,HMAC 是比公钥更好的选择。您应该针对您的特定场景考虑一些会影响您的选择的最佳实践:
- 您想在签名中考虑主机名和方案 (http/https) 吗?或许。
- 您想考虑签名中的路径吗?大概。
- 您想考虑签名中的查询字符串吗?大概。
- 您想在签名之前标准化参数顺序并转义吗?通常不会。
- 您想嵌入签名时间等(以创建有时间限制的 URL)吗?
- 您是否想将签名 URL 与其他用户状态(例如 cookie)绑定?
- 您是否直接在 HMAC 中使用用户生成的或用户可见的内容?如果是这样,您应该使用为每个请求随机化的值对键进行“加盐”。
计算签名时,您需要以 URL 友好的方式对其进行编码(base64 和 base32 是流行的选择)并选择 HMAC 算法(例如 SHA-256),并决定要使用多少位签名保留(将 HMAC 值截断一半通常是安全的)。如果您选择 base64,请注意 url 安全与非 url 安全实现使用的不同字母。
这是用于签名路径+查询字符串的伪代码实现(没有错误检查或加盐等):
const secret = ...;
def sign(path, querystring):
return path + "?" + querystring + "&sig=" + url_encode(base64_encode(hmacsha256(secret, path + "?" + querystring).truncate(16)))
def verify(path, querystring):
querystring_without_sig = remove_query_parameter(querystring, "sig")
sig = base64_decode(url_decode(get_query_parameter(querystring, "sig")))
if hmacsha256(secret, path + "?" + querystring_without_sig)[:16] != sig:
raise "invalid sig"
建议使用 HMAC SHA256,并且支持所有常见语言。
Java:
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(secret);
return mac.doFinal(value.getBytes());
Python:
hmac.new(secret, input, hashlib.sha256).digest()
PHP:
hash_hmac("sha256", value, secret);