Iris非对称签名认证

API接口有多种认证方式,例如基础认证(用户名/密码)apikey(秘钥)token(令牌)JWT(令牌)signature(签名)等。非对称签名认证应用广泛,接下来从客户端到服务端简单实现非对称签名,签名算法采用HMAC SHA256

数字签名

数字签名技术是将摘要信息用发送者的私钥加密,与原文一起传送给接收者。接收者只有用发送者的公钥才能解密被加密的摘要信息,然后用HASH函数对收到的原文产生一个摘要信息,与解密的摘要信息对比。如果相同,则说明收到的信息是完整的,否则说明信息被修改,因此数字签名能保证信息传输过程中完整性、提供信息发送者的身份认证和不可抵赖性。可以用数字签名来做API认证。

签名过程

使用随机函数生成公私钥对,例如

1
2
公钥: "8thcm24furn0likp"
私钥: "PnA4tIBlKBbUV5iVQloLrvBoTbMR03el"

选择合适的摘要签名信息,可以采用当前时间

1
摘要信息: "date: 2019-10-21 10:58:58"

将摘要信息用HMAC SHA256进程签名,并对原始的二进制数据进行Base64编码,得到签名值

1
签名值: "33xn46YabD78N372yw+WV4NvNSTRrRbmAV7mcz0vDUQ="

客户端header组装,x-api-key用来传输公钥,date用来传输摘要信息,authorization用来传输完整的签名信息

1
2
3
'x-api-key': '8thcm24furn0likp',
'date': '2019-10-21 10:58:58',
'authorization': 'Signature keyId="8thcm24furn0likp",algorithm="hmac-sha256",signature="33xn46YabD78N372yw+WV4NvNSTRrRbmAV7mcz0vDUQ="'
签名实现

python可以基于HTTPSignatureAuth来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from httpsig.requests_auth import HTTPSignatureAuth
import requests
import datetime


class SignatureAuthHttp(object):
def __init__(self, key='8thcm24furn0likp', secret='PnA4tIBlKBbUV5iVQloLrvBoTbMR03el'):
self.auth = HTTPSignatureAuth(key=key, secret=secret)
self.header = {
'Content-Type': 'application/json',
'X-Api-Key': key,
'Date': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}

def httpGet(self, url):
return requests.get(url, auth=self.auth, header=self.header)

如果是Kong的上游请求需要进行签名认证,可以基于openssl.hmacngx模块实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
local BasePlugin = require "kong.plugins.base_plugin"
local req_set_header = ngx.req.set_header
local openssl_hmac = require "openssl.hmac"

local UpstreamAuthSignatureHandler = BasePlugin:extend()

function UpstreamAuthSignatureHandler:new()
UpstreamAuthSignatureHandler.super.new(self, "upstream-auth-signature")
end

function UpstreamAuthSignatureHandler:access(conf)
UpstreamAuthSignatureHandler.super.access(self)

local key = conf.key
local secret = conf.secret
local date = os.date("%Y-%m-%d %H:%M:%S", os.time())
local signature_salt = string.format("date: %s", date)
local signature = openssl_hmac.new(secret, "sha256"):final(signature_salt)
local authorization = string.format('Signature keyId="%s",algorithm="hmac-sha256",signature="%s"', key, ngx.encode_base64(signature))

req_set_header("date", date)
req_set_header("x-api-key", key)
req_set_header("authorization", authorization)

end

UpstreamAuthSignatureHandler.PRIORITY = 850

return UpstreamAuthSignatureHandler
验证签名

首先,服务端通过客户端请求传输的header信息,获取摘要信息、签名值、公钥并找到本地对应的私钥,然后使用同样的算法用私钥对摘要信息进行签名,最后通过对比传输的签名值和本地签名值是否一致,来判断认证的有效性。

基于Iris框架的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// SignatureAuth 判断签名是否有效
func SignatureAuth(ctx iris.Context) {
// 通过header获取签名信息
auth := ctx.GetHeader("Authorization")
date := ctx.GetHeader("date")
authKey := ctx.GetHeader("x-api-key")
if auth == "" || date == "" || authKey == "" {
ctx.StatusCode(401)
ctx.JSON(iris.Map{"code": 1, "msg": "error", "data": "invalid authentication"})
return
}

// 设置签名信息失效时间,这里为10s
loc, _ := time.LoadLocation("PRC")
t, _ := time.ParseInLocation("2006-01-02 15:04:05", date, loc)

if time.Since(t).Seconds() > 10 {
ctx.StatusCode(401)
ctx.JSON(iris.Map{"code": 1, "msg": "error", "data": "invalid authentication"})
return
}

// 获取签名值
authInfo := strings.Split(auth, ",")
authSignature := strings.Split(authInfo[2], "\"")[1]

// 获取本地私钥
userProfile, err := models.GetAPISecret(authKey)
if err != nil {
ctx.StatusCode(401)
ctx.JSON(iris.Map{"code": 1, "msg": "error", "data": "invalid authentication"})
return
}

// 使用同样的算法用私钥对摘要信息进行签名
mac := hmac.New(sha256.New, []byte(userProfile.APISecret))
mac.Write([]byte("date: " + date))
signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))

// 对比传输的签名值和本地签名值
if authSignature == signature {
ctx.Next()
} else {
ctx.StatusCode(401)
ctx.JSON(iris.Map{"code": 1, "msg": "error", "data": "invalid authentication"})
return
}
}

给需要认证的API添加拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func before(ctx iris.Context) {
SignatureAuth(ctx)
}

func InitRouter() *iris.Application {
r := iris.New()
r.Use(recover.New())

r.Use(before)

api := r.Party("/api/xxx")
{
... ...
api.Get("/xxx", v.xxx)
... ...
}

return r
}

测试,未通过签名认证认返回信息

1
2
3
4
5
{
"code": 1,
"data": "invalid authentication",
"msg": "error"
}
----------------本文结束 感谢阅读----------------