node.jsのデフォルトのモジュールにcryptoパッケージがあり、node.jsで認証を実装する場合に広く使われている。
crypto: 使い方
const crypto = require('crypto')const salt = crypto.randomBytes(16).toString('hex')
const hash = crypto
.pbkdf2Sync('password', salt, 10000, 512, 'sha512')
.toString('hex')
このsaltとhashを保存しておき、入力されたpasswordを同様にhash化したものと検証する。
しかし、pbkdf2よりもBcryptのほうがアルゴリズムが強固であり、こちらを使った方が望ましい。
bcrypt: 使い方
const bcrypt = require('bcrypt')const main = async () => {
const hash = await bcrypt.hash('pass', 10)
console.log(hash)
// => $2a$10$Q9ppTJAXdSlC5S8jNGa9K.JC00A5Gq5DfJV8jYLVccaGequxzbUUG const ok = await bcrypt.compare('pass', hash)
console.log(ok) // => true const ng = await bcrypt.compare('nopass', hash)
console.log(ng) // => false
}main()
生成されたhashを保存しておきbcrypt.compareを使いpasswordと検証する。なぜ、ソルトを保存しなくていいかというと、hash値の8文字目から22文字が生成されたソルトであるからだ。生成されたhashは全てが暗号化されたパスワードではない。Hashに中身について見てみると以下のように分類できる。
$: セパレータ
2文字目~: 2a
暗号化のバージョン。2a が主流。
4文字目~: 10
コスト。暗号化を何回繰り返すか。ここが大きければ復号が困難になるが、サーバへの負荷が大きくなる。このライブラリのデフォルトは10。回数は2のn乗であるので、コストが10の場合1024回演算を行う。
8文字目~:Q9ppTJAXdSlC5S8jNGa9K.
この22文字がソルトと呼ばれるランダムで生成された文字列である。
~:JC00A5Gq5DfJV8jYLVccaGequxzbUUG
暗号化されたパスワード本体。
なぜBcryptではソルトを別に保存しなくていいのだろうと思ったが、生成されたHashにソルトが含まれていたからだった。
アルゴリズムは常に破られる可能性があるので、常にセキュリティを意識して開発する必要があります。今回は、pbkdf2からBcryptへ移行しました。何か議論があればコメントまたは、@akameco までお願いします。