まず「salt 付きハッシュ」で何を守りたいのか
salt(ソルト)付きハッシュは、一言でいうと、
「同じ値でも、ユーザーごと・レコードごとに違うハッシュ値にする仕組み」
です。
例えば、パスワード "password123" をそのまま SHA-256 でハッシュすると、誰が使っても同じ値になります。
echo hash('sha256', 'password123');
// 例: ef92b7...
PHPもし 100 人のユーザーが同じパスワードを使っていたら、
DB の中には「同じハッシュ値」が 100 件並ぶことになります。
攻撃者からすると、
「このハッシュ値 = ‘password123’ だと一人分分かれば、
同じハッシュ値を持つ他の 99 人のパスワードも分かる」
という状態です。
ここを崩すために出てくるのが「salt(ソルト)」です。
salt の役割をイメージでつかむ
salt とは「ユーザーごとの隠し調味料」
salt は、ざっくり言うと「ランダムな追加文字列」です。
ハッシュ化したい値: "password123"
salt: "A9f3kL0zXy12AbCd"
→ "password123A9f3kL0zXy12AbCd" をまとめてハッシュ
PHPこうすると、同じ "password123" でも、
salt が違えばハッシュ値も全部違います。
ユーザーごとに別々の salt を使えば、
同じパスワードを使っているユーザーがいても、
DB 上のハッシュ値はバラバラになる
という状態を作れます。
これが salt の一番大事な役割です。
先に結論:パスワードには「自前の salt 付きハッシュ」は使わない
ここ、めちゃくちゃ重要なので先に言い切ります。
パスワードの保存に関しては、
「自前で salt を作って SHA-256 でハッシュ」
という実装は、今の時代の標準からすると不十分
です。
PHP には、パスワード用に
password_hash()
password_verify()
PHPという「塩もストレッチングも全部込み」の関数が用意されています。
パスワードを扱うときは、基本的にこれだけで完結させるのが正解です。
// 保存時
$hash = password_hash($password, PASSWORD_DEFAULT);
// 検証時
if (password_verify($password, $hash)) {
// OK
}
PHPこの中で、salt の生成・管理・ストレッチング(何回もハッシュを重ねる)などを
全部いい感じにやってくれます。
なので、「salt 付きハッシュ」を自前で実装するのは、
パスワード以外の値(トークン、識別子など)
学習目的で「仕組みを理解するため」
に限定するのが安全です。
ここから先は、「仕組みを理解するため」として読んでください。
自前でやる「salt 付きハッシュ」の基本パターン
1. ランダムな salt を生成する
まずは、十分にランダムな salt を作ります。
function generate_salt(int $bytes = 16): string
{
// 暗号論的に安全な乱数
$random = random_bytes($bytes);
// 保存しやすいように 16進文字列にしておく
return bin2hex($random);
}
PHP使い方の例です。
$salt = generate_salt(); // 16バイト → 32桁の16進文字列
echo $salt;
// 例: "a9f3c2b4d6e7f80123ab45cd67ef8901"
PHPここでのポイントは、「salt も暗号論的に安全な乱数で作る」ことです。mt_rand() などは使いません。
2. 値と salt を組み合わせてハッシュする
次に、「値+salt」をまとめてハッシュします。
function salted_sha256(string $value, string $salt): string
{
// 結合の順番は「value + salt」でも「salt + value」でもよいが、固定する
$combined = $value . $salt;
return hash('sha256', $combined);
}
PHP使い方の例です。
$password = 'password123';
$salt = generate_salt();
$hash = salted_sha256($password, $salt);
echo $salt . PHP_EOL;
echo $hash . PHP_EOL;
PHP保存するときは、
salt: a9f3c2b4d6e7f80123ab45cd67ef8901
hash: 3f8c...(64桁)
のように、「salt とハッシュ値の両方」を DB に保存します。
salt 付きハッシュの保存と検証の流れ
保存時のイメージ
例えば、「API キーをハッシュ化して保存する」ケースを考えます。
$apiKey = 'user-generated-api-key';
$salt = generate_salt();
$hash = salted_sha256($apiKey, $salt);
// DB に保存
$stmt = $pdo->prepare('
INSERT INTO api_keys (user_id, salt, hash, created_at)
VALUES (:user_id, :salt, :hash, NOW())
');
$stmt->execute([
':user_id' => $userId,
':salt' => $salt,
':hash' => $hash,
]);
PHP検証時のイメージ
ユーザーから API キーが送られてきたとき、
それが正しいかどうかをチェックします。
$inputApiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
// DB から salt と hash を取得
$stmt = $pdo->prepare('SELECT salt, hash FROM api_keys WHERE user_id = :user_id');
$stmt->execute([':user_id' => $userId]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) {
// API キーが登録されていない
}
$expectedHash = $row['hash'];
$salt = $row['salt'];
$actualHash = salted_sha256($inputApiKey, $salt);
// 比較は hash_equals で
if (!hash_equals($expectedHash, $actualHash)) {
// 不正な API キー
}
PHPここでのポイントは、
DB には「salt とハッシュ値だけ」を保存しておき、
検証時に「入力値+salt」を同じルールでハッシュして比較する
という流れです。
salt を「ユーザーごとに変える」ことの意味
salt がない場合の危険な状態
もし salt を使わずに、単に
$hash = hash('sha256', $value);
PHPだけで保存していたとします。
同じ値なら、どのユーザーでも同じハッシュ値になります。
攻撃者が一人分の値を突き止めると、
同じハッシュ値を持つ他の人の値も一気にバレる、という状態です。
salt があるとどう変わるか
ユーザーごとにランダムな salt を使っていれば、
user A: value = "abc", salt = "AAA...", hash = H1
user B: value = "abc", salt = "BBB...", hash = H2
PHPのように、同じ "abc" でもハッシュ値はバラバラになります。
つまり、
「ハッシュ値が同じだからといって、同じ元の値とは限らない」
「一人分の値がバレても、他の人には波及しにくい」
という状態を作れます。
これが salt の本質的な価値です。
もう一歩:salt 付きハッシュと「ストレッチング」
ここまでの話は、「salt を足して一回ハッシュする」だけでした。
しかし、パスワードのように「総当たり攻撃されやすい値」の場合は、
さらに「ストレッチング(何回もハッシュを重ねる)」が必要になります。
例えば、こんな感じです。
function salted_sha256_stretched(string $value, string $salt, int $iterations = 100000): string
{
$hash = $value . $salt;
for ($i = 0; $i < $iterations; $i++) {
$hash = hash('sha256', $hash, true); // バイナリで回す
$hash .= $salt; // 毎回 salt を混ぜる
}
return bin2hex($hash);
}
PHPただし、ここまで自前でやるくらいなら、
最初に出てきた password_hash() を素直に使った方が圧倒的に安全で楽です。
「salt + ストレッチング」を自前で正しく設計するのは、
セキュリティの専門領域に近いので、
業務では基本的に「標準関数に任せる」が正解です。
まとめ:今日からの「salt 付きハッシュ」ユーティリティ
大事なポイントを整理すると、こうなります。
salt は「ユーザーごと・レコードごとにハッシュ値をバラバラにするためのランダムな追加文字列」。
salt は random_bytes() などで十分ランダムに生成し、DB に一緒に保存する。
検証時は「入力値+salt」を同じルールでハッシュして、保存済みハッシュと hash_equals() で比較する。
パスワード保存は、自前の salt 付きハッシュではなく、必ず password_hash() / password_verify() を使う。
学習用・パスワード以外の用途としての「salt 付きハッシュ」の核になるコードは、これです。
function generate_salt(int $bytes = 16): string
{
return bin2hex(random_bytes($bytes));
}
function salted_sha256(string $value, string $salt): string
{
return hash('sha256', $value . $salt);
}
PHPもし、あなたのコードの中で「同じ値をそのまま SHA-256 でハッシュして保存している」箇所があれば、
そこが「salt を挟むかどうか」を検討するポイントです。
ただし、パスワードだけは例外で、「自前実装ではなく password_hash() 一択」——ここだけは強く覚えておいてほしいところです。
