
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:管理者