一次性密码(OTP)广泛用于各种应用程序和服务中的身份验证和验证目的。服务器通常生成它们并通过短信、电子邮件或其他渠道将它们发送给用户。然后,用户输入 OTP 来确认其身份或执行操作。
我接到一个任务,必须在 Node JS 中实现基于 OTP 的验证。在集成这样的东西之前,我确信我们大多数开发人员/工程师都会在互联网上查找最佳实践、教程、最新技术趋势以及其他主要软件系统在实现过程中面临的问题。于是我就这么做了,最吸引我注意的是像 otp-lib 和 otp-generator 这样的库,它们唯一的功能就是生成 OTP。其余任务(例如通过短信或电子邮件发送)仍然需要通过其他方式完成。知道此类库存在后,我想到的第一个问题是,为什么我们必须花这么长的时间来使用库来生成 OTP,而我们所要做的只是编写一行代码:
在这篇博文中,我将解释我在 OTP 生成器的小型研究中学到的东西,为什么使用 Math.random() 生成 OTP 是一个坏主意,生成 OTP 的其他方法是什么,以及为什么应该使用库用于这样的任务?
随机数主要有两种:
伪随机数是由一种算法生成的,该算法采用称为种子的初始值,并生成看似随机的数字序列。但是,该算法是确定性的,这意味着如果您知道种子和算法,则可以预测序列中的下一个数字。 Javascript 的 Math.random() 和 Python 的 random.randInt() 是伪随机数生成器的示例。
加密随机数是由不可预测且无法复制或猜测的过程生成的。它们通常基于一些物理现象,例如大气噪声、热噪声或量子效应。
不同的 Javascript 引擎在生成随机数时的行为略有不同,但这一切本质上都归结为单一算法 XorShift128+。
XorShift 是一种确定性算法,它使用加法作为更快的非线性变换解决方案。与使用乘法的同类算法相比,该算法速度更快。它比 Mersenne Twister(Python 的 random 模块使用)失败的可能性更小
该算法接受两个状态变量,对它们应用一些异或和移位,并返回更新后的状态变量的总和,这是一个整数。这些状态通常使用系统时钟进行播种,因为这是唯一数字的良好来源。
异或移位加在 JavaScript 中的实现如下所示:
使用常量的 OR 运算将返回的整数转换为双精度型。详细实现可以在chrome源码中找到。
预测 Math.random() 的结果很困难,但是也不是完全不可能。了解了算法,如果知道 state0 和 state1 的值,就可以轻松地重新生成相同的随机数。
逆向工程 XorShift128+ 使用 Z3 定理证明器,您可以通过提供服务器生成的 3 个连续随机数来找到 state0 和 state1 的值。
Z3 求解器的实现可以在这里找到。
现在的问题是如何从服务器获取这 3 个随机数。这是最难的部分,可以在以下某些情况下获得:
Another approach to exploit a random number is using the fact that Math.random() only returns numbers between 0 and 1 with 16 decimal places. This means that there are only 10^16 possible values that Math.random() can return. This is a very small space compared to the space of possible OTPs. if your OTP has 6 digits, there are 10^6 possible values. This visualizer shows that there is a pattern to the numbers generated. Using it, the possibilities can be reduced by 30%. Therefore, if you can guess or brute-force some of the digits of the OTP, you can reduce the space of possible values and increase your chances of finding the correct OTP.
As mentioned previously, cryptographic random numbers are non-deterministic because they depend on the physical factors of a system. Every programming language can access those factors using low-level OS kernel calls.
NodeJS provides its inbuilt crypto module, which we can use to generate randomBytes and then convert them to a number. These random bytes are cryptographic and purely random in nature. The generated number can easily be truncated to the exact number of digits we want in OTP.
import * as crypto from 'crypto'; const num = parseInt(crypto.randomBytes(3).toString('hex'), 16) // num.toString().slice(0,4) // truncate to 4 digits
NodeJS 14.10+ provides another function from crypto to generate a random number in a given min-max range.
crypto.randomInt(1001, 9999)
Even after knowing the vulnerability of Math.random() and finding a more secure way to generate a random number cryptographically, we still remain with the same question from the beginning. Why do we have to go to such lengths to use a library to generate OTP when all we have to do is write a one-liner?
Before answering this questions, let's take a look at what is the inconvenience faced while handling and storing an OTP. The problem with using the above method to generate OTPs is that you have to store them in the database in order to verify them later. Storing the OTP in the database is not a good practice for the following reasons:
The OTP libraries use different algorithms and techniques to generate and verify OTPs that behave similarly to a Cryptographic random OTP, while also removing the overhead to store the OTP in a database.
There are mainly two types of OTP implementation techniques.
HOTP stands for HMAC-based One-Time Password. It is an algorithm that generates an OTP based on a secret key and a counter. The secret key is a random string that is shared between the server and the user. The counter is an integer that increments every time an OTP is generated or verified.
The algorithm works as follows:
• The server and the user generate the same OTP by applying a cryptographic hash function, such as SHA-1, to the concatenation of the secret key and the counter.
• The server and the user truncate the hash value to obtain a fixed-length OTP, usually 6 or 8 digits.
• The user sends the OTP to the server for verification.
• The server compares the OTP with its own generated OTP and verifies it if they match.
• The server and the user increment their counters by one.
HOTP 主要用于基于硬件令牌的身份验证,例如 Yubikey。 Yubikey 基本上是一个编程的硬件密钥,您可以将其物理连接到计算机或手机。您无需通过短信或电子邮件接收代码,只需按 Yubikey 上的按钮即可验证和验证自己的身份。
• 它不需要将 OTP 存储在数据库中,因为它可以即时生成和验证。
• 它不依赖于伪随机数,因为它使用不可预测且不可逆的加密哈希函数。
• 它可以抵抗重放攻击,因为每个 OTP 只有效一次。
• 它需要服务器和用户计数器之间的同步。如果由于网络延迟、传输错误或设备丢失而导致验证失败。
• 只要不使用新生成的 HOTP,它就仍然有效,这可能是一个漏洞。
• 需要一种安全的方式来分发和存储密钥。如果密钥泄露或被盗,OTP 可能会被泄露。
TOTP 代表基于时间的一次性密码。它是一种基于密钥、时间戳和纪元生成 OTP 的算法。
算法的工作原理如下:
• 服务器为用户决定密钥并通过身份验证器应用程序等介质共享。
• 服务器可以直接生成 OTP 并通过邮件或短信发送给用户,也可以要求用户使用身份验证器使用共享密钥生成 OTP。
• 用户可以直接发送通过邮件或短信收到的 OTP,也可以在身份验证器应用程序中生成它,以防在固定时间窗口内进行 2FA。
• 服务器将 OTP 与自己生成的 OTP 进行比较,并验证它们在纪元时间范围内是否足够接近。
• 它不需要将 OTP 存储在数据库中,因为它可以即时生成和验证。
• 它不依赖于伪随机数,因为它使用不可预测且不可逆的加密哈希函数。
• 它可以抵抗重放攻击,因为每个 OTP 仅在短时间内有效。
• 它不需要服务器和用户时间戳之间的同步。只要他们有相当准确的时钟,他们就可以独立生成和验证 OTP。
• 它需要一种安全的方式来分发和存储密钥。如果密钥泄露或被盗,OTP 可能会被泄露。
• 它需要服务器和用户都有可靠的时间来源。如果他们的时钟有偏差或被篡改,验证就会失败。
• 服务器必须考虑处理请求时的时间漂移或延迟,因此它应该保持比客户端稍大的纪元。
通过我们对 OTP 的一点研究之旅,我们了解到 Math.random() 是可以预测、利用和重放的。我们还了解到,将 OTP 存储在数据库中并不是一个好的做法。
TOTP可以生成安全高效的OTP,并且还可以验证它们。它可以离线和在线生成 OTP,不需要同步或存储,并且可以抵抗重放攻击。因此,它解决了我们与最佳实践、安全性和可靠性相关的大部分问题。
以上是当我们可以只执行 Math.random() 时,为什么还要使用 OTP 库的详细内容。更多信息请关注PHP中文网其他相关文章!