Node.jsのcryptoで暗号化したパスワードの復号化と認証処理

Node.jsを使ったシステム開発において、パスワード認証は非常に重要な機能の一つです。
本記事では、クライアントから送信されたパスワードをデータベースのデータと照合する認証処理を実装する方法について解説します。

実装の背景

システムでは、以下のセキュリティ要件を満たす必要があります。

  1. データベース内のパスワードは暗号化された状態で保存する
  2. クライアント側では、パスワードをMD5ハッシュ化して送信する
  3. サーバー側で復号化したデータとクライアントからのハッシュ値を比較する

このような要件に対応するために、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を用いたパスワード認証処理の実装例を解説しました。
このコードをベースに、セキュリティ要件に応じた拡張やカスタマイズを行い、より安全な認証機能を実現してください。


Amazonベストセラー

返信を残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA