在请求一些开放平台的接口时,我们一般会在请求头中塞入一些签名相关的信息以证明身份。以微信API-V3的为例,请求头中包含的认证信息主要包含:
- signature(签名值)
- nonce(随机串)
- timestamp(时间戳)
本文将一步步介绍以上3个小玩意的含义和用途。 关键字:公钥、私钥、签名、中间人攻击、重发攻击
非对称加密 & signature
非对称加密是数据加密的一种方法,加密和解密分别使用公钥和私钥两种密钥完成,与对称加密的区别在于,非对称加密加密和解密使用的是不同的密钥,使用其中一个密钥(私钥或公钥)对数据加密后,必须使用另一个密钥才能解密,而对称加密则使用同一个密钥进行加密和解密。它们的示意图如下。
关于公钥、私钥、签名进行通信有一个最经典的解释,当然,如果里面几位主角的英文名让你头大,你也可以看我接下来提供的山寨版,请看下图(以小明为起点):
由此看出,小明和小红发送消息时总是使用对方的公钥加密信息,而使用自己的私钥对收到的信息进行解密。这也就意味着两方通信之前需要互相交换公钥并且藏好自己的私钥,使得即便有人劫持了在网络中传递的密文也无法解密得到信息,因为解密的私钥只有自己知道。但由于公钥是公开的,任何人都能拿到小明的公钥,如果有一个叫老王的人用小明的公钥伪装成小明给小红发信息,还把小红给约出去了,那就悲剧了。为了对信息进行身份标识,就得使用signature(签名)了,首先,小明和小红提前做了如下约定:
如此一来小红只需要比对双方计算的信息摘要是否相等,如果不相等则说明签名使用的不是小明的私钥或者信息摘要被人篡改了。于是将签名加入到他们的通信(以小明为起点):
由于可以保证生成摘要的信息不会被篡改,在接口签名中,摘要可以由请求方法,请求路径,请求参数等等一些不希望被篡改的信息生成,以微信的签名API-V3为例:
signature = base64(SHA256withRSA("HTTP请求方法\nURL\n请求时间戳\n请求随机串\n请求报文主体\n", privateKey))
至此老王就无法随意伪装成小明了,但这仍然阻挡不了老王。前面提到小明和小红必须交换公钥才能通信,在交换过程中,如果被老王劫持,小明和小红持有的对方的公钥都将会被篡改为老王的,老王就能伪装成小明或小红并与对方通信,这也就是中间人攻击(Man-in-the-MiddleAttack):
为了解决这一问题,小明和小红需要向第三方权威证书发布机构登记自己的公钥,从而获取证书(其中包含公钥),交换公钥也升级为交换证书。当小红收到小明的证书时就可以到第三方权威机构确认其中的公钥是否是小明的。
重发攻击
有了第三方权威证书发布机构的认证,老王就不能肆意伪装自己了,但老王依然表示
于是乎,阴险的老王秀了波操作(此处省略了加密和解密过程):
即,老王虽然不能伪装或者篡改小明和小红的通信信息,但是能窃取并重发他们发出去的信息,造成的破坏也将会是巨大的,比如老实人小明可能要破产。这也就是重发攻击(Replay Attacks)。
使用nonce和timestamp解决重发攻击
这里我们抛开小明等人谈论nonce和timestamp,而以熟知的web应用来举例。首先,客户端请求前生成一个随机数nonce,作为该请求的唯一标识,签名时加入该nonce以保证nonce不被篡改,同时由于签名中加入了随机数nonce,因此相同的请求参数,每次签名得出的结果都不同,保证了签名不可预测,nonce放在header中一起发送给服务端,以便服务端使用该nonce进行验签,并记录起来。后续请求应该判断该nonce是否使用过,若已使用过,则认为是重发攻击,这样就可以避免同一个请求被重发多次。但是,服务器记录的nonce会随着请求次数的增多而增多,客户端生成重复的nonce的概率也会增高,一些正常的请求可能会被误判为重发攻击。此时有必要为nonce设置一个有效期,比如60秒,超过60秒就删除。但是,问题又来了,因为服务器只保留60秒内的nonce,则一个已发送的请求在60秒后又可以重发了。因此还需要加入一个时间戳timestamp参数用于表示请求时间,同样需要加入到签名中以保证timestamp不被篡改,服务器判断请求时间如果在60秒以前,则认为这是一个过时的请求,抛出异常。因此,服务器只需要记录60秒以内的nonce并拒绝60秒以前的请求就可以确保没有请求被重发了。
总结
- 信息摘要是从信息中提取的不想被篡改的部分。
- 签名是对信息摘要使用私钥进行加密的结果,用于证明信息的发出者。
- 加密时加入nonce用于标识一次唯一的加密,防止重发攻击。
- 加密时加入nonce可以使加密结果不可预测。
- 加密时加入timestamp用以解决nonce随请求次数无限增多的问题。
最终,小明左手拿着nonce右手拿着timestamp战胜了老王。