UUID.randomUUID()适合用作一次性密码吗?

34
作为之前讨论的一部分,确认电子邮件应该有一个独特的、(实际上)难以猜测的代码——基本上是一次性密码——在确认链接中。 UUID.randomUUID()文档说:

使用加密强度很高的伪随机数生成器生成UUID。

这是否意味着在正确实现的JVM中,UUID随机生成器适用于用作唯一的、(实际上)难以猜测的OTP?

2
你可能会对我在另一个问题中的回答感兴趣,如果这很重要的话,它将为您提供更多安全性,使用更少的数字... - erickson
2
还可以在这里查看讨论:https://security.stackexchange.com/questions/890/are-guids-safe-for-one-time-tokens - steve cook
8个回答

22

如果您阅读了定义UUID的RFC(从API文档链接过去),就会发现UUID的并非所有位都是随机的(“变体”和“版本”不是随机的)。因此,如果正确实现,类型4 UUID(您打算使用的类型)应具有128位总大小中122位(对于此实现来说是安全的)随机信息。

因此,是的,它将像“安全”发生器生成的122位随机数一样工作。但是,较短的值可能包含足够数量的随机性,并且可能更容易供用户使用(也许我是仅有的在终端上读取电子邮件的守旧人,但是跨行包装的确认URL令人讨厌…)。


其他人提供了类似的答案,但这似乎是最完整和信息丰富的。谢谢。 - cqcallaw
13
实际上,RFC明确警告不要将UUID作为安全令牌使用:“不要假设UUID难以猜测;例如,它们不应该被用作安全权限(仅拥有其标识符即可获得访问权限)。 ”使用安全RNG生成的UUID4几乎不可能被猜测,但标准明确允许使用不安全的RNG。 http://tools.ietf.org/html/rfc4122#section-6 (抱歉重新唤起一个非常古老的评论,但安全是每个人都关心的问题。) - Mike McCoy
实际上,这里的重点并不在于UUID标准所说的内容,而在于Java UUID实现在实践中有多安全。那么,用于UUID的Java RNG有多安全? - Salvioner

19

不可以。根据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库中。


18
UUID规范与Java实现不同。虽然规范并不要求UUID必须是安全的,但Java实现确实提供了122位的安全随机性。另请参见https://dev59.com/4Wsz5IYBdhLWcg3w-85v#7532965。 - Dan
4
我会推荐明确地使用加密库。 - jchook
2
当涉及到安全问题时,小心谨慎、明确无误是最好的选择。对于军用级别的系统,您不会使用UUID.randomUUID()。但如果问题是“我是否通过使用UUID.randomUUID()来生成令牌引入了安全漏洞?”,那么答案是否定的。是的,绝对没问题;-) - Dan
1
这个回答现在只是错误信息。虽然说“UUID通常不应该被信任作为OTP”,但是毫无根据地声称Java的UUID不能被信任是事实上不正确的。此外,如果没有具体的替代OTP生成器的建议,这个回答就没有什么帮助了。 - Tim Pote
3
感谢您的反馈。已进行编辑以包含解决方案。 - jchook

11

是的,使用java.util.UUID是可以的,其中的randomUUID方法是从加密安全源生成的。没有更多需要说明的了。

这是我的建议:

  1. 向用户发送一个巨大密码的链接作为URL参数。
  2. 当用户点击链接时,编写后端以确定参数是否正确且用户已登录。
  3. 在发出24小时后使UUID无效。

这需要一些工作,但如果您真正关心编写一个强大、安全的系统,这是必要的。


1
您的建议正是我的意图,只不过我打算在访问确认URL并完成确认后立即使UUID无效。 - cqcallaw
2
@cqcallaw 如果用户从未访问该URL怎么办?我总是在第一次使用后或几个小时后使这些密钥失效,以先到者为准。 - Lukas Liesis

1
密码强度可以根据所需的熵(熵越高越好)进行量化。
对于二进制计算机,
熵 = 密码长度 * log2(符号空间)
符号空间是可供选择的唯一符号(字符)的总数
对于一个使用qwerty键盘的普通英语用户,
1. 符号从52个字符(26 * 2表示大小写) + 10个数字 + 可能的15个其他字符 (*, + -, ...) 中选择,一般的符号空间约为75。 2. 如果我们期望最小密码长度为8:
熵 = 8 * log275 ~= 8 * 6.x ~= 50
为了生成仅包含十六进制字符(16个符号空间=0-9,a-f)的一次性自动生成密码并达到熵(50),
密码长度 = 50 / log216 = 50 / 4 ~= 12
如果应用程序可以放宽要求,考虑完整的英文字母和数字大小写敏感性,样本空间将为62(26 * 2 + 10)。
密码长度=50 / log262 = 50 / 6 ~= 8
这将减少用户输入字符数至8(16进制下为12)。
使用UUID.randomUUID()时,存在两个主要问题:
1.用户必须输入32个字符(不够用户友好)
2.实现必须确保唯一性标准(与库版本和语言依赖紧密耦合)
我知道这不是一个直接的答案,最终选择最佳策略取决于应用程序所有者考虑安全性和可用性约束。
个人而言,我不会将UUID.randomUUID()用作一次性密码。

-2

随机代码用于确认链接的目的是让攻击者无法猜测或预测其值。正如您所见,要找到正确的确认链接代码,128位长度的UUID会产生2^128种不同的可能代码,也就是说有340,282,366,920,938,463,463,374,607,431,768,211,456个可尝试的代码。我想您的确认链接并不是用来发射核武器的,对吧?这对攻击者来说已经足够难以猜测了。它是安全的。

-- 更新 --

如果您不信任提供的具有密码学强度的随机数生成器,您可以将一些更加不可预测的参数与UUID代码一起混合并进行哈希处理。例如:

code = SHA1(UUID, 进程PID, 线程ID, 本地连接端口号, CPU温度)

这样做将使其更加难以预测。


2
仅仅生成的比特数并不能使其不可预测。java.util.Random 可以用来获取 128 比特的数据,但这并不意味着它是安全的。 - Louis Wasserman
3
现今,人们为了赚钱而攻击,而你的预算有限。没有东西是不可摧毁的。那么它应该有多安全?安全的关键在于——破解它需要多少成本和可以获得多少利益?如果这是一个普通网站确认链接的代码,那么这就足够安全了。但如果这是核武器的代码,那我就不能这么说了。对我来说,安全的定义是——破解它的成本 >> 预期收益。当破解它的成本太高,而你能从中获得的收益很少时,那么它就是安全的。 - Fang-Pen Lin
有一个关于randomUUID的话题 https://dev59.com/FnE85IYBdhLWcg3w_I38 - Fang-Pen Lin
2
“具有密码学强度的随机数最少符合FIPS 140-2《密码模块安全要求》第4.9.1节中指定的统计随机数生成器测试。此外,SecureRandom必须产生非确定性输出。因此,传递给SecureRandom对象的任何种子材料都必须是不可预测的,并且所有SecureRandom输出序列都必须是密码学强的,如RFC 1750中所述:安全性的随机性建议。” - Fang-Pen Lin
是的,UUID似乎比java.util.Random更安全,但这个回答没有给出任何原因的提示。 - Louis Wasserman
显示剩余2条评论

-3

我认为这应该是合适的,因为它是随机生成的,而不是来自任何特定的输入(例如您没有用用户名之类的东西来喂它)- 因此对此代码的多次调用将产生不同的结果。它声明了一个128位密钥,因此足够长,以至于破解变得不切实际。

那么你接下来会使用这个密钥来加密一个值,还是期望将其作为实际密码使用?无论如何,您都需要重新解释密钥,以便以键盘可以输入的格式输入。例如,进行Base64或Hex转换,或以某种方式将值映射到字母数字,否则用户将尝试输入键盘上不存在的字节值。


我计划将确认代码嵌入到确认URL中,邮件接收者点击该URL即可完成确认,无需用户输入... - cqcallaw

-4

它非常适合作为一次性密码,因为我甚至已经在我正在工作的应用程序中实现了相同的功能。此外,您分享的链接已经说明了一切。


-4

我认为java.util.UUID应该是很好的选择。您可以从这个文章中找到更多信息:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接