3秒でわかる
パスワードをハッシュ化する前に足す利用者ごとのランダム値。同じパスワードでも別の結果にして、まとめ攻撃を成立させなくします。
もう少し詳しく
どういうものか
ソルトは、パスワードをハッシュ化する前に付け加えるランダムな文字列です。利用者ごとに別の値を生成し、ハッシュ値と一緒にデータベースへ保存します。秘密の値ではないので、隠す必要はありません。
保存されるのは「パスワード + ソルト」を通したハッシュ値です。認証時は、入力されたパスワードに保存済みのソルトを足して同じ計算を行い、保存されたハッシュ値と一致するかを見ます。
なぜ必要か
ソルトが無いと、同じパスワードは常に同じハッシュ値になります。この性質があると、攻撃側は事前に「よく使われるパスワードとそのハッシュ値」の対応表を作っておき、漏えいしたデータと突き合わせるだけで大量の利用者を一度に解けます。これがレインボーテーブル攻撃です。
利用者ごとにソルトが違えば、同じ password123 でも保存値が全員異なります。事前計算した表は使えなくなり、攻撃は 1 件ずつ総当たりする作業に戻ります。同じ漏えいでも、被害の広がり方が根本から変わります。
具体例
Node.js の bcrypt はソルトの生成と埋め込みを内部で行います。
const bcrypt = require("bcrypt");
const hash1 = await bcrypt.hash("password123", 12);
const hash2 = await bcrypt.hash("password123", 12);
console.log(hash1 === hash2); // false (ソルトが違う)
const ok = await bcrypt.compare("password123", hash1);
console.log(ok); // true生成される文字列は下記の構造で、ソルトが値そのものに含まれています。
$2b$12$KIXQ2v0Yz9m1cQ8Rj3tGZe....
| | | |
| | ソルト(22文字) ハッシュ値
| コスト(2^12 回)
<a href="/glossary/algorithm" class="text-primary font-medium underline underline-offset-2 hover:text-primary-dark">アルゴリズム</a>識別子保存するのはこの 1 本の文字列だけです。照合時は compare が中からソルトとコストを読み取って同じ計算を再現します。
つまずきやすいところ
全利用者で同じソルトを使うと、対応表を 1 つ作り直すだけで破られます。ソルトは利用者ごと、登録のたびに生成します。
SHA-256 にソルトを足しただけで安全になったと考えるのも危険です。SHA-256 は高速に計算できるよう作られており、GPU を使えば毎秒数十億回試せます。パスワード保存には、計算コストを意図的に上げた bcrypt、scrypt、Argon2 を使います。
ソルトを別テーブルに隠して守ろうとする設計も見かけますが、ソルトは秘密情報ではありません。秘密の値を足す手法はペッパーと呼ばれ、ソルトとは目的が異なります。
似た用語との違い
| 用語 | 保存場所 | 目的 |
|---|---|---|
| ソルト | ハッシュ値と同じ場所 | 同一パスワードの衝突を防ぐ |
| ペッパー | アプリ側の秘密設定 | DB 単体の漏えいに備える |
| ストレッチング | 保存不要(回数のみ) | 計算を遅くして総当たりを鈍らせる |
覚え方
ソルトは全員の料理に別の味付けを施す作業です。味見の結果を 1 つ覚えても、他の皿の答えにはなりません。