• 使用Lua GD库动态生成验证码图片


    最近得闲,学习一下Lua。

    Lua下有个gd图形库,通过简单的Lua语句就能控制、生成图片。

    之前在某个项目中要用到验证码,当时对这方面不太了解,就采用最不专业的做法:预先准备好若干验证码图片,把对应的值存入到数据库;使用时随机取出一对“图片-验证码值”供用户验证。这样做的好处是减少编码复杂度和服务器负担,但是问题也显而易见:预先准备的验证码图片数量有限,要是有人恶意攻击的话,这种验证码恐怕只是个摆设。要是专业人士见到我的这种实现,只怕会笑掉大牙。

    当时也考虑过动态生成图片,随机生成几个数字、字母组成的验证码,然后将此验证码生成图片,最后对验证码图片进行模糊处理(倾斜、模糊、加干扰等)。验证码的生成到不难,有比较成熟的随机函数可以做到;但是将验证码生成图片并做模糊处理,当时就没有什么好的办法了。考虑过Java下应该有图形库可以做到这一点,但是觉得代码量肯定不小,运行效率恐怕也难以保证,所以就没深究。

    直到最近接触到Lua的gd库,才重新想起这事。关于该gd库的细节,请参看官方文档:http://lua-gd.luaforge.net/manual.html

    不说废话,直接上代码:

     1 require("gd")
     2 
     3 --定义词典
     4 
     5 dict={'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','1','2','3','4','5','6','7','8','9','0'}
     6 
     7 --随机种子
     8 
     9 math.randomseed(os.time())
    10 
    11 im2 = gd.createTrueColor(10040)
    12 
    13 white = im2:colorAllocate(255255255)
    14 
    15 stringmark=""
    16 
    17 for i=1,6 do
    18 
    19 stringmark=stringmark..dict[math.random(62)]
    20 
    21 end
    22 
    23 im2:string(gd.FONT_GIANT, 1810, stringmark, white)
    24 
    25 im2:png("./output/验证码.png",100)
    26 
    27 

    这样总共用不到20行代码就实现了一个简单的验证码,效果如下:

    虽然基本实现了验证码图片的生成,但还不太理想;要实现真正可用的验证码,大概还需要做如下处理:设置不同字体、字符要随机倾斜、要随机模糊字符、要增加干扰等。

    因此在此基础上略作了改进:设置不同字体、字符随机倾斜;至于随机模糊字符、增加干扰暂时还没想好怎么处理。最后代码如下:

      1 require("gd")
      2 
      3 require("lfs")
      4 
      5 --定义词典
      6 
      7 dict={'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','1','2','3','4','5','6','7','8','9','0'}
      8 
      9 --随机种子
     10 
     11 math.randomseed(os.time())
     12 
     13 im2 = gd.createTrueColor(10040)
     14 
     15 white = im2:colorAllocate(255255255)
     16 
     17 stringmark=""
     18 
     19 fonts={}
     20 
     21 --查找字体
     22 
     23 function searchFont()
     24 
     25 local i=1
     26 
     27 for file in lfs.dir("./复件 output/"do
     28 
     29 if file~="." and file~=".." then
     30 
     31 fonts[i]=string.sub(file,1,string.find(file,"ttf")).."tf"
     32 
     33 print(fonts[i])
     34 
     35 i=i+1
     36 
     37 end
     38 
     39 end
     40 
     41 end
     42 
     43 --测试不同字体的效果
     44 
     45 function testFont()
     46 
     47 searchFont()
     48 
     49 if table.getn(fonts)==0 then --没有指定字体路径,就搜索系统字体
     50 
     51 for file in lfs.dir("C:/WINDOWS/Fonts/"do
     52 
     53 if string.find(file,".ttf")and not string.find(file,"esri"then
     54 
     55 makeStringWithRotate(file)
     56 
     57 end
     58 
     59 end
     60 
     61 else--否则就使用指定字体
     62 
     63 for i=1,table.getn(fonts) do
     64 
     65 makeStringWithRotate(fonts[i])
     66 
     67 end
     68 
     69 end
     70 
     71 end
     72 
     73 --生成带角度字符串
     74 
     75 function makeStringWithRotate(font)
     76 
     77 for i=1,6 do
     78 
     79 local s=dict[math.random(62)]
     80 
     81 im2:stringFT(white,"C:/WINDOWS/Fonts/"..font,18,math.random()/math.pi,5+(i-1)*1525, s)
     82 
     83 stringmark=stringmark..s
     84 
     85 end
     86 
     87 im2:png("./output/"..font..".png",100)
     88 
     89 -- 清理工作,准备下次使用
     90 
     91 stringmark=""
     92 
     93 im2 = gd.createTrueColor(10040)
     94 
     95 end
     96 
     97 --生成普通字符串
     98 
     99 function makeString()
    100 
    101 im2 = gd.createTrueColor(10040)
    102 
    103 white = im2:colorAllocate(255255255)
    104 
    105 for i=1,6 do
    106 
    107 stringmark=stringmark..dict[math.random(62)]
    108 
    109 end
    110 
    111 im2:string(gd.FONT_GIANT, 1810, stringmark, white)
    112 
    113 stringmark=""
    114 
    115 im2:png("./output/验证码.png",100)
    116 
    117 end
    118 
    119 testFont()
    120 
    121 --makeString()
    122 
    123 

    说明如下:

    由于不同字体的显示效果不一样,在有些字体中0、o、O不分;i、I、1、l、L不分;有些字体无法显示;这样导致验证码无法识别,因此必须去掉不适合用来生成验证码的字体。但是由于系统字体太多,如果逐一由手工验证,将是一件繁复而无意义的工作,因此在这里我采用“循环验证”的方式来处理:

    1.使用系统中每一种字体都生成一张验证码图片放到指定目录A中("./output/"),图片名即字体名

    2.依次对这些验证码图片进行验证,剔除不适合做验证码的字体

    3.将剔除后合格的验证码图片拷到指定目录B下(C:/luaaio_2.0_windows/test/test_gd/复件 output/),删除原目录A("./output/")中的内容

    4.重新运行本程序,将读取目录B中合格字体,然后使用这些字体创建验证码图片到目录A中

    5.重复步骤2,继续剔除不合格的字体,直到得到所有合格的字体。

    最后,在我自己系统上经过多次运行,最后从几百个字体中得到比较容易分辨、适合作验证码的字体如下:

    courbd.ttf

    courbi.ttf

    DejaVuMonoSans.ttf

    DejaVuMonoSansBold.ttf

    DejaVuMonoSansBoldOblique.ttf

    DejaVuMonoSansOblique.ttf

    lucon.ttf

    monosbi.ttf

    nina.ttf

    simhei.ttf

    simkai.ttf

    swissci.ttf

    tahomabd.ttf

    timesbd.ttf

    timesbi.ttf

    timesi.ttf

    trebuc.ttf

    trebucit.ttf

    效果如下: 

    说明:上面代码中为了访问文件系统,使用了lua扩展“LuaFileSystem”,具体些请参看文档:http://keplerproject.github.com/luafilesystem/index.html

    现在找出合适的字体了,做进一步改进:每次生成使用随机字体。

    加上背景颜色,代码如下:

      1 require("gd")
      2 
      3 require("lfs")
      4 
      5 --定义词典
      6 
      7 dict={'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','1','2','3','4','5','6','7','8','9','0'}
      8 
      9 --随机种子
     10 
     11 math.randomseed(os.time()*2-1023)
     12 
     13 im2 = gd.createTrueColor(10040)
     14 
     15 fg = im2:colorAllocate(129,32,28)
     16 
     17 bg = im2:colorAllocate(216,235,238)
     18 
     19 FONT_PATH="C:/WINDOWS/Fonts/"
     20 
     21 fonts={"courbd.ttf","courbi.ttf","DejaVuMonoSans.ttf","DejaVuMonoSansBold.ttf","DejaVuMonoSansBoldOblique.ttf","DejaVuMonoSansOblique.ttf","lucon.ttf","monosbi.ttf","nina.ttf","simhei.ttf","simkai.ttf","swissci.ttf","tahomabd.ttf","timesbd.ttf","timesbi.ttf","timesi.ttf","trebuc.ttf","trebucit.ttf"}
     22 
     23 --生成的随机码
     24 
     25 stringmark=""
     26 
     27 --初始化:创建图片、设置背景
     28 
     29 function init()
     30 
     31 im2 = gd.createTrueColor(10040)
     32 
     33 im2:filledRectangle(0,0,100,40,bg)
     34 
     35 stringmark=""
     36 
     37 end
     38 
     39 --查找字体
     40 
     41 function searchFont()
     42 
     43 local i=1
     44 
     45 for file in lfs.dir("./复件 output/"do
     46 
     47 if file~="." and file~=".." and file~="Thumbs.db" then
     48 
     49 fonts[i]=string.sub(file,1,string.find(file,"ttf")).."tf"
     50 
     51 print(fonts[i])
     52 
     53 i=i+1
     54 
     55 end
     56 
     57 end
     58 
     59 end
     60 
     61 --测试不同字体的效果
     62 
     63 function testFont()
     64 
     65 searchFont()
     66 
     67 if table.getn(fonts)==0 then --没有指定字体路径,就搜索系统字体
     68 
     69 for file in lfs.dir(FONT_PATH) do
     70 
     71 if string.find(file,".ttf")and not string.find(file,"esri"then
     72 
     73 makeStringWithRotate(file)
     74 
     75 end
     76 
     77 end
     78 
     79 else--否则就使用指定字体
     80 
     81 for i=1,table.getn(fonts) do
     82 
     83 makeStringWithRotate(fonts[i])
     84 
     85 end
     86 
     87 end
     88 
     89 end
     90 
     91 --生成带角度字符串
     92 
     93 function makeStringWithRotate(font)
     94 
     95 for i=1,6 do
     96 
     97 local s=dict[math.random(62)]
     98 
     99 im2:stringFT(white,FONT_PATH..font,18,math.random()/math.pi,5+(i-1)*1525, s)
    100 
    101 stringmark=stringmark..s
    102 
    103 end
    104 
    105 im2:png("./output/"..font..".png",100)
    106 
    107 -- 清理工作,准备下次使用
    108 
    109 stringmark=""
    110 
    111 im2 = gd.createTrueColor(10040)
    112 
    113 end
    114 
    115 --生成普通字符串
    116 
    117 function makeString()
    118 
    119 im2 = gd.createTrueColor(10040)
    120 
    121 white = im2:colorAllocate(255255255)
    122 
    123 for i=1,6 do
    124 
    125 stringmark=stringmark..dict[math.random(62)]
    126 
    127 end
    128 
    129 im2:string(gd.FONT_GIANT, 1810, stringmark, white)
    130 
    131 stringmark=""
    132 
    133 im2:png("./output/验证码.png",100)
    134 
    135 end
    136 
    137 --使用随机字体生成带角度的字符串
    138 
    139 function makeIt()
    140 
    141 makeStringWithRotate(fonts[math.random(18)])
    142 
    143 end
    144 
    145 --调用接口:直接使用下面的三个函数(之一),即可得到不同类型的验证码
    146 
    147 makeIt()
    148 
    149 --testFont()
    150 
    151 --makeString()
    152 
    153 

    效果如下:

    奇怪的是:在makeIt()中,math.random(18)在似乎在一定时间内返回固定值,可能是因为使用的是系统时间做随机种子。

    这个程序还有如下不足:

    (1)没有加“字符模糊”和“干扰”,还是比较容易被破解。这个目前还没什么好的思路,暂时就不考虑。

    (2)由于验证码只是简单的采用系统时间作为随机数种子生成的,是一种伪随机数。如果通过某种方式得到这个随机数的种子(即系统时间),然后根据相同的随机数算法,是完全可以计算出验证码的:这样验证码就不攻自破了。因此在这里可以考虑对这个随机数种子做进一步处理,将系统时间按照某种算法进行处理,得到的结果作为随机数种子;这样即使得到了系统时间,如果不知道这个算法,同样得不到最后的随机数种子。据我估计:这个算法应该不需要很复杂,即使作简单的四则运算,再配合随机数算法,应该是不那么容易破解的。

    (3)还可以对验证码图片的随机性作进一步改进:字符颜色、背景颜色都可以随机,不过这样可能搭配出来的颜色会看不清(但可以预定义一组颜色进行处理)。不过没什么价值,也就罢了。

    关于验证码,我没有作专门研究:不知道在实际应用中的验证码是如何实现的,也不清楚到底需要考虑哪些问题。这里仅仅是我学习Lua的gd库时的一个练习,没有在实际生产系统中检验过,有什么问题欢迎指正。

    --------

    ps:关于增强验证码的随机性,觉得还可以改进:在这里预先定义的字典都是固定的,按照“小写字母-大写字母-数字”的顺序排列,攻击者如果得到随机种子和这个字典顺序,按照相同的随机算法,是可以计算出验证码的。要解决这个问题,就得从这三个因素下手:

    (1)对随机算法保密,即使得到随机种子和词典,也无法计算出验证码。这是最理想的方法,但很难实现:大家用的都是lua库中的随机库,除非自己对随即算法做改进,并且保密起来。

    (2)对随机种子保密,前面已经提到过。

    (3)对词典保密。这里采用的是固定的词典,按字母顺序排列,很容易就被猜到。要改进的话,可以在每次调用时初始化词典,采用随机算法将词典顺序打乱;这样每次调用时采用的都是随机词典,也会增加破解难度。问题是这样一来,计算这个随机词典也需要额外开销,对整个系统来说是不是合算,也需要考虑;而且,如果生成随机词典的种子被人得到,随机词典也会被人破解。

    最近想用php写个网站,想把这套验证码搬上去,不知是否可行。对php不是很了解,听说php有个扩展实现了lua解释器,不知能不能用?

  • 相关阅读:
    第六篇:python高级之网络编程
    第五篇:python高级之面向对象高级
    sublime插件开发教程
    Metatable In Lua 浅尝辄止
    cocos2dx-lua绑定之代码编辑器
    sublime入门文章
    Sublime Text快捷键
    lua中文教程【高级知识】
    lua基本语法
    Lua 不是 C++
  • 原文地址:https://www.cnblogs.com/chutianyao/p/1782008.html
Copyright © 2020-2023  润新知