如前所述,确认电子邮件应在确认链接中具有唯一的(实际上)不可猜测的代码——本质上是一次性密码。
UUID 是使用加密强的伪随机数生成器生成的。
这是否意味着正确实现的 JVM 中的 UUID 随机生成器适合用作唯一的(实际上)不可猜测的 OTP?
如果您阅读定义 UUID 的RFC,并从 API 文档链接到该 RFC,您会发现并非 UUID 的所有位实际上都是随机的(“变体”和“版本”不是随机的)。因此,如果正确实施,类型 4 UUID(您打算使用的那种)应该具有 122 位(对于此实施而言是安全的)随机信息,总大小为 128 位。
所以是的,它与来自“安全”生成器的 122 位随机数一样有效。 但较短的值可能包含足够的随机性,并且对用户来说可能更容易(也许我是唯一仍然在终端中阅读电子邮件的老式人,但是跨行的确认 URL 很烦人......) .
不,根据UUID 规范:
不要假设 UUID 很难猜;例如,它们不应该用作安全功能(仅拥有授予访问权限的标识符)。一个可预测的随机数源会加剧这种情况。
此外,UUID 只有 16 个可能的字符(0 到 F)。SecureRandom
您可以使用(感谢@erickson)生成更紧凑且更明确安全的随机密码。
import java.security.SecureRandom;
import java.math.BigInteger;
public final class PasswordGenerator {
private SecureRandom random = new SecureRandom();
public String nextPassword() {
return new BigInteger(130, random).toString(32);
}
}
附言
我想给出一个清楚的例子,说明使用 UUID 作为安全令牌可能会导致问题:
在uuid-random中,我们 通过巧妙的方式在内部重新使用随机字节发现了巨大的速度提升,从而产生了可预测的 UUID。尽管我们没有发布更改,但 RFC 允许这样做,并且此类优化可能会潜入您的 UUID 库而不引起注意。
是的,使用 ajava.util.UUID
很好,randomUUID
方法从加密安全源生成。没有太多需要说的了。
这是我的建议:
这将需要一些工作,但如果您真的关心编写一个健壮、安全的系统,这是必要的。
密码强度可以根据所需的熵进行量化(越高越好)。
对于二进制计算机,
熵 = 密码长度 * log 2(符号空间)
符号空间是可供选择的唯一符号(字符)总数
对于使用 qwerty 键盘的普通英语用户,
52
字符中选择的(两种情况下都是 26 * 2)+10
数字 + 可能还有15
其他字符,例如 (*, + -, ...),一般符号空间在75
.8
:熵 = 8 * log 2 75 ~= 8 * 6.x ~= 50
要为仅使用十六进制(符号空间 = 0-9,af)50
的自动生成的一次性密码实现熵( ),16
密码长度 = 50 / log 2 16 = 50 / 4 ~= 12
如果应用程序可以放宽考虑完整的区分大小写的英文字母和数字,样本空间将是62
(26 * 2 + 10),
密码长度 = 50 / log 2 62 = 50 / 6 ~= 8
这将用户输入的字符数减少到 8 个(从 12 个十六进制)。
使用 UUID.randomUUID(),两个主要问题是
我知道这不是一个直接的答案,考虑到安全性和可用性限制,它真的取决于应用程序所有者来选择最佳策略。
就个人而言,我不会使用 UUID.randomUUID() 作为一次性密码。
确认链接的随机代码的要点是攻击者不应该能够猜测或预测该值。如您所见,要找到确认链接的正确代码,128 位长度的 UUID 会产生 2^128 个不同的可能代码,即 340,282,366,920,938,463,463,374,607,431,768,211,456 个可能的代码尝试。我想你的确认链接不是用来发射核武器的,对吧?这对于攻击者来说很难猜到。它是安全的。
- 更新 -
如果您不信任提供的加密强随机数生成器,您可以将一些更不可预测的参数与 UUID 代码一起放入并散列它们。例如,
code = SHA1(UUID、进程PID、线程ID、本地连接端口号、CPU温度)
这使得预测变得更加困难。
我认为这应该是合适的,因为它是随机生成的,而不是从任何特定输入中生成的(即你没有用用户名或类似的东西来提供它)——所以多次调用这段代码会产生不同的结果。它声明它是一个 128 位密钥,因此它的长度足以破解是不切实际的。
然后您打算使用此密钥来加密一个值,还是希望将其用作实际密码?无论如何,您都需要将密钥重新解释为可以通过键盘输入的格式。例如,进行 Base64 或 Hex 转换,或以某种方式将值映射到字母数字,否则用户将尝试输入键盘上不存在的字节值。
它非常适合作为一次性密码,因为即使我已经为正在工作的应用程序实现了相同的密码。此外,您共享的链接说明了一切。
我认为 java.util.UUID 应该没问题。您可以从这篇文章中找到更多信息: