如何对数据附加签名,防止伪造

Web 应用通常借助 Cookie 保存会话信息,这些信息一般并不敏感,但必须防止用户伪造。

我们先来考察一个应用场景:用户登录,后端将登录用户账号保存在 Cookie 中;后续访问是,浏览器会带上该 Cookie ,后端据此检查登录是否合法。

这个场景存在一个非常严重的安全漏洞,用户可以伪造 Cookie !举个例子,他只要将 Cookie 改成我的账号 fasionchan ,就可以用我的账号来操作!那么,我们应该如何应对伪造数据呢?

您可能想到对数据进行加密,离开密钥就无法解读数据,更何况伪造?加密适用保护敏感数据,它可以同时实现 保密防伪 两个目的。换句话讲,如果数据是不能被第三方获悉的,那就必须加密。

如果数据无须保密,而只想防伪,则可以为其计算 签名signature )。例子中用 Cookie 保存的登录账号就是一个典型的例子,让第三方知道了也没什么影响,关键是要防止别人伪造。

那么,怎么对数据计算签名呢?

哈希

众所周知,对一段文本进行哈希,得到的哈希值可以作为文本的摘要,或者叫做信息指纹。哈希算法分很多种,常用的有 MD5SHA 等等。同样的文本,用同样的的哈希算法,算出来的哈希值一定是一样的。以 MD5 为例:

1
2
3
import hashlib
text = 'fasionchan'
hashlib.md5(text.encode('utf8')).hexdigest() # a5ff4dddf920a287e1a3559836675e15

签名密钥

您可能会说,哈希计算得到摘要也没啥用呀,别人照样可以这样做。没错,但我们再引入一个密钥,情况就不同了:

1
2
3
4
5
import hashlib
secret = '123456'
user = 'fasionchan'
signature = hashlib.md5((user+secret).encode('utf8')).hexdigest()
print('{}-{}'.format(user, signature)) # fasionchan-65cd2de0af7cc1f5f765df7bdabd1e49

注意,这里密钥 secret 我随便用 123456 。密钥通常不能是简单的弱密码,不然很容易被破解。

现在将登录用户和签名拼在一起保存在 Cookie 中:

1
fasionchan-65cd2de0af7cc1f5f765df7bdabd1e49

当后端服务器收到 Cookie 后,可以重新计算签名,以此进行验证:

1
2
3
cookie = 'fasionchan-65cd2de0af7cc1f5f765df7bdabd1e49'
user, signature = cookie.split('-')
hashlib.md5((text+secret).encode('utf8')).hexdigest() == signature # True

如果计算得到的签名跟 Cookie 中保存的不一致,则说明数据是伪造的。由于第三方不掌握参与签名计算的 secret ,也就无法伪造数据。因此,secret 密钥必须妥善保管,绝对不能泄露。

弱密码

由于哈希算法可以暴力破解,如果黑客掌握了足够的数据和签名样本,而密钥又不够复杂,就很有可能被破解出来。举个例子,假设黑客有一个弱密码表,他只要逐个遍历验证,即可破解出密钥,如果密钥就在弱密码表里的话。

因此,密钥的选择必须足够复杂,绝对不能是一些常见的字符序列。

  • 最好大小写字母、数字和符号都有;
  • 长度足够长;
  • 最好是随机生成的,没有规律的;

有效期

某些场景数据需要设置有效期,这时可以在数据的基础上加一个时间戳:

1
2
3
4
5
6
7
8
import hashlib
import time

secret = '123456'
user = 'fasionchan'
ts = str(int(time.time()))
signature = hashlib.md5((user+ts+secret).encode('utf8')).hexdigest()
print('{}-{}-{}'.format(user, ts, signature)) # fasionchan-1673062003-37f3e061d217cd0d258c67c9f78c93fb

后端接到数据后,同样先将各个部分切分开,重新计算签名校验真伪。然后只需检查时间戳,即可判断数据是否还有效。需要特别注意,时间戳必须参与签名计算,否则别人可以伪造假的时间戳,从而绕过有效期限制。

小菜学网络】系列文章首发于公众号【小菜学编程】,敬请关注:

【小菜学网络】系列文章首发于公众号【小菜学编程】,敬请关注: