作者:糖果

以往我们讲到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:转载到其它平台请注明作者姓名及原文链接,请勿用于商业用途。

点击查看Lua FAQ