CSRF攻击预防的Token生成原理

时间:2021-10-13 13:41:09

以往我们讲到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
gen_token = function(key, expires)
    --做成一个过期时间戳。    ifexpires == 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。    returnmsg .. "."..signatureend  local 
val_token = function(key,token)
    --对输入数据的判空操作    ifnot (token) then     returnnil,'mssing csrf token'    end         --对token的msg部分,signature签名部分进行拆分。    local msg, sig = token:match("^(.*)%.(.*)$")    ifnot (msg) then         returnnil,"malformed csrf token"    end     --对解包后msg,按照相同的加密key:"testkey",重新进行sha256哈希,比对signature,    --如果不一致,说明这个token中的数据有问题,无效的token。    ifnot (sig == hmac_sha256('testkey', msg)) then         returnnil,"invalid csrf token(bad sig)"    end      --对msg进行base64解码,判断其中的key和传入的key是否一致。    --如果不一致说明token也是无效的。    msg =json.decode(decode_base64(msg))    ifnot (msg.key == key) then     returnnil,"invalid csrf token (bad key)"       end         --取出msg部分的时间戳,判断是否大于当前时间,如果大于,说明token过期无效了。    ifnot (not msg.expires or msg.expires > os.time()) then         returnnil,"csrf token expired"    endend

 

下面是关于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 = 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