はじめに
この記事では、JavaScriptを使用したユーザー認証の基本的な概念と、パスワードの安全な管理方法について解説します。
具体的には、パスワードのハッシュ化とその検証方法、そしてこれらを実現するためのbcryptモジュールの使用方法について詳しく説明します。
尚、今回の記事はハッシュやソルトの概念について学ぶ内容なので、データベースを使用しません。
この記事の対象者
この記事は、JavaScriptを使ったWeb開発に興味がある初中級者向けです。特に、ユーザー認証やパスワードの安全な管理方法について学びたいと考えている方にとって有益な内容となっています。
参照サイト
GitHub - node.bcrypt.js
1. 認証と認可
認証と認可は、セキュリティの重要な要素であり、しばしば一緒に使われますが、それぞれ異なる意味を持っています。
認証 (Authentication)
認証は、ユーザーが誰であるかを確認するプロセスです。一般的には、ユーザー名とパスワードの組み合わせを使用して、ユーザーが自分自身であると主張する身元を確認します。
他の認証方法には、生体認証(指紋や顔認証など)、ワンタイムパスワード(OTP)、デバイス認証(スマートフォンやハードウェアトークン)などがあります。
認可 (Authorization)
認可は、認証されたユーザーがアクセスできるリソースや実行できる操作を決定するプロセスです。
例えば、あるユーザーは特定のファイルを閲覧できるかもしれませんが、それを編集する権限はありません。認可は、ユーザーの役割、グループメンバーシップ、アクセス制御リスト(ACL)などに基づいて決定されます。
2. ハッシュ化とは
ハッシュ化は、任意の長さの入力を取り、固定長の文字列を出力するプロセスです。この出力は通常、ハッシュ値、ハッシュコード、ハッシュ合計、または単にハッシュと呼ばれます。
ハッシュ関数の主な特性
- 同じ入力からは常に同じハッシュ値が生成されます
- 異なる入力からは、異なるハッシュ値が生成されます(衝突が発生する可能性はありますが、理想的にはそれが最小限に抑えられます)
- ハッシュ値から元の入力を再構築することは不可能です(一方向性)
- ハッシュ化は、データの整合性チェック、パスワードの保存、データの重複検出など、多くの用途で使用されます
3. パスワードの管理方法
暗号学的ハッシュ関数
暗号学的ハッシュ関数は、ハッシュ関数の一種で、特にセキュリティに関連する用途で使用されます。これらの関数は、上記のハッシュ関数の特性に加えて、以下の特性を持っています:
計算が比較的遅い
これは、ハッシュ関数を使った攻撃をより困難にするためです。
ハッシュ値が予測不可能
同じハッシュ関数を使っても、入力がわずかに異なると、出力のハッシュ値は大きく異なります。
ソルトについて
ソルトは、ハッシュ化プロセスに追加されるランダムなデータのことを指します。ソルトを使用する主な目的は、レインボーテーブル攻撃や辞書攻撃を防ぐことです。これらの攻撃は、事前に計算されたハッシュ値のリスト(レインボーテーブル)を使用して、ハッシュ値から元のパスワードを推測しようとします。
ソルトをパスワードに追加すると、ハッシュ値がユニークになり、攻撃者がレインボーテーブルを使用してパスワードを推測するのを困難にします。ソルトは通常、ハッシュ値と一緒に保存され、パスワードの検証時に再利用されます。
4. bcryptを使用してハッシュ化を行う
4-1 : 初期設定
まず、bcryptモジュールをインストールします。以下のコマンドを実行します:
npm install bcrypt
4-2 : Node.jsを書く
次に、以下のNode.jsコードを書きます:
const bcrypt = require('bcrypt');
const hashPassword = async (password) => {
const salt = await bcrypt.genSalt(10);
const hash = await bcrypt.hash(password, salt)
console.log(salt);
console.log(hash);
}
hashPassword('123456');
このコードでは、まずbcryptモジュールをインポートします。
次に、非同期関数hashPasswordを定義します。この関数は、パスワードを引数として受け取り、bcryptのgenSalt関数を使用してソルトを生成します。ソルトの「ラウンド数」は10に設定されています。このラウンド数は、ソルトを生成する際の計算の複雑さを決定します。ラウンド数を増やすと、ソルトの生成に必要な時間が増え、ハッシュ関数の計算がより遅くなります。
genSalt関数がソルトを生成した後、hash関数を使用してパスワードをハッシュ化します。この関数は、パスワードとソルトを引数として受け取り、ハッシュ値を生成します。最後に、生成されたソルトとハッシュ値をコンソールに出力します。
このコードを実行すると、以下のような結果が得られます
$2b$10$0ZCYgIDKFgG0cpkUvCH1ru
$2b$10$0ZCYgIDKFgG0cpkUvCH1ruJ5hSpyx1E59.rFSAG99pPMWcpaDS8Ly
最初の行は生成されたソルトを、2行目は生成されたハッシュ値を示しています。ソルトが変わるとハッシュ値も変わることに注意してください。これは、ソルトがハッシュ関数の入力の一部であるためです。
4-3 : 省略した書き方
bcryptモジュールでは、ソルトの生成とハッシュ化を一度に行うことも可能です。以下のコードは、その省略形を示しています:
const bcrypt = require('bcrypt');
const hashPassword = async (password) => {
const hash = await bcrypt.hash(password, 12);
console.log(hash);
}
このコードでは、hash関数の第二引数に直接ラウンド数を指定しています。この関数は、内部でソルトを生成し、そのソルトを使用してパスワードをハッシュ化します。
5. 外から渡された値が、保存していたハッシュと一致するか確認する方法
5-1 : GitHubをチェックする
bcryptモジュールには、ハッシュ値と平文のパスワードを比較するcompare関数が含まれています。以下のコードは、その使用例を示しています:
参照 : GitHub - node.bcrypt.js
- GitHub一部抜粋
bcrypt.compare(myPlaintextPassword, hash, function(err, result) {
// result == true
});
bcrypt.compare(someOtherPlaintextPassword, hash, function(err, result) {
// result == false
});
この関数は、第一引数に平文のパスワード、第二引数に比較するハッシュ値を取ります。パスワードとハッシュ値が一致する場合、結果はtrueになります。一致しない場合、結果はfalseになります。
5-2 : 上記のドキュメントを元にコードを書く
以下のコードは、compare関数を使用してログイン処理を行う例を示しています:
const bcrypt = require('bcrypt');
const login = async (password, hashedPassword) => {
const result = await bcrypt.compare(password, hashedPassword);
if (result) {
console.log('Logged in!');
}
else {
console.log('Incorrect password!');
}
}
login('123456', '$2b$12$HdSax4benQHTqQZb7exPue0jn6Kwdm05otS99B.cmlqNCdx0s9.FC');
このコードでは、login関数を定義しています。この関数は、平文のパスワードとハッシュ値を引数として受け取り、compare関数を使用してそれらを比較します。結果がtrue(つまり、パスワードがハッシュ値と一致する)場合、ユーザーはログインします。結果がfalseの場合、エラーメッセージが表示されます。
5-3 : 結果
上記のコードを実行すると、以下のような結果が得られます:
Logged in!
これは、入力されたパスワードがハッシュ値と一致したため、ログインに成功したことを示しています。
まとめ
この記事では、JavaScriptでの認証の基本的な概念と、パスワードのハッシュ化と検証の方法について説明しました。特に、bcryptモジュールを使用してパスワードをハッシュ化し、そのハッシュ値を検証する方法について詳しく説明しました。
パスワードのハッシュ化は、セキュリティの重要な側面であり、ユーザーのパスワードを安全に保管するためには必須です。また、ハッシュ化されたパスワードを検証することで、ユーザーが正しいパスワードを入力したかどうかを確認できます。
しかし、これらの技術だけで完全なセキュリティが保証されるわけではありません。他のセキュリティ対策、例えば、パスワードの強度チェック、アカウントロックアウト、二要素認証などと組み合わせて使用することが重要です。