首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >如何设计一个API签名

如何设计一个API签名

原创
作者头像
林喜东
修改于 2019-12-22 12:30:22
修改于 2019-12-22 12:30:22
5.6K11
代码可运行
举报
文章被收录于专栏:林喜东的专栏林喜东的专栏
运行总次数:1
代码可运行

前言

大部分情况下,我们使用已有的API签名方案(如腾讯云API签名、阿里云APi签名、亚马逊API签名等等)即可,无需从零开始设计一个API签名方案。写这篇文章的主要目的,是希望通过思考如何去设计一个可用API签名的过程,更好地理解现有的各种大同小异的签名方案背后的设计原理,从而更好地保护好我们的API接口。当然,有需要自己设计一个签名方案的场景也可参考一下。

1、API签名是什么

API签名可以理解为就是对API的调用进行签名保护。是在进行API调用时,加了一个调用者及其调用行为的指纹信息,以帮助服务端更好的识别用户及其调用行为的合法性。其直接目的归纳为:

(1)明确调用者的身份(确认调用者是谁)

(2)明确调用者的调用行为(确认调用者想要做什么)

图·API签名解决两个问题
图·API签名解决两个问题

而明确调用者的身份和调用行为后,可基于调用者的身份做到包括但不限于以下几点:

A:拒绝非法用户身份者的调用请求

B:拒绝越权使用者的调用请求,保护隐私

C:控制访问者的调用频率,保护服务

D:记录调用者的访问记录,以便追溯

......

由此可见,API签名的真正目的是:通过明确调用者的身份,以便控制API的访问权限,从而保护数据的安全性

图·API签名的本质
图·API签名的本质

2、如何设计一个API签名

既然API签名的目的是:明确调用者的身份及其调用行为,那么我们进行设计时只有围绕这两点即可。

2.1、如何明确调用者

我们都知道,在程序的世界中,很难找到一个稳定且唯一的信息去标识一个调用者,因为调用者本身的信息(如IP、设备等)也是不固定的,所以,标识调用者最好的方法就是服务端统一分配:

2.1.1、用户身份标识

(1)调用者调用API前,必须向系统申请一个唯一的标识

(2)系统为每个调用者分配一个唯一的ID,这里暂定为SecretID

(3)调用者调用API时带上该SecretID

(4)服务端 通过SecretID确认调用者身份

以上流程的问题,在于SecretID是明文显示的,很容易被窃取和伪造;但SecretID又不能隐藏或加密,因为SecretID需要明确告诉服务端:我是谁?

所以,需要在SecretID之外,增加一个和SecretID绑定的信息,我们称之为:

2.1.2、用户密钥

用户密钥(即SecretKey)就是为了验证用户身份用的,为了提高其安全性能,必须保证

(1)调用者必须保护好SecretKey,不能在任何地方明文显示

(2)SecretKey最好不在请求过程中传输

至于,密钥如何分配、更换、失效、存储等密钥管理的内容不是本文重点,暂不深入。

那么,问题来了,有了密钥之后,如何验证用户的身份呢?这个就需要靠算法来解决了。

图·ID+Key确认用户身份
图·ID+Key确认用户身份

2.1.3、签名算法选择

在密码学中,有对称加密算法、非对称加密算法、 希运算消息认证码等等几种方案可以很好保护用户密钥的同时,验证用户的身份。那么,我们应该如何选择呢?

(1)首先排除的是非对称加密算法,理由是耗时长,性能差。

通过实测,非对接加密算法(RSA)相对加密算法(AES)和 希运算消息认证码算法(HmacSHA256)的加解密耗时要高2~3个数量级,对于一个服务端来说,性能也是很重要的考虑标准,故一般不选择非对称加密算法。

算法类型

加密耗时

解密耗时

RSA

380317 ns/op

34427 ns/op

AES

885 ns/op

938 ns/op

HmacSHA256

1458 ns/op

1458 ns/op

(2)从以上结果看,Hmac和AES似乎都不错,而且AES更优。但考虑到签名的目的,除了明确用户身份外,还要明确调用者的调用行为;也就是说,为了需要保证整个请求的完整性,需要加密整个请求的所有关键内容,这时,Hmac算法的防伪造性(即修改一个字节,签名信息就完全不一样)的优势就突显出来了,在性能差不多的情况下,当然,选择Hmac算法了。

(3)Hmac支持的hash算法非常多,但一般不建议使用MD5和SHA1,因其有哈希长度扩展攻击(Hash Length Extension Attacks)的风险,故一般推荐使用HmacSHA256或HmacSHA512。

若服务端支持多种算法,则请求时,需明确带上使用的签名方法:SignatureMethod

2.2、如何明确调用者的调用行为

方法很简单,那就是把调用行为涉及的关键信息都放到签名内容中进行签名。那么,哪些是关键信息呢?

2.2.1、请求的方法和接口

即每个请求Method和URL,这是每个请求都有的信息,且最为关键的信息。

2.2.2、请求的内容

请求内容一般指HEADERS、QueryString、BODY三大类。

那么,哪一类内容需要添加的签名内容中呢?

一个简单的判断标准,就是看这一块的内容的变更是否影响请求结果,若影响了,一般要求加入到签名内容中;若设计时还不确定,则全部内容加到签名内容中即可。

备注:实际上,一般是哪个字段有影响,添加哪个字段最简洁;但这样的话,服务端就非常麻烦,需要对每个API接口的每个字段分析,无论请求端还是服务端实现都特别麻烦且需要每个接口进行签名联调,不太现实。所以,一般是按大类进行的。

好了,到这里,API签名似乎已经完成了。

2.2.3、防重放

但是,对于部分请求来说,是有请求一次性要求,即同一请求内容一次和两次的结果是不一样的。这种情况下,恶意攻击者,截取一个合法请求后,不停地使用该请求对服务端进行攻击;这种攻击可能造成

(1)如果该请求是写请求,且服务端逻辑允许重复(如A向B转1元),则会造成严重后果。

(2)如果该请求是非常耗时操作,则可能造成服务性能下降。

(3)如果是普通读请求,看似无害,实则量大也是对后端服务性能的一种消化。

所以,在API签名这里,需要进行防重放设计,可以为后端其他服务减少压力。实现的方法,也很简单,那就是调用者每次调用时:

A:调用者生成并带上一个随机数Nonce

B:服务端该随机数是否已出现,有则拒绝,无则存储该随机数并放过请求

这里服务端要保证Nonce唯一,就得存储已经用过的Nonce,但长期保持会带来两个问题

(1)存储成本增加,日积月累,这里要存储的Nonce会越来越多,需要的存储空间就越大

(2)碰撞概率增加,正常服务被拒绝概率增大;这里随着生成Nonce值越来越多,碰撞的概率一定越来越大,若通过增加Nonce值的长度,有增加存储成本。

那么,另一个可行的办法,就是调用者每次请求时带上当前请求时间点Timestamp,然后由服务端限制请求的时效性。

2.2.4、请求的时效性

即某个请求,其请求时间戳Timestamp,和服务端的当前时间在规定时间内(如1分钟内)则为合法请求,反之,则视为无效请求。

如此,上面提到的Nonce值存储成本可能比较大的问题,在结合Timestamp后,可大大降低存储成本,如Timestamp=1min,则仅需存储1min内的请求Nonce值即可,大大减少存储的量级。

2.2.5、版本控制

另外,每个设计都很难做到完美,或者当前看已经比较完善,但随着技术的发展,会逐渐的暴露一些缺陷,此时,想做一个可持续发展的API签名方案,版本迭代自然少不了,所以,请求内容也可加上版本信息。

3、API签名方案实现

3.1、客户端流程

图·客户端API签名
图·客户端API签名

(1)生成随机数Nonce

(2)拼接签名内容,生成签名信息

(3)调用API时,带上签名信息

3.2、服务端流程

图·服务端API签名校验流程
图·服务端API签名校验流程

(1)校验时间有效性Timestamp

(2)校验Nonce唯一性

(3)提取SecretId和签名信息

(4)根据SecretId提取用户密钥SecretKey,用于生产签名

(5)拼接签名内容,生成签名

(6)校验请求签名和服务端生产签名是否一致

3.3、签名生产流程

几点声明:

(1)以下示例签名信息可放在QueryString中,但签名信息也可放在包头中

(2)为简化流程,以下部分暂不考虑包头签名,原理相通,实现时,自己加上即可

(3)这里的签名算法,指定为HmacSHA256

(4)拼接规则是多样化的,这里的各种拼接规则仅供参考

生成签名串的大致过程如下:

图·API签名生成流程
图·API签名生成流程

假设调用者已经有了SecretID 和 SecretKey 分别是:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
secretId: "SKIDz8krbsJ5yKBZQpn74WFkmLPx3EXAMPLE"
secretKey: "Gu5t9xGARNpq86cd98joQYCN3EXAMPLE"

3.3.1、确认是否做包体签名

有包体,则做包体签名,无包体,则不做包体签名。以下请求包体格式规定为JSON,这里的无需提取包体字段进行拼接,直接对整个包体内容进行签名即可,但对包体字段到顺序有要求。

假设本次要调用API接口名称为:GetLibTypeList,POST方法,请求包体格式为JSON,请求包体字段有PageIndex和PageSize。

(1)请求JSON包体转换为字符串

假设,本次请求的Json包结构,如下所示

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
    "PageIndex":0,
    "PageSize":10
}

先将json包结构体进行json序列化成byte, 再将byte转换为字符串,最终得到的包体签名原字符串如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{"PageIndex":0,"PageSize":10}

(2)生成包体签名串

首先使用签名算法HmacSHA256对上一步中获得的 包体原文字符串 进行签名,然后将生成的签名串使用 Base64 进行编码,即可获得的包体签名串。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
UodgxU3P77iThrEJtsiHi2kjYJmNA2jGEgYNnMD/X0s=

(3)签名串编码

生成的签名串并不能直接作为请求参数,需要对其进行 URL 编码。 ​ 如上一步使用HmacSHA256 生成的签名串为UodgxU3P77iThrEJtsiHi2kjYJmNA2jGEgYNnMD/X0s=,则其编码后为

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
UodgxU3P77iThrEJtsiHi2kjYJmNA2jGEgYNnMD%2FX0s%3D

因此,最终得到的签名串请求参数 (Signature) 为:UodgxU3P77iThrEJtsiHi2kjYJmNA2jGEgYNnMD%2FX0s%3D,它将用于生成最终的请求URL。

3.3.2、拼接请求字符串

请求参数主要有:

参数名称

类型

示例值

Version

固定为:20191001

20191001

SecretId

密钥ID

SKIDz8krbsJ5yKBZQpn74WFkmLPx3EXAMPLE

Timestamp

当前时间戳

1569490800

Nonce

随机正整数

3557156860265374221

SignatureMethod

签名方式

HmacSHA256或HmacSHA1

HashedRequestPayload

包体签名字符串

UodgxU3P77iThrEJtsiHi2kjYJmNA2jGEgYNnMD%2FX0s%3D

根据上述参数,使用HmacSHA256签名方式拼接的请求字符串如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Version=20191001&SecretId=SKIDz8krbsJ5yKBZQpn74WFkmLPx3EXAMPLE&Timestamp=1569490800&Nonce=3557156860265374221&SignatureMethod=HmacSHA256&HashedRequestPayload=UodgxU3P77iThrEJtsiHi2kjYJmNA2jGEgYNnMD%2FX0s%3D

备注:当无需包体签名时,则不拼接HashedRequestPayload即可

3.3.3、拼接签名原字符串

这里规定签名原文字符串的拼接规则为:

请求方法 + 请求主机 +请求路径 + ? + 请求字符串

参数构成说明:

  • 请求方法: 即 POST 、GET等方法, 为保证签名结果一致,一般需规定注意方法为全大写。
  • 请求主机:即主机域名,此处是本地测试,则使用:localhost:8008,具体请以实际请求的域名为准。
  • 请求路径: 即API 的请求路径,本例中请求路径为/GetLibTypeList
  • 请求字符串: 即上一步生成的请求字符串。

使用 HmacSHA256签名方式拼接的签名原字符串如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
POSTlocalhost:8008/GetLibTypeList?Version=20191001&SecretId=SKIDz8krbsJ5yKBZQpn74WFkmLPx3EXAMPLE&Timestamp=1569490800&Nonce=3557156860265374221&SignatureMethod=HmacSHA256&HashedRequestPayload=UodgxU3P77iThrEJtsiHi2kjYJmNA2jGEgYNnMD%2FX0s%3D

3.3.4、生成签名串

使用签名算法HmacSHA256对上一步中获得的 签名原文字符串 进行签名,然后将生成的签名串使用 Base64 进行编码,即可获得请求签名串如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
+ysXvBSshSbHOsCX2zWBE1tapVs68hi5GLdcQtwBUNk=

生成的签名串并不能直接作为请求参数,需要对其进行 URL 编码,编码后的签名串如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
%2BysXvBSshSbHOsCX2zWBE1tapVs68hi5GLdcQtwBUNk%3D

3.3.5、将签名信息添加到请求参数中

使用 HmacSHA256签名方式,发送的POST请求URL如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
http://localhost:8008/GetLibTypeList?Version=20191001&SecretId=SKIDz8krbsJ5yKBZQpn74WFkmLPx3EXAMPLE&Timestamp=1569490800&Nonce=3557156860265374221&SignatureMethod=HmacSHA256&HashedRequestPayload=UodgxU3P77iThrEJtsiHi2kjYJmNA2jGEgYNnMD%2FX0s%3D&Signature
=%2BysXvBSshSbHOsCX2zWBE1tapVs68hi5GLdcQtwBUNk%3D

注意:

(1)发送的请求URL各个参数无排序要求,但其顺序必须和签名原字符串的顺序保持一致

(1)需规定签名信息Signature必须作为最后一个参数,拼接在最后面,以便截取

(2)所有请求参数的参数值均需要做 URL 编码

需要注意的是,部分语言库会自动对 URL 进行编码,重复编码会导致签名校验失败。

4、代码实现

Go语言版实现代码可参考:https://github.com/esonlin/signature (含服务端代码和客户端demo)

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
1 条评论
热度
最新
App里面SecretKey如何存储比较安全
App里面SecretKey如何存储比较安全
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
腾讯云API:用Python使用腾讯云API(cvm实例)
腾讯云API地址:https://cloud.tencent.com/document/api
None-xiaomi
2018/05/30
26.5K10
CKafka系列学习文章 - 用java实现API调用(十一)
导语:上一章节我们了解了怎么手动拼接请求URL和用API2.0签名自动生成请求URL,来调用CKafka的接口,这一章将进入开发阶段,用java实现拼接请求URL,进行API调用。
发哥说消息队列
2019/09/06
6840
腾讯云API:让你的代码更加稳定(Python版)
之前发了两个文章,是关于腾讯云API的使用的文章,主要是小Demo的展示,用来帮助初学者,或者最初使用者作为参考。但是有些人可能有疑问,或者新的想法,你这代码是否可以进行一些“黑科技”,当然可以。首先,上一下之前的两个代码:
None-xiaomi
2018/06/01
4K1
利用API自动更新腾讯dnspod子域名解析记录实现ddns
由于个人网络是动态IP地址,导致每次重启路由器都会更换IP地址,或者是租约到期也会更新IP地址。 更换IP地址后每次都需要重新设置DNSPod,假如设置不及时还可能会影响到个人搭建的某些服务。 所以当时我就在想有没有办法实现定期查询本地IP地址与DNSPod记录IP地址是否相同, 相同则不进行任何操作,不同则自动上报更新IP地址。于是乎有了下面这个利用DNSPod的API实现动态更新IP地址的方法。
铭心
2024/12/20
8170
如何使用腾讯云云硬盘API
腾讯云控制台允许您以类似于使用硬盘驱动器的方式管理腾讯云CVM的额外存储。只需点击腾讯云简化的GUI或图形用户界面,即可为我们的CVM添加云硬盘。但是,这不是一个在大型集群的实用方法,因此腾讯云提供了相关API。我们可以通过腾讯云官方命令行工具直接与API进行交互。
好烟
2018/08/13
5.5K0
拒绝接口裸奔!开放API接口签名验证!
为开发者分配AccessKey(开发者标识,确保唯一)和SecretKey(用于接口加密,确保不易被穷举,生成算法不易被猜测)。
架构师修炼
2020/09/30
1.4K0
腾讯云语音识别v1签名算法详解
v1的签名文档:https://cloud.tencent.com/document/product/1093/35642
算法发
2020/08/28
2.7K0
【AI接入迷你赛】腾讯云产品鉴权签名 v3
腾讯云 API 会对每个请求进行身份验证,用户需要使用安全凭证,经过特定的步骤对请求进行签名 Signature,每个请求都需要在公共请求参数中指定该签名结果并以指定的方式和格式发送请求 。
用户4299935
2019/09/11
5.9K1
【AI接入迷你赛】腾讯云产品鉴权签名 v3
Python 和 PHP 对腾讯云签名 hmac_sha256 算法实现
开宗明义,米扑科技在使用腾讯云的API接口签名中,按照官方示例开发PHP、Python的接口,经常会提示签名错误
阳光岛主
2019/02/18
2.6K0
V3手动鉴权失败之C#篇
腾讯云 API 全新升级 3.0 ,该版本进行了性能优化且全地域部署、支持就近和按地域接入、访问时延下降显著,接口描述更加详细、错误码描述更加全面、SDK增加接口级注释,让您更加方便快捷的使用腾讯云产品。人脸识别、文字识别,语音识别等众多产品均已接入云API 3.0。
周朋伟
2020/12/31
2K0
V3手动鉴权失败之C#篇
【非官方教程】【视频】云API实践教程(上)
云API存在的目的是什么?有控制台给我们提供给中便利,我们为什么要用API来做一些操作?
None-xiaomi
2018/07/11
1.1K0
【玩转 EdgeOne】使用EdgeOne边缘函数搭建域名注册查询API
突然心血来潮,想注册个4位的.cn域名,但一个个查显然是不可能的事情,于是萌生了写一个查询域名是否已注册的API的想法。
HuoYun
2023/10/31
6970
Swift 实现腾讯云 TC3-HMAC-SHA256 签名方法
最近在接入一些腾讯云的API,腾讯是不是歧视我 iOS 没有 OC 示例,也没有 Swift 示例,可能是面向服务器的吧,但是边上安卓的童鞋直接复制JAVA代码就跑起来~~~我难受。
韦弦zhy
2020/03/20
3.2K0
Swift 实现腾讯云 TC3-HMAC-SHA256 签名方法
GitHub Actions + 腾讯云COS + SCF云函数 + 自动刷新CDN 完美自动化部署静态网站
作为强迫症患者,一直对自动化部署非常痴迷,个人认为全自动部署最重要的就是稳定可靠,经过研究测试,最终使用GitHub和腾讯云两大平台,成功完成了全自动部署网站的实践.
用户8851537
2021/07/30
1.6K0
CKafka系列学习文章 - 手动拼接和自动拼接请求URL(十)
导语:我们来搭建开发环境调用消息队列 CKafka--手动拼接和自动拼接请求URL,来调用获取消费分组offset的接口
发哥说消息队列
2019/09/05
1K0
APP 端签名方案
签名字符串:X-App-Key+X-App-Version+X-Device-Id+X-Platform+x-Nonce+$RequestMethod+$RequestPath+$Body+X-Timestamp
seif
2022/09/29
1.1K0
文字识别接入常见问题
https://cloud.tencent.com/act/event/ocrdemo
张世强
2020/09/20
2.9K0
文字识别接入常见问题
拒绝接口裸奔!开放API接口签名验证!
为开发者分配AccessKey(开发者标识,确保唯一)和SecretKey(用于接口加密,确保不易被穷举,生成算法不易被猜测)。
java思维导图
2020/09/30
2K0
拒绝接口裸奔!开放API接口签名验证!
人脸识别接入常见问题汇总
https://cloud.tencent.com/act/event/iaidemo
张世强
2020/09/20
5.2K0
人脸识别接入常见问题汇总
C#调用腾讯云文本翻译API,使用V3签名报错。
"Error":{"Code":"AuthFailure.SignatureFailure","Message":"The provided credentials could not be validated. Please check your signature is correct."
用户9018380
2021/09/20
2.2K0
相关推荐
腾讯云API:用Python使用腾讯云API(cvm实例)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档