HMAC
HMAC是一種利用密碼學中的散列函數來進行消息認證的一種機制,所能提供的消息認證包括兩方面內容:
①消息完整性認證:能夠證明消息內容在傳送過程沒有被修改。
②信源身份認證:因爲通信雙方共享了認證的密鑰,接收方能夠認證發送該數據的信源與所宣稱的一致,即能夠可靠地確認接收的消息與發送的一致。
HMAC是當前許多安全協議所選用的提供認證服務的方式,應用十分廣泛,並且經受住了多種形式攻擊的考驗。
HMAC與一般的加密重要的區別在於它具有“瞬時"性,即認證只在當時有效,而加密算法被破解後,以前的加密結果就可能被解密。
Kong配置
請求參數
參數說明
需要增加2個request header:
- Date:當前時間(爲了方便對接,開發階段此值設定爲距當前時間1小時內),GMT格式,與北京時間相差8小時。例:
Sat, 10 Oct 2020 08:10:21 GMT
。(該參數除了用於參與簽名計算,更重要的作用是防止重複攻擊。假設抓包到了請求數據,包含當前日期以及認證信息,如果該日期沒有時間限制,就可以一直重複使用) - Authorization:
hmac username="app", algorithm="hmac-sha256", headers="date request-line", signature="Vr7f7g6dzwxW6bP7dSiJutyOm8aiJl+IG5XJqyOHERU="
,其中username的值由網關給出,signature爲Base64編碼後的簽名,其餘固定。
簽名算法
使用HMAC-SHA256加密算法,參數爲message和secret。secret由網關給出。僞代碼描述:
message="date: Thu, 22 Jun 2017 17:15:21 GMT\nGET /requests HTTP/1.1"
secret="secret"
digest=HMAC-SHA256(message, secret)
base64_digest=base64(digest)
message格式:
- 如果request header的name不是
request-line
,追加小寫的header name,追加冒號和空格,追加值。比如date: Thu, 22 Jun 2017 17:15:21 GMT
。 - 如果是
request-line
,追加HTTP request line
。 - 多個request header中間使用
\n
隔開。
message參數:
- Date:客戶端增加的參數,見參數說明,符合message格式的第一條,注意冒號和值之間有一個空格。
- Request-Line:Request-Line=Method空格Request-URI空格HTTP-Version。例如:
GET /request?a=11&b=22 HTTP/1.1
- Request-URI:上面Request-Line的一部分,the path with the querystring。例如:url爲
https://example.com:1234/v1/movies?movie=foo
,則Request-URI=/v1/movies?movie=foo
完整的message示例:date: Thu, 22 Jun 2017 17:15:21 GMT\nGET /request?a=11&b=22 HTTP/1.1
舉個例子
假設有GET請求接口地址爲:http://kong.alicontainer.com/app/hot
Date爲:Sat, 15 Oct 2020 07:40:21 GMT
則message爲:date: Sat, 15 Oct 2020 07:40:21 GMT\nGET /app/hot HTTP/1.1
signature爲:cCUTMy/11ZkyAtp+iSh1/BftKJODibZQgf4Oi8q/d9o=
異常說明
http status:401
-
header中沒有date參數,或者date時間不合法(距當前時間5分鐘內,gmt格式,與北京時間差8小時)
{ "message": "HMAC signature cannot be verified, a valid date or x-date header is required for HMAC Authentication"}
-
簽名錯誤
{ "message": "HMAC signature does not match"}
客戶端實現
客戶端當根據自己的環境使用攔截器思想,在請求即將發出前統一攔截,在header中增加認證參數,以下僅給出獲取計算signature的代碼示例。
Java版本
/**
* 獲取當前GMT format 時間
*
* @return
*/
public static String getGMTTime() {
DateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
return dateFormat.format(Calendar.getInstance().getTime());
}
public static String sign(String secret, String message) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
mac.init(secretKey);
return Base64.encodeBase64String(mac.doFinal(message.getBytes()));
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new IllegalArgumentException("簽名錯誤:" + e.getMessage());
}
}
public static void main(String[] args) {
String secret = "dapeng";
String message = "date: Sat, 10 Oct 2020 08:10:21 GMT\nGET /mock?a=1 HTTP/1.1";
String sign = sign(secret, message);
System.out.println(sign);//Vr7f7g6dzwxW6bP7dSiJutyOm8aiJl+IG5XJqyOHERU=
}
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
body驗證
body數據簽名驗證需要增加另一個header:Digest
,value爲:Digest: SHA-256=base64(sha256(<body>)),例如:SHA-256=RgecWaKvMqh5T79Ps93OuyZ5CZuPnZ6dTgsh5i9xJUs=
將body字符串使用sha256加密,再使用base64編碼,這裏不涉及secret。
java版本:
public static String String2SHA256(String str) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
byte[] hash = messageDigest.digest(str.getBytes());
return Base64.encodeBase64String(hash);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("簽名錯誤:" + e.getMessage());
}
}
public static void main(String[] args) {
Map<String, String> map = new HashMap<>(1);
map.put("name", "123");
String body = JSON.toJSONString(map);
System.out.println(String2SHA256(body));
}