はじめに
エンジニアとして働き始めて、7年ほどソフトウェア開発に携わりました。
文系からエンジニアになりましたので、体系的にIT系の基礎知識を学んだことがなく、なんとなく自信が持てないまま、年数を重ねてきました。
なので、体系的に基礎知識を学ぶのと、自信につながるかもと思い、IT系の資格を取ってみようと勉強中です。
具体的には、基本情報と情報安全確保支援士とAWS関連の資格を取ろうと思っています。
今回は情報安全確保支援士の勉強で学んだパスワード管理について整理し、記事にしました。
本記事の内容
安全なパスワード管理を勉強する上で、目にするキーワードであるハッシュ・ソルト・ペッパーの整理をします。
1. ハッシュとは?
ハッシュとは、ハッシュ値を求めることを指します。
ハッシュ値とは、入力された値(パスワードなど)を一定のルールで不可逆な文字列にしたものです。
例えば、password
という文字列をハッシュ値にすると5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8
となったりします(計算方法によって異なります)
上記のハッシュ値を求める計算方法をハッシュ関数といいます
特徴は下記となります。
- 同じ入力からは同じ出力が得られる
- 出力から元の入力を復元することはできない
- 小さな変更でも出力が大きく変わる
2. ソルト(Salt)とは?
ソルトは、同じパスワードであっても異なるハッシュ値を生成させるためのランダムな文字列です。
これにより、辞書攻撃やレインボーテーブル攻撃を防ぐことができます。
ソルトの例:
- ソルト付きで同じパスワードをハッシュすると:
password123 + ソルトA → ハッシュA
password123 + ソルトB → ハッシュB
ソルトのポイント:
特性 | 内容 |
---|---|
ランダム性 | ユーザーごとにランダム生成 |
保存方法 | ハッシュに埋め込まれて保存される(例: BCrypt) |
公開可否 | 公開されていても問題なし |
攻撃対策 | 同じパスワードでも異なるハッシュにして識別困難にする |
3. ソルトがバレても安全?
ユーザー登録の際、ユーザーが入力したパスワードはハッシュ値に変換し、DBに保存します。
そして、ログイン時、ユーザーが入力したパスワードは、バックエンド側でハッシュ値に変換され、DBに保存されているハッシュ値と比較して、パスワードがあっているかどうかを確認します。
この時、ハッシュ値を生成するために使用したソルトは、ハッシュ値から取り出すようです。
ここで疑問が起きたのですが、「ハッシュ値にソルトが含まれていると、ハッシュ値がバレるとパスワードがバレるんじゃないか?」という疑問です。
結論から言うと、ソルトとハッシュ関数がバレるとパスワードもバレます。なので、計算速度が遅くなるようなハッシュ関数を使うのが対策となります(後述します)
ソルトの役割は同じ文字列であってもハッシュ値が異なるようにするのが目的です。なので、バレても問題ないというのが考え方となります。
4. ハッシュ関数がバレたら?
3にも少し書きましたが、ハッシュ関数がバレるとハッシュ値からパスワードが計算できるのでバレない方がいいんじゃないか?と思いました。
ただ、安全なパスワード管理では、ハッシュ関数は公開されていて当たり前という前提で設計されているみたいです(ケルクホフスの原理)
つまり:
- BCryptを使っていることが攻撃者にバレても問題ない
- それでも安全に保てる設計が必要
5. では、ソルトとハッシュ関数がバレたらどうなる?
何度も書きましたが、ソルトとハッシュ関数がバレた場合、
理論上はパスワードを総当たりで試せば割り出せます。
- 攻撃者は、辞書やブルートフォースを使って、ハッシュと一致する文字列を探すことが可能
- 重要なのは、計算コストの高いハッシュ関数を使うことになります
6. 安全なハッシュ関数(BCryptなど)を使う理由
ハッシュ関数 | 1回の計算時間 | 攻撃に対する強さ |
---|---|---|
SHA-256 | 0.000001秒 | 弱い(高速すぎて辞書攻撃に弱い) |
BCrypt | 約0.3秒 | 強い(1秒で数回しか試せない) |
BCryptやArgon2などの関数は、わざと遅くして攻撃者が総当たりできないようにしています。
7. さらに守るなら:ペッパー(Pepper)
ペッパーは、ソルトとは異なり、全ユーザー共通の秘密文字列です。
アプリ内で管理し、ソルトとは違って外部には保存・公開しません。
使い方(擬似コード):
ハッシュ対象 = パスワード + ペッパー
→ これをハッシュ化(BCryptなど)
ペッパーの特徴:
特性 | 内容 |
---|---|
役割 | 万が一ハッシュが漏洩した場合の追加防御 |
保存場所 | コード外・環境変数・シークレット管理サービスなど |
公開可否 | 非公開(秘密) |
セキュリティ効果 | 攻撃者がハッシュ値とソルトを手に入れても、ペッパーがなければ割り出し困難 |
8. 実装例(Go / PHP / TypeScript)
Go
const pepper = "my-secret-pepper"
func HashPassword(password string) ([]byte, error) {
combined := password + pepper
return bcrypt.GenerateFromPassword([]byte(combined), bcrypt.DefaultCost)
}
PHP
$pepper = 'my-secret-pepper';
function hashPassword($password) {
$peppered = $password . $GLOBALS['pepper'];
return password_hash($peppered, PASSWORD_BCRYPT);
}
TypeScript(Node.js)
const bcrypt = require('bcrypt');
const pepper = 'my-secret-pepper';
async function hashPassword(password: string): Promise<string> {
const combined = password + pepper;
return await bcrypt.hash(combined, 10);
}
※ Pepperは .env
や AWS Secrets Manager など、安全な場所に保管しましょう。
9. まとめ
要素 | 目的 | 公開可否 | 保存場所 |
---|---|---|---|
ソルト | 同じパスワードの一意性確保 | 公開してOK | ハッシュに含める |
ハッシュ関数 | パスワードの変換処理 | 公開してOK | アプリコードに含む |
ペッパー | 万が一の漏洩に備えた追加防御 | 非公開 | 環境変数・KMSなど |
10. セキュアなパスワード保存の鉄則
- **ソルト + 安全なハッシュ関数(BCryptなど)**を使う
- ペッパーを加えるとさらに強固
- 強くて長いパスワードを使う(ユーザー側にも指導を)
- ハッシュ値は絶対に元に戻らないようにする
おまけ:さらに強化するには?
- Argon2など最新のハッシュ関数も検討
- 2段階認証やパスワードレス認証の導入
- ログイン試行制限やIP制限などの追加セキュリティ