パスワードを保存する時に最低限やるべき事　不可逆暗号（ハッシュ）化

## はじめに
仮に自前で認証機能を実装する場合、パスワードを保存する際には最低限すべき事があり、それについて少し理解を深めてみたので備忘録を残す。

※勉強中の身のため、誤りや理解不足等あるかもしれません。もしあればご指摘いただけると幸いです。

## パスワード保存時には不可逆暗号（ハッシュ）化をする
パスワード保存時にはそのままDBに保存するのは絶対NGで、[sha256](https://wa3.i-3-i.info/word15997.html)などの暗号学的に優れたハッシュ関数でハッシュ化するのが鉄則になる。

そして、単にハッシュ化すればいいのではなく、ソルト（Salt）と呼ばれるランダムな文字列をパスワードに追加してハッシュ化する必要がある。理由としてはハッシュ化の方法が分かれば https://hashtoolkit.com/ のようなサイトで簡単に逆引きができてしまうため。

実際にどのようにパスワードを保存すればいいのか？だが、例えば以下のようなコードになるだろう（以下はORMの[Sequelize](https://sequelize.org/)を利用している時の実装例で、[create（Simple INSERT queries）](https://sequelize.org/docs/v6/core-concepts/model-querying-basics/#simple-insert-queries)を行った際に暗黙的にパスワードをsha256でハッシュ化するような実装になっている）。

```js
// 省略
import sha256 from 'crypto-js/sha256';
import config from 'config';
// 省略
const encrypt = (v) => sha256(`${v}:${config.get('salt.password')}`).toString();

export default class Users extends Model {
	static init(sequelize, DataTypes) {
		return super.init(
			{
				// 省略
				password: {
					type: DataTypes.CHAR(64),
					allowNull: false,
					set(v) {
						this.setDataValue('password', encrypt(v));
					}
				},
				// 省略
			},
			{
				sequelize,
				// 省略
			}
		);
	}
}
```

ハッシュ化のコード自体はシンプルで、
```js
sha256(`${v}:${config.get('salt.password')}`).toString().
```
で`パスワード＋ソルト`をsha256によりハッシュ化している。

そして、上記のような実装でパスワードをハッシュ化した場合、実際に認証をする時は入力されたパスワードを同じ方法でハッシュ化し、`入力値のハッシュ値＝DBのハッシュ値`となれば、パスワードが一致しているとして、認証を通すという実装になるだろう。

※ハッシュ化する部分のソースコード全体は以下。

https://github.com/yuta-katayama-23/node-express/blob/main/src/models/sequelize/users.js

※sha256は入力された値を16進数の32バイト（64桁の英数字）（256ビット）にするアルゴリズムで、パスワードの不可逆暗号（ハッシュ）化以外にも、一意になる特徴を利用してファイルの妥当性チェック（[【Windows 11対応】MD5／SHA-1／SHA-256ハッシュ値を計算してファイルの同一性を確認する](https://atmarkit.itmedia.co.jp/ait/articles/0507/30/news017.html)などを参照）にも利用されたりする。

※今回は[crypto-js](https://www.npmjs.com/package/crypto-js)を利用したが、他にも[node.bcrypt.js](https://github.com/kelektiv/node.bcrypt.js)などのライブラリを利用してハッシュ化する方法もある。

### 追記
[コメント](https://qiita.com/yuta-katayama-23/items/6d3482e3e760807ded3c#comment-ffb5ed2a66ed12719411)でご指摘いただいたように、パスワードを保存する際には、ソルト＋ストレッチングの2つを行う事が望ましいようである。その2つを満たすためには[node.bcrypt.js](https://github.com/kelektiv/node.bcrypt.js)のようなライブラリを利用するとよく、それで簡単に要件を満たした実装ができる。実装方法は公式の[Usage](https://github.com/kelektiv/node.bcrypt.js#usage)に書かれている通りで、以下のようになる。

```js
// 省略
import bcrypt from 'bcrypt';
// 省略
const encrypt = (v) => bcrypt.hashSync(v, 10);

// 省略
				password: {
					type: DataTypes.CHAR(60), // ”60 characters long”と書かれているので 
					allowNull: false,
					set(v) {
						this.setDataValue('password', encrypt(v));
					}
				},
// 省略
```

ソースコード全体は以下。

https://github.com/yuta-katayama-23/node-express/commit/ca6fd6509cddba06334189975f56942c638e8724

- 参考：[「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典 ストレッチング【パスワード】](https://wa3.i-3-i.info/word16975.html)
- 参考：[【パスワード】bcryptとは](https://medium-company.com/bcrypt)

## まとめとして
今回はパスワードを保存する際に、最低限やるべき事としてパスワードに秘密鍵を追加してsha256によるハッシュ化を行う方法を見てきた。パスワードをそのまま保存するのは、仮に攻撃されて流出したら大問題になるし、たとえハッシュ化していてもソルト（Salt）を付けずにハッシュ化していると辞書攻撃でパスワードが分かってしまいセキュリティとしては弱いのでNG。最低限、ソルトを付けてハッシュ化は必要なるだろう。

※そもそも、自分でID・パスワードを管理するのは結構大変な気もするので、IdPに頼るのも一つの手な気もする（[Google Identity](https://developers.google.com/identity/protocols/oauth2/openid-connect)などでの認証はよく見るだろう）。[食べログ](https://tabelog.com/account/login/)なんかはIdPによる認識しかできないようになっている（2022年6月時点）。

- 参考：[パスワードを保存するときに考慮すること](https://christina04.hatenablog.com/entry/password-hash-function)