V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
greatonce
V2EX  ›  科技

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

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

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

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

    第 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   ·   实用小工具   ·   5296 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 07:23 · PVG 15:23 · LAX 23:23 · JFK 02:23
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.