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

2016-05-05 20:26:39 +08:00
 greatonce

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

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

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

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

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

哪里会有 180 个验证码?

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

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

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

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

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

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

另外存储到 cookie 并不是理想有效的方法,因为 cookie 可以随时被清除

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/276616

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX