假设是生成验证然后存储起来再验证,这种就没必要说了。
大家一起交流一下可以不用存储的解决方案
根据回帖看,大家热情似乎不高啊,以下分享一下我的解决方案吧:
1.先说一下非存储型验证码的目的和动机:
之所以不希望存储类型的验证码,一来是想减少程序对外部资源的依赖,不管存储到file或是redis,这样始终会有额外的开销,需要维护额外的业务,减少对环境的依赖,这样对于后端分布式,去中心化的部署会很轻
2.不过是非存储型验证码还是存储型验证码,对试错的次数肯定需要限制的,例如,在N时间内连续输入N次,暂停向该用户开放功能权限(登录,注册,找回密码)
3.具体解决方案:
创建一个迭代,使用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秒获取的验证码,而提交的时候是第二天了,计算不出验证码。
最后,也许你有更好的解决方案,请不吝赐教,如果以上内容有改进或任何问题,请帮助指出,谢谢。
1
fireapp 2016-05-05 21:27:05 +08:00
1. 填写手机号或者邮箱
2. 手动确认或者程序自动确认,请求验证码 3. server 根据该手机号码返回一个 token, 跟验证码 token 需要自定义小算法简单加密下:手机号 /邮箱 + 时间戳 + 验证码 + 校验码, 里面只有时间戳可逆 4. 验证时根据 token 解析出时间戳,然后根据手机号或者邮箱,时间戳,用户提交验证码,根据小算法再次加密,跟 token 对比,一致就说明正确…其中时间戳主要用来限制失效性 大概就是这样 |
2
cstabor 2016-05-10 18:06:25 +08:00
重复使用呢?
|
3
greatonce OP @cstabor 这是个问题,不过能接收获取到验证码就表示(手机号码,邮箱)有效,所以暂时不考虑重复使用了,当然这些都是必须要有错误次数限制的, N 时间内连续输入错误 N 次,禁止一段时间。
|
4
aec4d 2016-05-11 11:20:28 +08:00
非存储是不可能的 因为要后台要记录错误次数
以手机验证为例 用户上传手机号申请发送验证码 此时后台用 手机号:验证码为原文生成密文加上当前时间戳加上 HMAC 验证 用 cookies 返回 用户上传该 cookies 、手机号、验证码后台进行验证 校检 hmac 验证时间 /验证码是否有效 如果无效则记录失败次数(此处就是存储 可以写一个以时间段为限制的缓存类或者直接用 redis 等) |
8
aec4d 2016-05-11 20:15:32 +08:00
@greatonce 假如 4 位数字验证码 别人最多尝试 1000 次就能破 记录则不会发生这种情况
当然你要是根据 IP 限制请求频率也可以 比如 1 分钟最多请求 10 次 那么验证码 30 分钟有效期可以尝试 300 次 另外难免会有公用出口 ip 的用户 |
9
aec4d 2016-05-11 20:19:17 +08:00
接上 限制 IP 也无解 大把的代理 IP 可用 如果不限制错误尝试 没什么安全性可言
|
10
greatonce OP @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 秒获取的验证码,而提交的时候是第二天了,计算不出验证码。 最后,也许你有更好的解决方案,请不吝赐教,如果以上内容有改进或任何问题,请帮助指出,谢谢。 |
11
aec4d 2016-05-12 11:04:22 +08:00
@greatonce
4 位数字是一万次 说错了 "所以需要限制试错次数,这个不需要我再说了,大家都知道怎么做。"(这才是需要讨论的最重要的步骤) 按照你的意思,假如每隔 1 分钟算一次 md5(mobile+timestamp)。 hashSet 有 3 个。那么一小时会产生 180 个 code 。那么这 180 个 code 都是有效的。无形中让猜的概率大了 180 倍 和我写的思路并没有区别,你最后还是需要记录手机号的验证码次数 |
12
greatonce OP @aec4d
你都没仔细看我的帖子吗? 哪里会有 180 个验证码? 在你没有加入讨论之前,我就说了肯定需要限制试错次数的,不限制试错次数,那验证码就没有什么意义。 至于试错次数的解决方案,你认为是重要步骤,其实只要仔细看帖子就知道解决方案了, 简单的存储键值对就可以了 phone=>times or email=>times 就解决了,这也会是重点? 我帖子里面说的是验证码的生成思路不需要存储,每次都是根据算法 只希望大家的讨论都有建设性,不想辩论基础问题。 |
13
aec4d 2016-05-12 14:30:00 +08:00
@greatonce 你的标题着重写非存储,
我的第一个回复是:"非存储是不可能的 因为要后台要记录错误次数", 你回复:“为什么还需要 cookie 存储” 我回复:"cookies 不需要存储,存储的是错误记录"(手机号对应错误尝试次数,这是唯一需要记录的地方) ======================= 可能你说的非存储只是生成的 md5(hash, phone or email, timeItem)不需要存储,我理解的非存储是整个验证过程中的不需要存储 ======================= 对于你后来的算法,是我理解错误了 对于你说的算法。在第一个回复第三行已经给出 over |