一次性密碼 (OTP) 廣泛用於各種應用程式和服務中的身份驗證和驗證目的。伺服器通常會產生它們並透過簡訊、電子郵件或其他管道將它們發送給使用者。然後,使用者輸入 OTP 來確認其身分或執行操作。
我接到一個任務,必須在 Node JS 中實作基於 OTP 的驗證。在整合這樣的東西之前,我確信我們大多數開發人員/工程師都會在互聯網上尋找最佳實踐、教程、最新技術趨勢以及其他主要軟體系統在實現過程中面臨的問題。於是我就這麼做了,最吸引我注意的是像 otp-lib 和 otp-generator 這樣的函式庫,它們唯一的功能就是產生 OTP。其餘任務(例如透過簡訊或電子郵件發送)仍然需要透過其他方式完成。在知道這類庫存後,我想到的第一個問題是,為什麼我們必須花這麼大的篇幅來使用函式庫來產生 OTP,而我們所要做的只是寫一行程式碼:
const otp = Math.ceil(Math.random() * 10000)
在這篇文章中,我將解釋我在OTP 產生器的小型研究中學到的東西,為什麼使用Math.random() 產生OTP 是一個壞主意,產生OTP 的其他方法是什麼,以及為什麼需要一個庫應該用於這樣的任務嗎?
隨機數主要有兩種:
偽隨機數是由一種演算法產生的,該演算法採用稱為種子的初始值,並產生看似隨機的數字序列。但是,該演算法是確定性的,這意味著如果您知道種子和演算法,則可以預測序列中的下一個數字。 Javascript 的 Math.random() 和 Python 的 random.randInt() 是偽隨機數產生器的範例。
加密隨機數是由不可預測且無法複製或猜測的過程產生的。它們通常基於一些物理現象,例如大氣噪聲、熱噪聲或量子效應。
不同的 Javascript 引擎在產生隨機數時的行為略有不同,但這一切本質上都歸結為單一演算法 XorShift128+。
XorShift 是一種確定性演算法,它使用加法作為更快的非線性變換解決方案。與使用乘法的同類演算法相比,此演算法速度更快。它比 Mersenne Twister(Python 的 random 模組使用)失敗的可能性更小
演算法接受兩個狀態變量,對它們應用一些 XOR 和移位,並傳回更新後的狀態變數的總和,這是一個整數。這些狀態通常使用系統時鐘進行播種,因為這是唯一數字的良好來源。
異或移位加上 JavaScript 的實作如下圖:
let state0 = 1; let state1 = 2; function xorShiftPlus() { let s1 = state0; let s0 = state1; state0 = s0; s1 ^= s1 << 23; s1 ^= s1 >> 17; s1 ^= s0; s1 ^= s0 >> 26; state1 = s1; return state0 + state1; }
使用常數的 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 並透過郵件或 SMS 傳送給用戶,也可以要求使用者使用身份驗證器使用共用金鑰產生 OTP。
• 使用者可以直接發送透過郵件或簡訊收到的 OTP,也可以在驗證器應用程式中產生它,以防在固定時間視窗內進行 2FA。
• 伺服器將 OTP 與自己產生的 OTP 進行比較,並驗證它們在紀元時間範圍內是否足夠接近。
• 它不需要將 OTP 儲存在資料庫中,因為它可以即時產生和驗證。
• 它不依賴偽隨機數,因為它使用不可預測且不可逆的加密雜湊函數。
• 它可以抵抗重播攻擊,因為每個 OTP 僅在短時間內有效。
• 它不需要伺服器和使用者時間戳記之間的同步。只要他們有相當準確的時鐘,他們就可以獨立產生和驗證 OTP。
• 它需要一種安全的方式來分發和儲存金鑰。如果金鑰洩漏或被盜,OTP 可能會被洩露。
• 它需要伺服器和使用者都有可靠的時間來源。如果他們的時鐘有偏差或被竄改,驗證就會失敗。
• 伺服器必須考慮處理請求時的時間漂移或延遲,因此它應該保持比客戶端稍大的紀元。
透過我們對 OTP 的一點研究之旅,我們了解到 Math.random() 是可以預測、利用和重播的。我們也了解到,將 OTP 儲存在資料庫中並不是一個好的做法。
TOTP可以產生安全且高效的OTP,並且還可以驗證它們。它可以離線和線上產生 OTP,不需要同步或存儲,並且可以抵抗重播攻擊。因此,它解決了我們與最佳實踐、安全性和可靠性相關的大部分問題。
以上是當我們可以只執行 Math.random() 時,為什麼還要使用 OTP 函式庫的詳細內容。更多資訊請關注PHP中文網其他相關文章!