前言
通常所说的保证接口安全究竟是在说什么?首先没有绝对的安全,对于反编译逆向工程这里不做讨论。我们要解决的问题是,确保后端接口收到的请求是由我们自己的APP所发出, 中间传输过程中内容不能被篡改,不能有同行通过抓包分析接口的请求报文然后随意调用。 那么这个hmac认证能解决上述问题吗?https可以解决上述问题吗?https只能保证数据不被篡改,但是信源无法保证。 http协议是明文的文本协议,https会对数据包进行加密,但是https请求仍然可以被抓包,并解密其中的内容,只需要伪造一个自签名的根证书, 所有的抓包工具如果需要抓https请求必然要在手机安装一个证书。所以单纯的https并不能保证接口不暴露。 当使用hmac加密签名时,只要secret不泄露,该请求就不会被伪造,也就是即使知道接口地址和参数,但是也无法传递正确的参数,只能是一直在复用由app发出的请求参数, 并且由于hmac协议中clock skew配置,这些参数也不能一直复用下去。
hmac简述
hmac是Hashing for Message Authentication的简写,可以用来保证数据的完整,客户端把内容通过散列/哈希算法算出一个摘要,并把算法和内容以及摘要传送给服务端,服务端按照这个算法也算一遍,和摘要比一下如果一样就认为内容是完整的,如果不一样就认为内容被篡改了。
HMAC是一种利用密码学中的散列函数来进行消息认证的一种机制,所能提供的消息认证包括两方面内容:
①消息完整性认证:能够证明消息内容在传送过程没有被修改。
②信源身份认证:因为通信双方共享了认证的密钥,接收方能够认证发送该数据的信源与所宣称的一致,即能够可靠地确认接收的消息与发送的一致。
HMAC是当前许多安全协议所选用的提供认证服务的方式,应用十分广泛,并且经受住了多种形式攻击的考验。
HMAC与一般的加密重要的区别在于它具有“瞬时"性,即认证只在当时有效,而加密算法被破解后,以前的加密结果就可能被解密。
关于Kong的hmac说几点
- Clock Skew
使用Kong的hmac后,请求必须带有Date和x-date请求头,这个是防止重放攻击(Replay Attacks),默认的情况下,客户端传过来的时间和服务端的时间相差300s,Kong就认为这个请求是有问题的。这个值可以修改的,通过修改config.clock_skew
的参数 - Headers的完整性
使用Kong的hmac来确保请求头的完整性。具体要确保那个头,这个也是可以配置的,下面会演示。官方推荐,最差应该是 request-line, host, and date这三个头,最好是全部的头。 - Body的完整性
- 使用Kong的hmac可以确保请求内容的完整性,下面会演示
确保请求头的完整性
-
基础环境准备
你要有一个正常的route和service,如果没有的话,可以参考https://blog.csdn.net/u014686399/article/details/100084613,我的试验也是基于这个博客的 -
针对服务启用插件
/services/2fc52878-1fe9-48ba-bc45-146da3822851/plugins
{
"name":"hmac-auth",
"config": {
"enforce_headers": ["testhamc"],
"algorithms": ["hmac-sha1", "hmac-sha256"]
}
}
- 2fc52878-1fe9-48ba-bc45-146da3822851这个是服务的id,当然你可以针对route启用插件
- hmac-auth这个是固定值, 插件的名字
- config.enforce_headers这是指定你要校验的头,我这里用了一个testhamc,如果你是生产环境还是遵循一下官网的建议吧
- 创建一个consumer
consumers/ POST
{
"username":"xjj_user_01",
"custom_id":"users_001"
}
- 创建证书
/consumers/f4c713c0-6bf1-4079-83a0-135180d6ba53/hmac-auth POST
{
"username":"rita",
"secret":"123.com"
}
- f4c713c0-6bf1-4079-83a0-135180d6ba53这个是consumer的id
- secret是用来做算法的key的,算法一般会用sha256或者sha1
- 测试
- 直接访问
可以看到直接访问不了了 - 添加Authorization头
- 直接访问
# 首先确定一下testhamc头,因为这是我自定义的头,所以值我就自定义为123456
# 做签名,我是通过java实现的
```text
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
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 = "123.com";
String message = "testhamc: 123456";
String sign = sign(secret, message);
System.out.println(sign);//cnKy69DPkd1rycOvz5biWTJ6rk/UObLAXrNzEOCF4TE=
}
再次访问
curl -i -X GET http://172.16.0.92:31545/xjj_tv/main \
-H "testhamc: 123456" \
-H 'Authorization: hmac username="rita", algorithm="hmac-sha256", headers="testhamc", signature="cnKy69DPkd1rycOvz5biWTJ6rk/UObLAXrNzEOCF4TE="'
- username是证书的用户名
会提示我们添加一个DATE的头
-
添加后再访问
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());
}
curl -i -X GET http://172.16.0.92:31545/xjj_tv/main \
-H "Date: Web, 28 Aug 2019 09:09:00 GMT" \
-H "testhamc: 123456" \
-H 'Authorization: hmac username="rita", algorithm="hmac-sha256", headers="testhamc", signature="cnKy69DPkd1rycOvz5biWTJ6rk/UObLAXrNzEOCF4TE="'
- DATE是 GMT格式的,和服务器上不能相差300秒
访问通过了
确保body的完整性
需要在kong的hmac插件上,打开validate request body = true
的开关!!!!!
body数据签名验证需要增加另一个header:Digest,value为:Digest: SHA-256=base64(sha256()),例如:SHA-256=RgecWaKvMqh5T79Ps93OuyZ5CZuPnZ6dTgsh5i9xJUs=
注意有个前缀:SHA-256=
!!!
- 确定一个body
body肯定是事先确定的,我这里定的内容是 {“appkey”:"123"}
- 制作body的签名,我是通过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("appkey", "123");
String body = JSON.toJSONString(map);
System.out.println(String2SHA256(body));//YKnEib/1qa6/suFFavmKF296fLlZ9sDJDv+zUGQM7q0=
}
- 测试
curl -i -X GET http://172.16.0.92:31545/xjj_tv/main \
-H "Date: Web, 28 Aug 2019 10:20:00 GMT" \
-H "testhamc: 123456" \
-H 'Authorization: hmac username="rita", algorithm="hmac-sha256", headers="testhamc", signature="cnKy69DPkd1rycOvz5biWTJ6rk/UObLAXrNzEOCF4TE="' \
-H "Digest: SHA-256=l12ueyMM3AyCTH8uE9Cs3PVF1ZGR29rhi1l+SsuJzEI=" \
-d '{"appkey":"123"}'
测试通过