V2EX 首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Sponsored by
二向箔安全
​一对一的线上 web 安全培训服务
咨询微信:twosecurityrefer
Promoted by 二向箔安全
V2EX  ›  程序员

API 接口签名验证

  •  
  •   hellojammy · 2016-12-08 21:33:30 +08:00 · 2979 次点击
    这是一个创建于 373 天前的主题,其中的信息可能已经有所发展或是发生改变。

    点击访问原文 您还可以加入全栈技术交流群( QQ 群号: 254842154 )


    系统从外部获取数据时,通常采用 API 接口调用的方式来实现。请求方和 接口提供方之间的通信过程,有这几个问题需要考虑:

    1 、请求参数是否被篡改;

    2 、请求来源是否合法; *3 、请求是否具有唯一性。

    今天跟大家探讨一下主流的通信安全解决方案。

    参数签名方式

    这种方式是主流。它要求调用方按照约定好的算法生成签名字符串,作为请求的一部分,接口提供方验算签名即可知是否合法。步骤通常如下:

    ①接口提供方给出 appid 和 appsecret

    ②调用方根据 appid 和 appsecret 以及请求参数,按照一定算法生成签名 sign

    ③接口提供方验证签名

    生成签名的步骤如下:

    ①将所有业务请求参数按字母先后顺序排序

    ②参数名称和参数值链接成一个字符串 A

    ③在字符串 A 的首尾加上 appsecret 组成一个新字符串 B

    ④对字符串进行 md5 得到签名 sign

    假设请求的参数为: f=1,b=23,k=33 ,排序后为 b=23,f=1,k=33 ,参数名和参数值链接后为 b23f1k33 ,首尾加上 appsecret 后 md5 : md5(secretkey1value1key2value2...secret)。

    签名的 php 版本实现:

    	public static function sign($appSecret, $params) {
    	if (!is_array($params)) 
    		$params = array();
    
    	ksort($params);
    	$text = '';
    	foreach ($params as $k => $v) {
    		$text .= $k . $v;
    	}
    
    	return md5($appSecret . $text . $appSecret);
    }
    

    接口调用方的请求地址类似于:

    /api/?f=1&b=23&k=33&sign=signValue
    

    以上签名方法安全有效 地解决了参数被篡改和身份验证的问题,如果参数被篡改,没事,因为别人无法知道 appsecret ,也就无法重新生成新的 sign 。

    这里使用了 md5 的算法进行签名,也可以自行选择其他签名方式,例如 RSA , SHA 等。

    另外,多说一句,微信公众号开发时,验证服务器地址的有效性也采用了类似的方法,只是生成签名的方法不一样。

    请求唯一性保证

    md5 签名方法可以保证来源及请求参数的合法性,但是请求链接一旦泄露,可以反复请求,对于某些拉取数据的接口来说并不是一件好事,相当于是泄露了数据。

    在请求中带上时间戳,并且把时间戳也作为签名的一部分,在接口提供方对时间戳进行验证,只允许一定时间范围内的请求,例如 1 分钟。因为请求方和接口提供方的服务器可能存在一定的时间误差,建议时间戳误差在 5 分钟内比较合适。允许的时间误差越大,链接的有效期就越长,请求唯一性的保证就越弱。所以需要在两者之间衡量。

    秘钥的保存

    在签名的过程中,起到决定性作用之一的是 appsecret ,因此如何保存成为关键。我们分类讨论。

    接口调用方的代码跑在服务器的情况比较好办,除非服务器被攻陷,否则外接无法知道 appsecret ,当然,要注意不能往日志里写入 appsecret 的值,其他敏感值也禁止写入日志,如账号密码等信息。

    假如是客户端请求接口,就需要多想一步了。假如把 appsecret 硬编码到客户端,会有反编译的风险,特别是 android 。可以在客户端登陆验证成功后,返回给客户端的信息中带上 appsecret(当然,返回的数据也可能被拦截,真是防不胜防啊。。。)。特别说明一下,在 android 开发中,假如硬要把 appsecret 硬编码,建议把 appsecret 放到 NDK 中编译成 so 文件, app 启动后去读取。

    TOTP : Time-based One-time Password Algorithm (基于时间的一次性密码算法)

    在一些小型项目中,可能不需要复杂的签名校验,只需要 做调用方的身份验证。TOTP(rfc6238)即可满足。

    TOTP 是基于时间的一次性算法,客户端和服务器端约定秘钥,加入时间作为运算因子得到一个 6 位数字。客户端请求服务端时生成一个 6 位数字,服务端使用相同算法验证这个 6 位数字是否合法。

    下面再展开一下讨论,跟本文讨论的主题关系不大。

    TOTP 允许 客户端和服务器端存在时间误差,如口令在 N 分钟内有效。给出一下源码供大家参考:

    与 TOTP 类似的还有 HTOP,它是基于次数的验证算法。这里不展开讨论。

    TOTP 流程

    基于 TOTP 还有很多应用,例如动态的登录口令。用户登录时,除了需要输入设置的密码外,还需要输入动态密码,每个用户的秘钥都不一样,用户的手机端安装一个 app 即可实现,动态口令每 N 分钟变化一次。 android 客户端在各大应用市场搜索 google-authenticator,如 百度应用市场, ios 客户端在 appstore 也可搜索得到。下面给出部分下载链接:

    支付宝、 QQ 令牌、银行客户端等这些手机客户端中也有类似的应用,在验证密码之后会多出一道动态口令的验证,他们使用的方案都类似于 google-authenticator 。

    大公司的运维人员,甚至是所有员工登录内部 OA 系统(单点登录),都需要 PIN+令牌码的双重验证( PIN 是自行设置的固定密码,令牌码则是动态口令码),他们通常使用 RSA SecurID 双因素动态口令身份认证解决方案。


    点击访问原文 您还可以加入全栈技术交流群( QQ 群号: 254842154 )


    SecureID_token

    24 回复  |  直到 2016-12-10 09:17:30 +08:00
        1
    bdbai   2016-12-08 21:58:20 +08:00 via Android
    secret key 编译进 so 里也可以逆出来,不知道有没有更好的方案来保存 secret key 。
        2
    sunchen   2016-12-08 22:50:06 +08:00
    最近逆了二三十个 App ,全都是这种签名套路,签名算法写到 so 里然后加固下会安全点, 另外签名的时候最好不要用常见的 md5 , aes , sha1 , rsa 之类的,代码特征比较明显,容易猜到
        3
    Sight4   2016-12-08 22:51:35 +08:00
    实际上,如果考虑到通信双方的互信问题, RSA+SSL 才是更好的解决办法, RSA 的私钥是由通信双方各自保存,绝不能泄漏给第三方。缺点是 RSA 的消耗实在是太高了
        4
    helloccav   2016-12-08 22:54:37 +08:00
    @sunchen "签名的时候最好不要用常见的 md5 , aes , sha1 , rsa 之类", 对这个有点疑问,你的意思是自己造一个加密算法? 以前 V2EX 里曾经有帖子讨论这个自己造一个加密算法是否更安全,结论是更不安全.
        5
    sunchen   2016-12-08 23:59:11 +08:00
    @helloccav 安全不安全是相对的,也许自己造的算法不完备,但是被别人一眼看出你在用 md5 和被别人跟踪了两个小时代码才逆出来你这个不完备的方案哪个更好呢
        6
    alexgor   2016-12-09 00:11:52 +08:00 via Android
    太实用了
        7
    Kylinsun   2016-12-09 00:14:41 +08:00 via Android
    mark
        8
    raysonx   2016-12-09 00:44:13 +08:00 via iPad
    首先,理论上密钥就不应该保存在客户端。
    然后,你自己造签名算法也没用,你总要在客户端保存密钥并且附带签名算法,我直接调你的库的可以了。
        9
    raysonx   2016-12-09 00:49:33 +08:00 via iPad
    @sunchen 我当年在业余时间做过 12306 的抢票软件, 12306 的 web 端在提交订单时就附带签名信息,然而并没有什么用,我直接用调试器追踪到 js 签名函数,把整个函数用换成 C 重新实现了一遍,签名就过掉了。根本不用研究它怎么算的,直接无脑做语言转译就行。就算不能转译,我还可以加一个互操作层直接调它的函数。
    另外我当年移植安卓版的百度贴吧客户端到 windows phone 也是这么做的,
        10
    raysonx   2016-12-09 00:56:17 +08:00 via iPad   ♥ 1
    @bdbai 这个是无解的,因为密钥和签名算法都已经公开。
        11
    lovebeyondalways   2016-12-09 07:16:32 +08:00 via Android
    好文章
        12
    liuxu   2016-12-09 09:30:26 +08:00
    @sunchen 用自己的算法伪造成 md5 格式
        13
    alouha   2016-12-09 10:00:29 +08:00
    很实用,文章中说到的,工作中基本都接触过,坐等大神给出更好的客户端保存“ appsecret ” 的方式……
        14
    ryanking8215   2016-12-09 10:07:56 +08:00
    @sunchen 签名算法不是都公开的吗?例如 amazon s3,weixin 等的. 要保密的是 app secrect key 吧
        15
    sweat89   2016-12-09 10:43:23 +08:00
    防篡改用签名
    防伪造用 https
        16
    hellojammy   2016-12-09 11:28:06 +08:00
    @sweat89 前端的签名岂不是会别人看到算法?你是怎么弄的
        17
    hellojammy   2016-12-09 11:50:52 +08:00   ♥ 1
    @bdbai 恩,只是提高了门槛而已。更好的方式就是每次在登录的时候拿到 secretkey ,每次都不一样。
        18
    sunchen   2016-12-09 14:20:00 +08:00
    @raysonx 主要目的是提高别人破解你的成本,不存在绝对的安全。
        19
    sunchen   2016-12-09 14:21:26 +08:00
    @liuxu 反正就是给攻击者挖坑就行了,别人跳的过去跳不过去没法定论,但是多挖坑准没错
        20
    ljcarsenal   2016-12-09 17:06:24 +08:00
    不错
        21
    bdbai   2016-12-09 18:36:09 +08:00 via Android
    @raysonx 密钥不保存在客户端,是每次领一个么?这样也能被逆出来的吧。
        22
    raysonx   2016-12-09 19:24:52 +08:00 via Android
    @bdbai 但是你怎么领呢?我也可以拿到你领密钥的 API 自己去领啊,关键你没法对客户程序鉴权。
        23
    laxenade   2016-12-10 00:42:29 +08:00
    如果是前端调用 api 有什么好的签名方法吗
        24
    ihuotui   2016-12-10 09:17:30 +08:00 via Android
    @raysonx 那就是用户就是破解者,然后可以根据特征封用户了
    DigitalOcean
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   鸣谢   ·   1255 人在线   最高记录 3541   ·  
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.0 · 56ms · UTC 16:44 · PVG 00:44 · LAX 08:44 · JFK 11:44
    ♥ Do have faith in what you're doing.
    沪ICP备16043287号-1