Math.random() を実行するだけで済むのに、なぜ OTP ライブラリを使用するのでしょうか。

王林
リリース: 2024-08-17 20:30:32
オリジナル
425 人が閲覧しました

Why do we use OTP libraries when we can just do Math.random()

ワンタイム パスワード (OTP) は、さまざまなアプリケーションやサービスで認証と検証の目的で広く使用されています。通常、サーバーがそれらを生成し、SMS、電子メール、またはその他のチャネル経由でユーザーに送信します。次に、ユーザーは OTP を入力して身元を確認するか、アクションを実行します。

Node JS で OTP ベースの検証を実装する必要があるタスクを受け取りました。このようなものを統合する前に、私たちのほとんどの開発者/エンジニアは、インターネットでベスト プラクティス、チュートリアル、最近の技術トレンド、他の主要なソフトウェア システムが実装中に直面する問題などを調べていると思います。そこで私はそれを実行しましたが、最も注目を集めたのは、OTP を生成することだけが機能する otp-lib や otp-generator のようなライブラリでした。 SMS や電子メールでの送信などの残りのタスクは、引き続き他の手段で行う必要があります。このようなライブラリが存在することを知った後で頭に浮かぶ最初の疑問は、ワンライナーを書くだけで済むのに、なぜわざわざライブラリを使用して OTP を生成する必要があるのか​​ということです。

const otp = Math.ceil(Math.random() * 10000)
ログイン後にコピー

このブログ投稿では、OTP ジェネレーターの小規模な研究中に学んだこと、OTP を生成するために Math.random() を使用するのがなぜ悪い考えなのか、OTP を生成する他の方法は何か、ライブラリを使用する理由について説明します。このようなタスクに使用する必要がありますか?

乱数の種類

乱数には主に 2 種類があります:

  • 擬似乱数 (PRN)
  • 暗号化乱数 (CRN)。

擬似乱数

擬似乱数は、シードと呼ばれる初期値を取得し、ランダムに見える一連の数値を生成するアルゴリズムによって生成されます。ただし、アルゴリズムは決定論的です。つまり、シードとアルゴリズムがわかっていれば、シーケンス内の次の数値を予測できます。 Javascript の Math.random() と Python のrandom.randInt() は、擬似乱数生成器の例です。

暗号乱数

暗号乱数は、予測不可能なプロセスによって生成され、再現したり推測したりすることはできません。これらは通常、大気騒音、熱雑音、量子効果などの物理現象に基づいています。

Math.random() はどのように機能するのでしょうか?

Javascript エンジンが異なると、乱数を生成する際の動作が少し異なりますが、基本的にはすべて単一のアルゴリズム XorShift128+ によって決まります。

XorShift は決定論的アルゴリズムであり、高速な非線形変換ソリューションとして加算を使用します。乗算を使用する他のアルゴリズムと比較して、このアルゴリズムは高速です。また、メルセンヌ ツイスター (Python のランダム モジュールで使用) よりも失敗する可能性が低くなります

アルゴリズムは 2 つの状態変数を受け取り、XOR とシフトを適用して、更新された状態変数の合計を整数で返します。通常、状態はシステム クロックを使用してシードされます。これは、システム クロックが一意の番号の優れたソースであるためです。

JavaScript での XOR シフト プラスの実装は次のようになります:

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 演算を使用して double に変換されます。詳細な実装は、Chrome ソース コードで確認できます。

Math.random() で生成された乱数を予測する方法

Math.random() の結果を予測するのは困難ですが、完全に不可能というわけではありません。アルゴリズムを理解していれば、state0 と state1 の値がわかっていれば、同じ乱数を簡単に再生成できます。

XorShift128+ のリバース エンジニアリング Z3 定理証明器を使用すると、サーバーによって生成された 3 つの連続する乱数を提供することで、state0 と state1 の値を見つけることができます。

Z3 ソルバーの実装はここにあります。

ここで問題は、これら 3 つの乱数をサーバーから取得する方法です。それが難しい部分ですが、次のような場合に取得できます:

  1. If an API returns a randomly generated number in its response or headers, it can easily be obtained by sending requests at set intervals.
  2. API documentation like OpenAPI/Swagger in modern applications is generated on the server. Sometimes their responses can contain an example value that uses a random number.
  3. With frameworks like NextJS that use server-side rendering while also being capable of handling backend API integrations, there are high chances of getting randomly generated numbers from the content served by them.

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.

Generating a Cryptographic Random Number in NodeJS

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:

  1. Storing OTPs in the database creates a lot of garbage data that has to be cleaned up periodically. OTP means a one-time password that can expire after a single use. It can also expire if not used for a specific duration or a new OTP is requested without using the previous one. This mainly adds unnecessary overhead to the database operations for maintaining valid OTPs while also consuming storage space.
  2. Storing OTPs in the database poses a security risk if the database is compromised. An attacker who gains access to the database can read the OTPs and use them to bypass the authentication or verification process. This can lead to account takeover, identity theft, or fraud.
  3. Storing OTPs in the database makes them vulnerable to replay attacks. A replay attack is when an attacker intercepts an incoming valid OTP and uses it again before it expires. This can allow the attacker to perform unauthorised actions or access sensitive information.

What do the OTP libraries do differently?

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

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 は基本的に、コンピュータまたは電話に物理的に接続できるプログラムされたハードウェア キーです。 SMS や電子メールからコードを受け取る代わりに、Yubikey のボタンを押すだけで本人確認と認証を行うことができます。

HOTP の利点は次のとおりです。

• OTP はその場で生成および検証できるため、データベースに保存する必要はありません。
• 予測不可能かつ不可逆的な暗号化ハッシュ関数を使用するため、疑似乱数に依存しません。
• 各 OTP は 1 回だけ有効であるため、リプレイ攻撃に耐性があります。

HOTP の欠点は次のとおりです。

• サーバーとユーザーのカウンター間の同期が必要です。ネットワークの遅延、送信エラー、デバイスの紛失などにより同期が取れていない場合、検証は失敗します。
• 新たに生成された HOTP が使用されない限り有効ですが、脆弱性となる可能性があります。
• 秘密鍵を配布および保管するには安全な方法が必要です。秘密キーが漏洩または盗難された場合、OTP が侵害される可能性があります。

TOTP

TOTP は、タイムベースのワンタイム パスワードの略です。これは、秘密キー、タイムスタンプ、エポックに基づいて OTP を生成するアルゴリズムです。

  • 秘密キーは、サーバーとユーザーの間で共有されるランダムな文字列です。 SHA1( "secretvalue" + user_id ) を生成することで、ユーザーごとに一意に作成できます。
  • タイムスタンプは、現在時刻を秒単位で表す整数です
  • エポックとは、アルゴリズムが同じ結果を生成する期間です。一般に、30 秒から 1 分の間に保持されます。

アルゴリズムは次のように機能します:
• サーバーはユーザーの秘密キーを決定し、Authenticator アプリなどの媒体を介してそれを共有します。
• サーバーは OTP を直接生成し、メールまたは SMS でユーザーに送信することも、オーセンティケーターを使用して共有キーを使用して OTP を生成するようにユーザーに要求することもできます。
• ユーザーは、メールまたは SMS で受信した OTP を直接送信することも、2FA の場合は一定の時間枠で認証アプリで OTP を生成することもできます。
• サーバーは OTP を独自に生成した OTP と比較し、それらがエポック時間範囲内で十分に近いかどうかを検証します。

TOTP の利点は次のとおりです。

• OTP はその場で生成および検証できるため、データベースに保存する必要はありません。
• 予測不可能かつ不可逆的な暗号化ハッシュ関数を使用するため、疑似乱数に依存しません。
• 各 OTP は短期間のみ有効であるため、リプレイ攻撃に対して耐性があります。
• サーバーとユーザーのタイムスタンプ間の同期は必要ありません。適度に正確なクロックを持っている限り、OTP を独立して生成および検証できます。

TOTP の欠点は次のとおりです。

• 秘密鍵を配布および保存するには安全な方法が必要です。秘密キーが漏洩または盗難された場合、OTP が侵害される可能性があります。
• サーバーとユーザーの両方にとって信頼できる時間源が必要です。時計が歪んでいたり改ざんされている場合、検証は失敗します。
• サーバーはリクエストの処理における時間のずれや遅延を考慮する必要があるため、クライアントよりもわずかに長いエポックを維持する必要があります。

結論

OTP に関するちょっとした調査を通じて、Math.random() が予測、悪用、再生できることを知りました。また、OTP をデータベースに保存するのは良い習慣ではないこともわかりました。

TOTP は安全で効率的な OTP を生成し、検証することもできます。オンラインだけでなくオフラインでも OTP を生成でき、同期やストレージを必要とせず、リプレイ攻撃に耐性があります。したがって、ベスト プラクティス、セキュリティ、信頼性に関する懸念のほとんどが解決されます。

以上がMath.random() を実行するだけで済むのに、なぜ OTP ライブラリを使用するのでしょうか。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート