【Security】Argon2がNodeで標準装備

こんにちは、フリーランスエンジニアの太田雅昭です。

ハッシュ化

ハッシュ化は、かつてはMD5などが用いられていました。しかしこれは脆弱であるため、2025年現在はArgon2, Scrypt, Bcryptなどが推奨されています。Argon2とScryptはメモリハードでより突破されにくく、そのうち最も堅牢とされているのがArgon2です。

Node.jsネイティブのArgon2

Argon2はNode.js v24.7から標準搭載されています。しかしかなり複雑な実装となります。

// https://nodejs.org/api/crypto.html#cryptoargon2syncalgorithm-parameters
import { argon2Sync, randomBytes, timingSafeEqual } from 'node:crypto';

const CONFIG = {
  parallelism: 4,
  tagLength: 64,
  memory: 65536,
  passes: 3,
  algorithm: 'argon2id',
} as const;

export function hashPassword(password) {
  const nonce = randomBytes(16);
  const tag = argon2Sync(
    CONFIG.algorithm,
    {
      message: password,
      nonce,
      parallelism: CONFIG.parallelism,
      tagLength: CONFIG.tagLength,
      memory: CONFIG.memory,
      passes: CONFIG.passes,
    });
  return {
    algorithm: CONFIG.algorithm,
    parallelism: CONFIG.parallelism,
    tagLength: CONFIG.tagLength,
    memory: CONFIG.memory,
    passes: CONFIG.passes,
    nonceHex: nonce.toString('hex'),
    tagHex: tag.toString('hex'),
  };
}

export function verifyPassword(stored, password) {
  const nonce = Buffer.from(stored.nonceHex, 'hex');
  const expected = Buffer.from(stored.tagHex, 'hex');
  const actual = argon2Sync(stored.algorithm, {
    message: password,
    nonce,
    parallelism: stored.parallelism,
    tagLength: stored.tagLength,
    memory: stored.memory,
    passes: stored.passes,
  });
  return timingSafeEqual(expected, actual);
}

const stored = hashPassword('password');
console.log(verifyPassword(stored, 'password'));  // true
console.log(verifyPassword(stored, 'wrong'));     // false

ライブラリのArgon2

ライブラリを使えばかなり楽に描けます。PHCフォーマットもされるため、DB保存もバッチリです。syncがないのが惜しいですが、メンテナンスコストは爆下がりです。

import argon2 from 'argon2';

async function main() {
  const password = 'password';
  const hash = await argon2.hash(password);
  const isVerified = await argon2.verify(hash, password);
  console.log({ hash, isVerified });
}

main();

Next.jsでは工夫が必要

Argon2ライブラリはバイナリを含むため、Next.jsでライブラリのArgon2を使う場合は工夫が必要です。以下のようにnext.config.tsで設定します。

const config: NextConfig = {
  webpack: (config, { isServer }) => {
    if (isServer) {
      config.externals.push({
        argon2: 'commonjs argon2',
      });
    }

    return config;
  },
};