CSRF攻击预防的Token生成原理

      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对上处理过程进行描述。

  ?

  1

  2

  3

  4

  5

  6

  7

  8

  9

  10

  11

  12

  13

  14

  15

  16

  17

  18

  19

  20

  21

  22

  23

  24

  25

  26

  27

  28

  29

  30

  31

  32

  33

  34

  35

  36

  37

  38

  39

  40

  41

  42

  43

  44

  45

  46

  47

  48

  49

  50

  51

  52

  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

  –对解包后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组织列出的一览列表。

  说一下为什么要加密,我们面临的任务是什么!我们现在面临的任务是,要对一段字符串进行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个源码工程,还是算了,有时间的时候再装好测试一下,先暂时不用。

  3.LuaCrypto

  这个包的安装用的是luarocks,就比较简单了

  luarocks install luacrypto

  我们选用这个包进行加密处理。

  LuaCrypto其实是openssl库的前端lua调用,依赖openssl,openssl库显然会支持sha256加密,相对也比一般的第三方实现更可靠。

  写一个简单的加密程序:

  ?

  1

  2

  3

  4

  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库就是这样,有很多功能程序有很多的实现,并且很多非官方的第三方实现。

发表评论