【问题标题】:what is the best way to generate a reset token in python?在 python 中生成重置令牌的最佳方法是什么?
【发布时间】:2013-01-20 17:56:09
【问题描述】:

我正在尝试为密码重置进行验证过程,我使用的是两个值:纪元时间,并且我想使用用户的旧密码 (pbkdf2) 作为密钥,

因为我不想获取非 ASCII 字符,所以我使用了SimpleEncode library,因为它速度很快,因为它只是一个带有密钥的 BASE64,但问题是密码太长(196 个字符)所以我得到长键!

我所做的是拆分结果code = simpleencode.encode(key,asci)[::30],但这不会是唯一的!

为了了解它的工作原理,我尝试了 Facebook 重置过程,但给出的是一个数字!那么这个过程是如何运作的,他们不是使用密钥来让某人难以伪造链接来重置某人的密码吗?

更新:算法将如何工作:

1- 使用 epoche time.time()获取时间

2-生成纪元时间的Base64(用于URL)和纪元时间值+一个密钥,这个密钥是PBKDF2(密码)。

3- 生成 URL www.example.com/reset/user/Base64(time.time()) 并发送此 URL + simpleencode.encode(key,asci)[::30]

4-当用户点击URL时,他把生成的代码,这个生成的代码,如果和URL匹配,那么让他修改密码,否则就是忘记URL!

【问题讨论】:

  • 如果您确认它还没有在数据库中并且它足够长以至于没有人会猜到它,它不需要是唯一的。
  • 您应该以任何形式将用户密码公开(哈希)作为重置令牌的一部分。这将允许攻击者学习哈希,然后他们可以尝试暴力破解。
  • 您正在暴露 PBKDF2(密码)。如果“密码”很弱,这可以通过暴力破解来逆转,如果它实际上是用户的密码,则很可能是这样。
  • @AbdelouahabPp 这仍然是一个安全漏洞,只有在被利用之前,安全漏洞才不太可能被利用。只是在可以避免的时候不要介绍它们。您始终可以使用不同于用于存储密码的盐来安全地散列密码。
  • @AbdelouahabPp 另外,30 个字符的 base64 随机数据(即哈希)绝对“足够独特”。

标签: python registration password-recovery


【解决方案1】:

不确定这是不是最好的方法,但我可能只是生成一个 UUID4,它可以在 URL 中用于重置密码并在“n”时间后过期。

>>> import uuid
>>> uuid.uuid4().hex
'8c05904f0051419283d1024fc5ce1a59'

您可以使用 http://redis.io 之类的东西来保存该密钥,并使用相应用户 ID 的值并设置其生存时间。因此,当来自http://example.com/password-reset/8c05904f0051419283d1024fc5ce1a59 的内容进入时,它会查看它是否有效,如果有效,则允许更改以设置新密码。

如果您确实想要一个“验证密码”,则与令牌一起存储一个小的随机密钥,例如:

>>> from string import digits
>>> from random import choice
>>> ''.join(choice(digits) for i in xrange(4))
'2545'

并要求在重置链接上输入。

【讨论】:

  • 感谢您的提示,它是 url 安全的,但是,我使用的方法怎么样,更新了问题
  • @AbdelouahabPp 我个人认为您的方法使问题过于复杂 - 没有理由使用有关用户的任何信息来生成令牌...
  • 所以如果我使用UUID方法,我必须生成用户必须输入才能进入的代码?
  • 验证网址没有被遗忘
  • @AbdelouahabPp 如果您的意思是“伪造” - 生成已发行代币的可能性非常小(340,282,366,920,938,463,463,374,607,431,768,211,456 分之一的机会)(更不用说在它仍然有效的时候)。添加了一个简单的 PIN 示例,如果这让您感觉更舒服...
【解决方案2】:

目前最简单的方法是使用ItsDangerous 库:

您可以对用户 ID 进行序列化和签名,以将新闻通讯退订到 URL。这样您就不需要生成一次性令牌并将它们存储在数据库中。任何类型的帐户激活链接和类似的东西都是一样的。

您还可以嵌入时间戳,从而非常轻松地设置时间段,而无需涉及数据库或队列。它全部经过加密签名,因此您可以轻松查看它是否被篡改。

>>> from itsdangerous import TimestampSigner
>>> s = TimestampSigner('secret-key')
>>> string = s.sign('foo')
>>> s.unsign(string, max_age=5)
Traceback (most recent call last):
  ...
itsdangerous.SignatureExpired: Signature age 15 > 5 seconds

【讨论】:

【解决方案3】:

为什么不直接使用jwt作为token,也可以给它设置一个过期时间,所以也可以给token设置一个过期日期。

  1. 生成使用密钥加密的令牌 (JWT)
  2. 发送包含带有令牌的链接作为查询参数的邮件(当用户打开链接时,页面可以读取令牌)
  3. 在保存新密码之前验证令牌

为了生成 jwt 令牌,我使用 pyjwt。下面的代码 sn-p 显示了如何在 24 小时(1 天)的到期时间下完成并使用密钥签名:

import jwt
from datetime import datetime, timedelta, timezone

secret = "jwt_secret"
payload = {"exp": datetime.now(timezone.utc) + timedelta(days=1), "id": user_id}
token = jwt.encode(payload, secret, algorithm="HS256")
reset_token = token.decode("utf-8")

下面sn-p展示了在django中如何设置token的验证和新密码。如果令牌已过期或被篡改,则会引发异常。

secret = "jwt_secret"
claims = jwt.decode(token, secret, options={"require_exp": True})
# Check if the user exists
user = User.objects.get(id=claims.get("id"))
user.set_password(password)
user.save()

【讨论】:

  • 感谢您的额外回答。如果用户 id(jwt 内部)由客户端提供(第 2 步,验证并存储新密码),是否有任何缺点?
  • 应该没问题,因为您的后端代码将发送带有重置链接的电子邮件,即使恶意用户输入了其他人的 id,也只有正确的用户才能访问他的电子邮件收件箱,并且只有他将能够点击重置链接
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-29
  • 2016-12-19
  • 1970-01-01
  • 2011-08-26
  • 1970-01-01
  • 2010-09-30
相关资源
最近更新 更多