CSRF攻击预防的Token生成原理
作者:糖果
以往我们讲到CSRF,谈及都是CSRF的攻击原理,这次讲一下预防CSRF,生成Token背后的
加密原理和具体实现例示。
1.Token构成。
从需求功能上来讲,为了防止CSRF工具,token需要具有不重复,另外,还含有特定的功能
信息,比如过期时间戳。
下面的图描述了一个token的数据构成:
Token的数据结构。
-----------------------------------------------------------------------------
| msg | separator | signature |
-----------------------------------------------------------------------------
| key | timestamp | . | Base64(sha256(msg)) |
-----------------------------------------------------------------------------
token由三部分组成:a).msg b). separator c).signature。
a). msg部分:而msg本身也有两部分组成:一部分,随机字符的主体,另一部分是过期时间戳。
b). 分隔符号:用符号分隔msg部分,和加密后生成的signature签名部分,这里用的是”.“
c). 签名signature。
signature签名,是对上面提到的msg,按照msg中提到的msg的信息部分,按照特定的秘锁进行加密。
token = base64(msg)格式化..base64(sha256("秘锁", msg))
2.Token的加密。
首先,是按照合适得加密方法对数据进行加密。这里我们通用的就使用了sha256散列算法,然后进行BASE64的格式转换。然后,我们需要在token串中隐含过期时间的设定,从需求上讲,每条与服务器交互的token有是有过期时间的,超过这个时间范围,就无效了,需要重新从服务器中取得。
3.Token的验证。
当用户从客户端,得到了token,再次提交给服务器的时候,服务器需要判断token的有效性,否则不加判断直接处理数据,token的生成就无意义了。
验证的过程是:
a). token解包。
先把接受到的token,进行分解。“.”为分隔符,分为msg部分+signature签名部分。
b). 比对签名。
对msg部分进行base64解码, decode_base64(msg)然后在对解码后的msg明文,进行同样的encode_base64(sha256(msg))加密。
秘锁相同,然后,判断加密后的数据和客户端传过来的token.signature的部分是否一致。如果一致,说明这个token是有效的。
c). 判断时间过期。
如果是有效的,取出msg.timestamp,和当前系统时间进行比较,如果过期时间小于当前时间,那这个token是过期的,需要重新的取得token。
原理都通用,此处使用lua对上处理过程进行描述。
local gen_token = function(key, expires)
--做成一个过期时间戳。
if expires == nil then
expires = os.time() + 60 + 60 * 8
end
--对msg部分进行base64编码。
local msg = encode_base64(
json.encode({
key = key,
expires = expires
}))
--进行sha256哈希。
local signature = encode_base64(hmac_sha256('testkey', msg))
--拼接成一条token。
return msg .. "." ..signature
end
local val_token = function(key,token)
--对输入数据的判空操作
if not (token) then
return nil, 'mssing csrf token'
end
--对token的msg部分,signature签名部分进行拆分。
local msg, sig = token:match("^(.*)%.(.*)$")
if not (msg) then
return nil, "malformed csrf token"
end
sig = encoding.decode_base64(sig)
--对解包后msg,按照相同的加密key:"testkey",重新进行sha256哈希,比对signature,
--如果不一致,说明这个token中的数据有问题,无效的token。
if not (sig == hmac_sha256('testkey', msg)) then
return nil, "invalid csrf token(bad sig)"
end
--对msg进行base64解码,判断其中的key和传入的key是否一致。
--如果不一致说明token也是无效的。
msg =json.decode(decode_base64(msg))
if not (msg.key == key) then
return nil, "invalid csrf token (bad key)"
end
--取出msg部分的时间戳,判断是否大于当前时间,如果大于,说明token过期无效了。
if not (not msg.expires or msg.expires > os.time()) then
return nil, "csrf token expired"
end
end
下面是关于Lua语言加密库,lua语言有别于其他语言,没有同意的官方指定加密库,为了便于读者,看后实践,下面对lua的加密库进行了补充描述。lua语言是一种弱类型的语言,简单明了,对于描述某些课题,便于表述,类似于伪语言,操作起来也很轻便,便于实践推敲算法。即使之后不适用lua,也可以很方面的迁移到其他语言。
我们在开发的工作中,难免要对一些数据进行加密处理,而加密模块的使用有是就必不可少。在lua官方的WIKI列表中就列出了,很多lua程序写的加密库,这写加密库有的是用纯lua写的,也有用lua调用C的程序实现加密。不过有些时候甄选这些库还是需要花一些时间精力,只是需要测试一下这是加密算是否是好用的。这是lua组织列出的一览列表。
http://lua-users.org/wiki/CryptographyStuff
说一下为什么要加密,我们面临的任务是什么!我们现在面临的任务是,要对一段字符串进行sha256算法加密。
我们从列表中选出了几个支持sha256加密的包,并说明一下这几个工具包。
1.SecureHashAlgorithm和SecureHashAlgorithmBW
这个工具包是支持sha256加密的,而且是纯lua方法的实现,问题是,这两个包分别依赖lua5.2和lua5.3。 而我们系统的运行环境是lua5.1,因为大部分的生产环境都是lua5.1,因为历史原因暂时没法改变。如果要把5.2的程序移植到5.1下运行,还需要移植一个lua5.2才独有的包,这是lua5.2升级之后才有的部件:bit32,而在lua5.3中又将这个部件去掉了,移植的动力不大,暂时不使用这个包。2.Lcrypt
这个包不是纯lua的实现,底层加密用的是C语言,而且额外还有依赖另外另个工具包 libTomCrypt和libTomMath,这两个包的官网已经被和谐了,github上有源码,所以要想让这个包正常运行需要手动make安装3个源码工程,还是算了,有时间的时候再装好测试一下,先暂时不用。网站:
http://www.eder.us/projects/lcrypt/
3.LuaCrypto
这个包的安装用的是luarocks,就比较简单了luarocks install luacrypto
我们选用这个包进行加密处理。
LuaCrypto其实是openssl库的前端lua调用,依赖openssl,openssl库显然会支持sha256加密,相对也比一般的第三方实现更可靠。
写一个简单的加密程序:
local crypto = require("crypto")
local hmac = require("crypto.hmac")
local ret = hmac.digest("sha256", "abcdefg", "hmackey")
print(ret)
ret的返回结果是,如下这个字符串。
704d25d116a700656bfa5a6a7b0f462efdc7df828cdbafa6fbf8b39a12e83f24
我们需要改造一下代码,在调用digest的时候指定输出的形式是raw二进制数据形式,然后在编码成base64的数据形式。
local ret = hmac.digest("sha256", "abcdefg", "hmackey",rawequal)
print(ret)
这时候的输出结果是:
cE0l0RanAGVr+lpqew9GLv3H34KM26+m+/izmhLoPyQ=
lua-base64
使用的是下面的库,lua库就是这样,有很多功能程序有很多的实现,并且很多非官方的第三方实现。
https://github.com/toastdriven/lua-base64
作者:糖果
PS:转载到其它平台请注明作者姓名及原文链接,请勿用于商业用途。