Menu
CoddyTech

パスワードは絶対に保存しない

CoddyのNode.jsジャーニー「認証とセキュリティ」セクションの一部。レッスン 4/24。

パスワードは使い回されるため、平文のパスワードが漏えいすると、ほかのサービスのアカウントも危険にさらされる可能性があります。ソルト付きのパスワードハッシュを保存しましょう。これは、復号するのではなく、再計算して検証する派生値です。

const salt = randomBytes(16).toString('hex');
const key = await scryptAsync(inviteCode, salt, 32);
const saved = salt + '.' + key.toString('hex');

この例では、招待コードに対してソルトと派生キーを組み合わせています。区切り文字を使うことで、検証時にソルトを取り出し、同じパラメーターで別のキーを導出できます。パスワードの課題では、独自の区切り文字とキーの長さを使います。新しいパスワードごとにソルトを生成し、キーを導出してから、その両方を保存しましょう。攻撃者は推測を試せるため、ソルト付きハッシュを使う場合でも、強力なパスワードが必要です。

challenge icon

チャレンジ

簡単

現在、signup は入力されたパスワードをそのまま保存します。漏えいしたデータベースからパスワードが知られないように、代わりにソルト付きハッシュを保存するようにしましょう。

  1. passwords.js で、hashPassword(password) を変更し、signup が保存するテキストをソルト、コロン、ハッシュの形式で返すようにします。
    • ソルトは16個のランダムなバイトで、呼び出しごとに新しく生成し、16進数で書き出します。0から9とaからfまでの文字が32個並びます。
    • ハッシュは、scrypt がパスワードとソルトから生成する64バイトのキーで、こちらも16進数で書き出します。文字数は128文字です。scryptAsync の第3引数は、キーの長さをバイト数で指定します。
    • 2つをコロンでつなぎます。保存される値は次のようになります。f2bba0669d9fa2d203afc7e61bf8f065:c3f7cae352e023a8d4766da9a316b5a2c844100efff22c4079f89a8ab8512ac91c33227481c3afa88c3ba0e88347f9e3e0c22dad9d55f19688a350f71be8dbd2
  2. server.js を変更する必要はありません。POST /signup はすでに hashPassword を呼び出し、その戻り値を保存します。また、ユーザー1人分の保存内容を表示する GET /debug/users/:username もあります。このルートは内容を確認できるように用意されているだけです。実際のアプリにこのルートが存在することはありません。

試してみましょう:「実行」を押します。POST /signup に {"username": "ada", "password": "correct horse"} を送信します。ステータス201と {"username": "ada"} が返されます。同じ本文で "username": "bob" を送信します。次に GET /debug/users/ada、続いて GET /debug/users/bob を送信します。それぞれの passwordHash は、32文字、コロン、さらに128文字で構成されています。adaとbobが同じパスワードを選んだにもかかわらず、2つの値は異なります。

自分で試してみよう

import { randomBytes, scrypt } from 'node:crypto';
import { promisify } from 'node:util';

const scryptAsync = promisify(scrypt);

// パスワードをsignupが保存するテキストに変換します。
// 現時点では、入力されたパスワードをそのまま保持します。
export async function hashPassword(password) {
    return password;
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

認証とセキュリティのすべてのレッスン

自分で練習してみよう: Expressプレイグラウンド