以往我们讲到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对上处理过程进行描述。
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152 | local --做成一个过期时间戳。 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 --对输入数据的判空操作 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 --对解包后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加密,相对也比一般的第三方实现更可靠。
写一个简单的加密程序:
1234 | local "crypto" ) local "crypto.hmac" ) local "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