大家说说自己的(邮箱,手机)验证码解决方案吧(非存储方案) - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
greatonce
V2EX    科技

大家说说自己的(邮箱,手机)验证码解决方案吧(非存储方案)

  •  
  •   greatonce 2016-05-05 20:26:39 +08:00 3472 次点击
    这是一个创建于 3496 天前的主题,其中的信息可能已经有所发展或是发生改变。

    假设是生成验证然后存储起来再验证,这种就没必要说了。

    大家一起交流一下可以不用存储的解决方案

    第 1 条附言    2016-05-12 00:32:01 +08:00

    根据回帖看,大家热情似乎不高啊,以下分享一下我的解决方案吧:

    1.先说一下非存储型验证码的目的和动机:

    之所以不希望存储类型的验证码,一来是想减少程序对外部资源的依赖,不管存储到file或是redis,这样始终会有额外的开销,需要维护额外的业务,减少对环境的依赖,这样对于后端分布式,去中心化的部署会很轻

    2.不过是非存储型验证码还是存储型验证码,对试错的次数肯定需要限制的,例如,在N时间内连续输入N次,暂停向该用户开放功能权限(登录,注册,找回密码)

    3.具体解决方案:

    1. 首先随机生成一组hash值(用于后面作为hash的salt)
    2. 然后获取用户输入的 phone number 或 email address
    3. 设置验证码有效期,这里默认为1小时
    4. 获取时间 Y-m-d H-1,Y-m-d H,Y-m-d H+1 生成三个时间,分别为当前时间(小时)的上一个小时,分别为当前时间(小时),分别为当前时间(小时)的下一个小时,具体为什么这样做,请看后面

    创建一个迭代,使用md5 hash

    for timeItem in timeContainer: for hash in hashSet: codes[] = md5(hash, phone or email, timeItem) // 如果需要数字验证码则可能需要在这里过滤一下,在装到codes

    最终所有的验证码都存储到codes,然后随机取一个code作为验证码给客户端就可以了,

    当客户端提交数据用于验证“验证码”是否正确的时候,继续用上面的参数生成所有的验证码,判断提交的验证码是否在codes里面,如果在里面就表示是有效的,否则无效。

    有人可能会说这个验证码可以重复使用,没错,在一个小时内是可以重复使用的,但验证码的目的就是验证 phone number 或 email address 的有效性,所以这不是什么问题。

    然后就是对试错次数的限制,如果不限制试错次数,那么验证码基本没有任何意义,尤其是4位数字验证码,运气差一点9000多次也差不多了,所以需要限制试错次数,这个不需要我再说了,大家都知道怎么做。

    上面提到的三个时间,上一小时,当前小时,下一小时,之所以这样是为了防止,用户在 23:59:59秒获取的验证码,而提交的时候是第二天了,计算不出验证码。

    最后,也许你有更好的解决方案,请不吝赐教,如果以上内容有改进或任何问题,请帮助指出,谢谢。

    14 条回复    2016-05-12 15:27:33 +08:00
    fireapp
        1
    fireapp  
       2016-05-05 21:27:05 +08:00
    1. 填写手机号或者邮箱
    2. 手动确认或者程序自动确认,请求验证码
    3. server 根据该手机号码返回一个 token, 跟验证码

    token 需要自定义小算法简单加密下:手机号 /邮箱 + 时间戳 + 验证码 + 校验码, 里面只有时间戳可逆
    4. 验证时根据 token 解析出时间戳,然后根据手机号或者邮箱,时间戳,用户提交验证码,根据小算法再次加密,跟 token 对比,一致就说明正确…其中时间戳主要用来限制失效性
    大概就是这样
    cstabor
        2
    cstabor  
       2016-05-10 18:06:25 +08:00
    重复使用呢?
    greatonce
        3
    greatonce  
    OP
       2016-05-10 20:53:43 +08:00
    @cstabor 这是个问题,不过能接收获取到验证码就表示(手机号码,邮箱)有效,所以暂时不考虑重复使用了,当然这些都是必须要有错误次数限制的, N 时间内连续输入错误 N 次,禁止一段时间。
    aec4d
        4
    aec4d  
       2016-05-11 11:20:28 +08:00
    非存储是不可能的 因为要后台要记录错误次数
    以手机验证为例
    用户上传手机号申请发送验证码 此时后台用 手机号:验证码为原文生成密文加上当前时间戳加上 HMAC 验证 用 cookies 返回
    用户上传该 cookies 、手机号、验证码后台进行验证 校检 hmac 验证时间 /验证码是否有效
    如果无效则记录失败次数(此处就是存储 可以写一个以时间段为限制的缓存类或者直接用 redis 等)
    greatonce
        5
    greatonce  
    OP
       2016-05-11 14:16:31 +08:00
    @aec4d 记录错误次数存储的是错误次数,不是验证码,为什么还需要 cookie 存储,感觉复杂了。
    aec4d
        6
    aec4d  
       2016-05-11 14:22:56 +08:00
    @greatonce cookies 不需要存储 只是需要在验证错误的时候记录 否则你错误次数和什么对应起来?
    greatonce
        7
    greatonce  
    OP
       2016-05-11 19:42:04 +08:00
    @aec4d 这样记录错误次数有什么意义,人家每次删除 cookie 不就行了。
    aec4d
        8
    aec4d  
       2016-05-11 20:15:32 +08:00
    @greatonce 假如 4 位数字验证码 别人最多尝试 1000 次就能破 记录则不会发生这种情况
    当然你要是根据 IP 限制请求频率也可以 比如 1 分钟最多请求 10 次 那么验证码 30 分钟有效期可以尝试 300 次 另外难免会有公用出口 ip 的用户
    aec4d
        9
    aec4d  
       2016-05-11 20:19:17 +08:00
    接上 限制 IP 也无解 大把的代理 IP 可用 如果不限制错误尝试 没什么安全性可言
    greatonce
        10
    greatonce  
    OP
       2016-05-12 00:27:53 +08:00
    @aec4d

    4 位数字, 1000 次就能破,你怎么得出这个结论的?

    我没有说不限制错误次数,但依赖的不是 IP 或者 hash 或者加密的 cookie ,

    而是记录请求获取验证码时的 phone number 或者 email address 就可以限制了,

    验证的最终目的是验证 phone number 或 email address 的有效性。


    --------------------------------------------------------------------------------


    根据回帖看,大家热情似乎不高啊,以下分享一下我的解决方案吧:

    1.先说一下非存储型验证码的目的和动机:

    之所以不希望存储类型的验证码,一来是想减少程序对外部资源的依赖,不管存储到 file 或是 redis ,这样始终会有额外的开销,需要维护额外的业务,减少对环境的依赖,这样对于后端分布式,去中心化的部署会很轻

    2.不过是非存储型验证码还是存储型验证码,对试错的次数肯定需要限制的,例如,在 N 时间内连续输入 N 次,暂停向该用户开放功能权限(登录,注册,找回密码)

    3.具体解决方案:

    1) 首先随机生成一组 hash 值(用于后面作为 hash 的 salt )
    2) 然后获取用户输入的 phone number 或 email address
    3) 设置验证码有效期,这里默认为 1 小时
    4) 获取时间 Y-m-d H-1 , Y-m-d H , Y-m-d H+1 生成三个时间,分别为当前时间(小时)的上一个小时,分别为当前时间(小时),分别为当前时间(小时)的下一个小时,具体为什么这样做,请看后面

    创建一个迭代,使用 md5 hash

    for timeItem in timeContainer:
    for hash in hashSet:
    codes[] = md5(hash, phone or email, timeItem) // 如果需要数字验证码则可能需要在这里过滤一下,在装到 codes



    最终所有的验证码都存储到 codes ,然后随机取一个 code 作为验证码给客户端就可以了,

    当客户端提交数据用于验证“验证码”是否正确的时候,继续用上面的参数生成所有的验证码,判断提交的验证码是否在 codes 里面,如果在里面就表示是有效的,否则无效。

    有人可能会说这个验证码可以重复使用,没错,在一个小时内是可以重复使用的,但验证码的目的就是验证 phone number 或 email address 的有效性,所以这不是什么问题。

    然后就是对试错次数的限制,如果不限制试错次数,那么验证码基本没有任何意义,尤其是 4 位数字验证码,运气差一点 9000 多次也差不多了,所以需要限制试错次数,这个不需要我再说了,大家都知道怎么做。

    上面提到的三个时间,上一小时,当前小时,下一小时,之所以这样是为了防止,用户在 23 : 59 : 59 秒获取的验证码,而提交的时候是第二天了,计算不出验证码。

    最后,也许你有更好的解决方案,请不吝赐教,如果以上内容有改进或任何问题,请帮助指出,谢谢。
    aec4d
        11
    aec4d  
       2016-05-12 11:04:22 +08:00
    @greatonce
    4 位数字是一万次 说错了
    "所以需要限制试错次数,这个不需要我再说了,大家都知道怎么做。"(这才是需要讨论的最重要的步骤)
    按照你的意思,假如每隔 1 分钟算一次 md5(mobile+timestamp)。 hashSet 有 3 个。那么一小时会产生 180 个 code 。那么这 180 个 code 都是有效的。无形中让猜的概率大了 180 倍
    和我写的思路并没有区别,你最后还是需要记录手机号的验证码次数
    greatonce
        12
    greatonce  
    OP
       2016-05-12 14:05:19 +08:00
    @aec4d

    你都没仔细看我的帖子吗?

    哪里会有 180 个验证码?

    在你没有加入讨论之前,我就说了肯定需要限制试错次数的,不限制试错次数,那验证码就没有什么意义。

    至于试错次数的解决方案,你认为是重要步骤,其实只要仔细看帖子就知道解决方案了,

    简单的存储键值对就可以了 phOne=>times or email=>times 就解决了,这也会是重点?

    我帖子里面说的是验证码的生成思路不需要存储,每次都是根据算法

    只希望大家的讨论都有建设性,不想辩论基础问题。
    aec4d
        13
    aec4d  
       2016-05-12 14:30:00 +08:00
    @greatonce 你的标题着重写非存储,
    我的第一个回复是:"非存储是不可能的 因为要后台要记录错误次数",
    你回复:“为什么还需要 cookie 存储”
    我回复:"cookies 不需要存储,存储的是错误记录"(手机号对应错误尝试次数,这是唯一需要记录的地方)
    =======================
    可能你说的非存储只是生成的 md5(hash, phone or email, timeItem)不需要存储,我理解的非存储是整个验证过程中的不需要存储
    =======================
    对于你后来的算法,是我理解错误了
    对于你说的算法。在第一个回复第三行已经给出
    over
    greatonce
        14
    greatonce  
    OP
       2016-05-12 15:27:33 +08:00
    @aec4d

    可能标题描述不清楚,验证码是不存储的,试错次数是存储的。(我从我的角度考虑,两个隶属不同服务,一个是验证码服务,另一个安全策略服务,例如 xss , csrf , anti spam )

    另外存储到 cookie 并不是理想有效的方法,因为 cookie 可以随时被清除
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2783 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 22ms UTC 12:53 PVG 20:53 LAX 04:53 JFK 07:53
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86