◆ crypt.scrypt

crypto.scrypt という関数を見つけました
パスワードをハッシュ化するときに使うものです
追加されたのは 10.5.0 なので比較的最近ですがちょっと前です
v10 のころはわりと変更点に目を通していたのですが パスワードをハッシュ化するようなちゃんとした認証をするウェブサーバなんて作らないので全然気づきませんでした

使い方はこんな感じです

const util = require("util")
const crypto = require("crypto")

util.promisify(crypto.scrypt)("text", "salt", 10).then(console.log)
// <Buffer 09 91 1e d8 a3 ea d5 eb 0d 84>

新しいものですがデフォルトで Promise を使うようになっていません
他と合わせるためかコールバックを使う非同期関数です
なので promisify を使ってます
同期処理がいいなら scryptSync があります

1 つ目の引数がパスワードで 3 つ目の引数が出力のバイト数です
Buffer なので toString で hex を指定すると文字数はその倍になります
(1 バイトは 16 進数表示にすると 00 ~ ff の 2 文字になります)

2 つ目の引数は salt で 16 バイト以上のランダムを指定するのが推奨らしいです
暗号化処理のパスワードみたいな感じで全体で固定のほうが扱いやすいのですが PHP のパスワードのハッシュ化関数みたいに毎回ランダムを作って 結果の一部に salt も含めて保存するのが良いのかもしれません
同じ salt だと 同じパスワードなら結果も同じになりますが salt が毎回ランダムなら同じパスワードでも結果が異なります

const crypto = require("crypto")

const passwordHash = (pw, len) => {
const salt_buf = crypto.randomBytes(16)
const result_buf = crypto.scryptSync(pw, salt_buf, len)
return [salt_buf.toString("base64"), len, result_buf.toString("base64")].join()
}

const passwordVerify = (pw, hash) => {
const parts = hash.split(",")
const salt_buf = Buffer.from(parts[0], "base64")
const len = +parts[1]
const result_b64 = parts[2]
return result_b64 === crypto.scryptSync(pw, salt_buf, len).toString("base64")
}

const hash = passwordHash("password", 16)
hash
// 'jMgZLkX0+JNOfzuHKgYrEw==,16,/JLx23J3skDsqyueDucd6Q=='

passwordVerify("password", hash)
// true

const hash2 = passwordHash("password", 16)
// 'kL9VVsfT2PH3Gim9cQzWmA==,16,IMcAfTKDLVsj6ieVJpDepQ=='

そういう特性上 実行ごとに salt をランダムにすると チェックするときにパスワードをハッシュ化してそれに一致するものを探すというありがちな考え方ができません
ハッシュ化されたものとパスワードを用意して このハッシュ値はこのパスワードから作られたものか というチェックになります
データベースの検索条件で ID とハッシュ値を送って 1 件返ってくればマッチするユーザがいる とはできません
ID でユーザ情報を取得してから パスワードが一致するかのチェックになります

scrypt では PHP の password_hash 関数と違って こういう使い方が強制されるわけではないので こういう面倒くささが嫌なら全部で同じ salt を使うこともできます

4 つ目の引数にはオプションでコストやブロックサイズや並列数を設定できます
いまいちよくわかってないのですがオプションにある maxmem の説明に 「128 * N * r > maxmem」 を満たすとエラーになるとあるので N (コスト) や r (ブロックサイズ) を大きくするとメモリ消費が増えるようです

scrypt は特に Node.js 固有のものではないアルゴリズムで Wikipedia に詳しいアルゴリズムも載ってます
パラメータの N などもそのままなので詳しい挙動が気になるなら読んでみると良いと思います
ただ 基本はこの辺のパラメータはデフォルトでいいと思います