Node.jsを使ったシステム開発において、パスワード認証は非常に重要な機能の一つです。
本記事では、クライアントから送信されたパスワードをデータベースのデータと照合する認証処理を実装する方法について解説します。
実装の背景
システムでは、以下のセキュリティ要件を満たす必要があります。
- データベース内のパスワードは暗号化された状態で保存する
- クライアント側では、パスワードをMD5ハッシュ化して送信する
- サーバー側で復号化したデータとクライアントからのハッシュ値を比較する
このような要件に対応するために、Node.jsのcrypto
モジュールを活用します。
データ登録処理
以下は、ユーザーデータを登録する処理の例です。
const crypto = require('crypto'); const { getCipheredData } = require('./common/crypto'); /** * データ新規登録 */ const insertUser = async (client, req) => { try { const userPasswordMd5 = req.body.password; // パスワードを暗号化 const password = getCipheredData(userPasswordMd5, req.config.crypto.password, req.config.crypto.user.salt, Buffer.from(req.config.crypto.user.iv)); // ユーザー登録 const insertUser = { text: ` INSERT INTO user( user_id, password ) VALUES($1, $2) RETURNING id`, values: [ req.body.user_id, password ], }; const result = await client.query(insertUser); return result.rows[0].id; } catch (err) { throw err; } };
パスワード認証処理
以下に、認証処理を簡潔にまとめたコードを示します。
const jwt = require("jsonwebtoken"); const { getDecipheredData, encryptMd5 } = require("../../common/crypto"); /** * ログイン処理関数 */ const signIn = async (client, req) => { try { const userPasswordMd5 = req.body.password; // データベースからユーザー情報を取得 const query = { text: ` SELECT id, user_id, password FROM user WHERE user_id = $1 AND del_flg = FALSE; `, values: [ req.body.user_id ], }; const result = await client.query(query); // 0件 if (result.rows.length == 0) { return null; }; const password = result.rows[0].password; // パスワードを復号化 const decryptedPassword = getDecipheredData( password, req.config.crypto.password, req.config.crypto.user.salt, Buffer.from(req.config.crypto.user.iv) ); // MD5ハッシュ化 const passwordMd5 = encryptMd5(decryptedPassword); // クライアントからのMD5パスワードと一致するか確認 if (userPasswordMd5 === passwordMd5) { // 一致したユーザーの情報を使ってトークンを発行 const payload = { userId: req.body.user_id, password: req.body.password, }; const accessToken = jwt.sign(payload, req.config.api_token.secret_key, { expiresIn: '90d' }); const refreshToken = jwt.sign(payload, req.config.api_token.secret_key, { expiresIn: '91d' }); return { userId: req.body.user_id, accessToken, refreshToken, }; } // 一致するデータがない場合 return null; } catch (err) { throw err; } }; module.exports = { signIn };
暗号化・復号化処理
以下は、暗号化および復号化を行うためのユーティリティ関数を定義したcrypto.js
ファイルの内容です。
const crypto = require('crypto'); const algorithm = 'aes-256-cbc'; // AESアルゴリズム[256ビット, CBCモード:Cipher Block Chaining mode (暗号ブロック連鎖モード)] /** * 暗号化処理 * * @param {*} data 平文文字列 * @param {*} password パスワード * @param {*} salt ソルト * @param {*} iv 初期化ベクトル * @return {*} */ const getCipheredData = (data, password, salt, iv) => { const inputEncoding = 'utf8'; const outputEncoding = 'hex'; // hexadecimal(16進数) const key = crypto.scryptSync(password, salt, 32); const cipher = crypto.createCipheriv(algorithm, key, iv); // 暗号化用インスタンス // 暗号化 let cipheredData = cipher.update(data, inputEncoding, outputEncoding); cipheredData += cipher.final(outputEncoding); return cipheredData; }; /** * 復号処理 * * @param {*} data 暗号化文字列 * @param {*} password パスワード * @param {*} salt ソルト * @param {*} iv 初期化ベクトル * @return {*} */ const getDecipheredData = (data, password, salt, iv) => { const inputEncoding = 'utf8'; const outputEncoding = 'hex'; // hexadecimal(16進数) const key = crypto.scryptSync(password, salt, 32); const decipher = crypto.createDecipheriv(algorithm, key, iv); // 復号用インスタンス // 復号 let decipheredData = decipher.update(data, outputEncoding, inputEncoding); decipheredData += decipher.final(inputEncoding); return decipheredData; }; /** * sha256ハッシュ化処理 * * @param {*} data 平文文字列 * @return {*} */ const encryptSha256 = (data) => { const hash = crypto.createHash('sha256'); hash.update(data); return hash.digest('hex'); }; module.exports = { getCipheredData, getDecipheredData, encryptSha256 };
処理の流れ
STEP
クライアントからのリクエスト受信
クライアントから送信されたMD5ハッシュ化済みパスワードを受け取ります。
STEP
データベースからユーザー情報を取得
全ユーザーのパスワードを含む情報をデータベースから取得します。
STEP
パスワードの復号化と照合
- データベースに保存されている暗号化パスワードを復号化します。
- 復号化したパスワードをMD5ハッシュ化して、クライアントからの値と比較します。
STEP
認証成功時の処理
一致した場合は、アクセストークンとリフレッシュトークンを発行します。
STEP
認証失敗時の処理
一致するデータがない場合はnull
を返します。
注意点
暗号化アルゴリズムやハッシュ化アルゴリズムを選定する際は、セキュリティ要件に基づいて慎重に検討してください。
MD5は衝突耐性が低いため、場合によってはSHA-256やbcryptを検討すべきです。
データベースのクエリで不要なデータを取得しないようにすることで、処理の効率を向上させることができます。
まとめ
本記事では、Node.jsを用いたパスワード認証処理の実装例を解説しました。
このコードをベースに、セキュリティ要件に応じた拡張やカスタマイズを行い、より安全な認証機能を実現してください。
- Original:https://minory.org/node-crypto-auth.html
- Source:minory
- Author:管理者