PHPでログイン機能を実装する場合、ユーザーのパスワードをそのままデータベースに保存してはいけません。
たとえば、ユーザーが次のようなパスワードを登録したとします。
password123
この文字列をそのままDBに保存すると、データベースが漏洩したときにユーザーのパスワードがそのまま流出します。
そのため、PHPではパスワードを保存するときに、password_hash() を使ってハッシュ化します。
$hash = password_hash($password, PASSWORD_DEFAULT);
ハッシュ化とは、パスワードを元に戻せない別の文字列に変換する処理です。
暗号化とは違い、ハッシュ化されたパスワードは基本的に元の文字列へ戻せません。
ログイン時には、保存済みのハッシュを復号するのではなく、ユーザーが入力したパスワードと保存済みハッシュが対応しているかを password_verify() で確認します。
パスワードのハッシュ化と暗号化の違い
パスワード管理では、「暗号化」と「ハッシュ化」を混同しないことが重要です。
ハッシュ化は元に戻せない
ハッシュ化は、入力された文字列を別の文字列に変換する処理です。
たとえば、パスワードをハッシュ化すると、次のような文字列になります。
$2y$12$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
この値から、元のパスワードを取り出すことはできません。
つまり、管理者であってもユーザーのパスワードを確認できない状態にするのが正しい設計です。
暗号化は鍵があれば元に戻せる
暗号化は、鍵を使ってデータを変換する処理です。
鍵があれば、暗号化したデータを元に戻せます。
住所、電話番号、クレジットカード情報など、あとから復号して利用する必要がある情報には暗号化が使われることがあります。
一方、パスワードはあとから元の文字列を確認する必要がありません。
ログイン時に必要なのは、入力されたパスワードが正しいかどうかの判定だけです。
そのため、パスワード保存には暗号化ではなく、ハッシュ化を使います。
PHPでパスワードをハッシュ化する基本
PHPでパスワードを安全に保存する場合は、password_hash() を使います。
登録時は password_hash() を使う
ユーザー登録時には、入力されたパスワードをそのまま保存せず、ハッシュ化してから保存します。
$password = $_POST['password'] ?? '';
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
この $passwordHash をデータベースに保存します。
平文の $password は保存しません。
DBにはハッシュ値を保存する
ユーザーテーブルを作る場合、パスワード用のカラム名は password よりも password_hash のほうが分かりやすいです。
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NULL
);
password_hash という名前にしておくことで、「ここには平文パスワードではなく、ハッシュ化済みの値を保存する」という意図が明確になります。
カラム長は VARCHAR(255) にする
パスワードハッシュを保存するカラムは、VARCHAR(255) にしておくのが無難です。
password_hash VARCHAR(255) NOT NULL
現在の PASSWORD_DEFAULT はbcryptを使用しており、bcryptのハッシュは通常60文字です。
しかし、PASSWORD_DEFAULT は将来的に別のアルゴリズムへ変更される可能性があります。
また、Argon2idなどを使う場合はbcryptより長いハッシュになることがあります。
そのため、VARCHAR(60) のように短く固定せず、VARCHAR(255) にしておくと安全です。
PASSWORD_DEFAULT とは
password_hash() の第2引数には、使用するアルゴリズムを指定します。
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
現在の PASSWORD_DEFAULT はbcrypt
PASSWORD_DEFAULT は、PHPが用意しているデフォルトのパスワードハッシュアルゴリズムです。
現在のPHPでは、PASSWORD_DEFAULT は PASSWORD_BCRYPT のエイリアスです。
つまり、次のように書いた場合、現在はbcryptでハッシュ化されます。
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
bcryptは、パスワード保存用として広く使われているハッシュアルゴリズムです。
将来的にアルゴリズムが変わる可能性がある
PASSWORD_DEFAULT は、将来のPHPバージョンで別のアルゴリズムに変更される可能性があります。
そのため、PHPでは PASSWORD_DEFAULT を使う場合、ハッシュ値を保存するカラムに十分な長さを確保することが推奨されます。
実務では、次のような設計にしておくと扱いやすいです。
password_hash VARCHAR(255) NOT NULL
ログイン時は password_verify() で検証する
ログイン処理では、ユーザーが入力したパスワードと、DBに保存されているハッシュ値を照合します。
このとき、password_hash() で再度ハッシュ化して比較してはいけません。
正しいログイン判定
ログイン時は、password_verify() を使います。
$password = $_POST['password'] ?? '';
$savedHash = $user['password_hash'];
if (password_verify($password, $savedHash)) {
echo 'ログイン成功';
} else {
echo 'ログイン失敗';
}
password_verify() は、入力された平文パスワードと保存済みハッシュが一致するかを判定します。
password_hash() の結果を直接比較してはいけない
次のような実装は誤りです。
if (password_hash($inputPassword, PASSWORD_DEFAULT) === $savedHash) {
echo 'ログイン成功';
}
password_hash() は、同じパスワードを渡しても毎回異なるハッシュ値を生成します。
そのため、ハッシュ値同士を単純に比較しても正しく判定できません。
必ず password_verify() を使います。
同じパスワードでも毎回違うハッシュになる理由
password_hash() は、同じパスワードを渡しても毎回異なるハッシュ値を生成します。
echo password_hash('password123', PASSWORD_DEFAULT);
echo password_hash('password123', PASSWORD_DEFAULT);
echo password_hash('password123', PASSWORD_DEFAULT);
出力されるハッシュ値は、それぞれ異なります。
ソルトが自動的に使われる
同じパスワードでも毎回違うハッシュになる理由は、内部でソルトが使われるためです。
ソルトとは、パスワードに加えられるランダムな値です。
ソルトを使うことで、同じパスワードを使っているユーザー同士でも、保存されるハッシュ値が同じになりにくくなります。
ソルトを自分で用意する必要はない
PHPの password_hash() を使う場合、自分でソルトを作る必要はありません。
むしろ、独自にソルトを付け加える実装は避けたほうが安全です。
$salt = 'my-original-salt';
$passwordHash = password_hash($password . $salt, PASSWORD_DEFAULT);
このような独自実装は、設計ミスや管理ミスの原因になりやすいです。
password_hash() は、必要なソルトを自動的に生成してくれます。
ハッシュ値には検証に必要な情報が含まれる
password_hash() が返す文字列には、検証に必要な情報が含まれています。
具体的には、次のような情報です。
- 使用されたアルゴリズム
- コスト
- ソルト
- 実際のハッシュ値
そのため、ソルトを別カラムに保存する必要はありません。
DBには、password_hash() の戻り値をそのまま保存すれば十分です。
bcryptの72バイト制限に注意する
現在の PASSWORD_DEFAULT はbcryptです。
bcryptを使う場合、重要な注意点があります。
bcryptは72バイトまでしか扱わない
bcryptでは、パスワードの入力が最大72バイトまでに制限されます。
72文字ではなく、72バイトです。
英数字だけならおおむね72文字まで扱えますが、日本語や絵文字を含む場合は1文字あたりのバイト数が増えます。
たとえば、日本語はUTF-8で1文字3バイト程度になることがあります。
そのため、日本語や絵文字を含む長いパスワードを許可する場合は注意が必要です。
長すぎるパスワードへの対応を決めておく
bcryptを使う場合は、パスワードの最大長をアプリ側で制限しておくと安全です。
$password = $_POST['password'] ?? '';
if (strlen($password) > 72) {
exit('パスワードが長すぎます');
}
ここで使っている strlen() は文字数ではなくバイト数を返します。
そのため、日本語UIでは「72文字以内」と表示するよりも、次のように表現したほうが分かりやすいです。
パスワードが長すぎます。短くしてください。
長いパスフレーズを重視するならArgon2idも検討する
長いパスフレーズやUnicode文字を広く許可したい場合は、Argon2idの利用も検討できます。
ただし、Argon2idはサーバー環境によって利用できない場合があります。
そのため、実装前に password_algos() で確認するとよいです。
var_dump(password_algos());
Argon2idを使う方法
PHPでは、bcryptのほかにArgon2idも利用できます。
PASSWORD_ARGON2ID を使う
Argon2idを使う場合は、次のように書きます。
$passwordHash = password_hash($password, PASSWORD_ARGON2ID);
Argon2idは、現代的なパスワードハッシュアルゴリズムとしてよく推奨されます。
新規開発で、サーバー環境が対応している場合は、有力な選択肢になります。
Argon2idが使えるか確認する
Argon2idは、PHPのバージョンだけでなく、PHPがArgon2サポート付きでビルドされているかにも影響されます。
そのため、次のように確認してから使うと安全です。
if (defined('PASSWORD_ARGON2ID') && in_array(PASSWORD_ARGON2ID, password_algos(), true)) {
$passwordHash = password_hash($password, PASSWORD_ARGON2ID);
} else {
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
}
このようにしておくと、Argon2idが使える環境ではArgon2idを使い、使えない環境では PASSWORD_DEFAULT にフォールバックできます。
Argon2idのオプション指定例
Argon2idでは、メモリコストや時間コストを指定できます。
$passwordHash = password_hash($password, PASSWORD_ARGON2ID, [
'memory_cost' => 19456,
'time_cost' => 2,
'threads' => 1,
]);
ただし、コストを高くしすぎると、ログイン処理の負荷が大きくなります。
ユーザー数が多いサービスでは、サーバー性能や同時ログイン数を考慮して調整する必要があります。
password_needs_rehash() でハッシュを更新する
パスワードハッシュの方式やコストは、将来的に見直す必要があります。
PHPでは、password_needs_rehash() を使うことで、古いハッシュを新しい設定に更新できます。
ログイン成功時に再ハッシュする
ログイン成功時に、保存済みハッシュが現在の設定に合っているかを確認します。
if (password_verify($password, $user['password_hash'])) {
if (password_needs_rehash($user['password_hash'], PASSWORD_DEFAULT)) {
$newHash = password_hash($password, PASSWORD_DEFAULT);
$stmt = $pdo->prepare(
'UPDATE users SET password_hash = :password_hash WHERE id = :id'
);
$stmt->execute([
':password_hash' => $newHash,
':id' => $user['id'],
]);
}
session_regenerate_id(true);
$_SESSION['user_id'] = $user['id'];
}
この方法なら、すべてのユーザーに一斉にパスワード変更を求める必要がありません。
ログインしたユーザーから順番に、新しいハッシュ方式へ移行できます。
Argon2idへ移行する場合にも使える
たとえば、以前はbcryptで保存していたパスワードを、今後はArgon2idへ移行したい場合にも使えます。
if (password_verify($password, $user['password_hash'])) {
if (password_needs_rehash($user['password_hash'], PASSWORD_ARGON2ID)) {
$newHash = password_hash($password, PASSWORD_ARGON2ID);
// DBを更新する
}
}
このように、password_needs_rehash() はパスワードハッシュの保守に役立ちます。
登録処理の実装例
ここからは、ユーザー登録処理のサンプルを紹介します。
以下は説明用の簡略コードです。
実際の本番環境では、CSRF対策、メール認証、レート制限、ログ管理なども追加してください。
ユーザー登録処理
<?php
session_start();
$pdo = new PDO(
'mysql:host=localhost;dbname=sample;charset=utf8mb4',
'db_user',
'db_password',
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]
);
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';
if ($email === '' || $password === '') {
exit('メールアドレスとパスワードを入力してください');
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
exit('メールアドレスの形式が正しくありません');
}
if (mb_strlen($password) < 8) {
exit('パスワードは8文字以上にしてください');
}
// PASSWORD_DEFAULTは現在bcryptのため、72バイト制限を意識する
if (strlen($password) > 72) {
exit('パスワードが長すぎます');
}
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
$stmt = $pdo->prepare(
'INSERT INTO users (email, password_hash, created_at) VALUES (:email, :password_hash, NOW())'
);
try {
$stmt->execute([
':email' => $email,
':password_hash' => $passwordHash,
]);
echo '登録が完了しました';
} catch (PDOException $e) {
// 本番環境では詳細なエラーを画面に表示しない
echo '登録に失敗しました';
}
登録処理のポイント
このコードのポイントは、入力されたパスワードをそのまま保存していないことです。
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
この1行でパスワードをハッシュ化し、DBには $passwordHash を保存しています。
また、PDOのプリペアドステートメントを使っているため、SQLインジェクション対策にもなります。
$stmt = $pdo->prepare(
'INSERT INTO users (email, password_hash, created_at) VALUES (:email, :password_hash, NOW())'
);
ただし、このコードだけで本番レベルのユーザー登録機能が完成するわけではありません。
実務では、次のような対策も必要です。
- CSRF対策
- メールアドレスの重複チェック
- メール認証
- 入力値の正規化
- ログ出力
- エラー表示の制御
- レート制限
- HTTPS対応
ログイン処理の実装例
次に、ログイン処理のサンプルを紹介します。
ユーザーのハッシュを取得する
ログイン時には、まず入力されたメールアドレスに対応するユーザーをDBから取得します。
$stmt = $pdo->prepare(
'SELECT id, email, password_hash FROM users WHERE email = :email LIMIT 1'
);
$stmt->execute([
':email' => $email,
]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
password_verify() でパスワードを確認する
取得したユーザー情報が存在し、かつ入力パスワードが正しいか確認します。
if (!$user || !password_verify($password, $user['password_hash'])) {
echo 'メールアドレスまたはパスワードが違います';
exit;
}
ログイン失敗時のメッセージは、メールアドレスとパスワードのどちらが間違っているか分からない表現にします。
メールアドレスまたはパスワードが違います
「メールアドレスが存在しません」「パスワードが違います」と分けて表示すると、攻撃者に登録済みメールアドレスを推測されやすくなります。
ログイン成功時はセッションIDを再生成する
ログイン成功時には、セッションIDを再生成します。
session_regenerate_id(true);
これは、セッション固定攻撃への対策です。
ログイン処理全体のコード
<?php
session_start();
$pdo = new PDO(
'mysql:host=localhost;dbname=sample;charset=utf8mb4',
'db_user',
'db_password',
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]
);
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';
$stmt = $pdo->prepare(
'SELECT id, email, password_hash FROM users WHERE email = :email LIMIT 1'
);
$stmt->execute([
':email' => $email,
]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$user || !password_verify($password, $user['password_hash'])) {
echo 'メールアドレスまたはパスワードが違います';
exit;
}
if (password_needs_rehash($user['password_hash'], PASSWORD_DEFAULT)) {
$newHash = password_hash($password, PASSWORD_DEFAULT);
$stmt = $pdo->prepare(
'UPDATE users SET password_hash = :password_hash, updated_at = NOW() WHERE id = :id'
);
$stmt->execute([
':password_hash' => $newHash,
':id' => $user['id'],
]);
}
session_regenerate_id(true);
$_SESSION['user_id'] = $user['id'];
$_SESSION['email'] = $user['email'];
echo 'ログインしました';
パスワード変更処理の実装例
ユーザーがログイン中にパスワードを変更する場合は、現在のパスワードを確認してから新しいパスワードを保存します。
現在のパスワードを確認する
まず、DBから現在のパスワードハッシュを取得します。
$stmt = $pdo->prepare(
'SELECT password_hash FROM users WHERE id = :id'
);
$stmt->execute([
':id' => $userId,
]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
次に、入力された現在のパスワードが正しいか確認します。
if (!$user || !password_verify($currentPassword, $user['password_hash'])) {
exit('現在のパスワードが正しくありません');
}
新しいパスワードをハッシュ化する
新しいパスワードは、登録時と同じように password_hash() でハッシュ化します。
$newPasswordHash = password_hash($newPassword, PASSWORD_DEFAULT);
パスワード変更処理全体のコード
<?php
session_start();
$userId = $_SESSION['user_id'] ?? null;
if (!$userId) {
exit('ログインしてください');
}
$currentPassword = $_POST['current_password'] ?? '';
$newPassword = $_POST['new_password'] ?? '';
$stmt = $pdo->prepare(
'SELECT password_hash FROM users WHERE id = :id'
);
$stmt->execute([
':id' => $userId,
]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$user || !password_verify($currentPassword, $user['password_hash'])) {
exit('現在のパスワードが正しくありません');
}
if (mb_strlen($newPassword) < 8) {
exit('新しいパスワードは8文字以上にしてください');
}
if (strlen($newPassword) > 72) {
exit('新しいパスワードが長すぎます');
}
$newPasswordHash = password_hash($newPassword, PASSWORD_DEFAULT);
$stmt = $pdo->prepare(
'UPDATE users SET password_hash = :password_hash, updated_at = NOW() WHERE id = :id'
);
$stmt->execute([
':password_hash' => $newPasswordHash,
':id' => $userId,
]);
echo 'パスワードを変更しました';
パスワードリセット時の考え方
ユーザーがパスワードを忘れた場合、現在のパスワードをメールで送ってはいけません。
正しい設計では、そもそも元のパスワードを復元できないため、現在のパスワードを送ることはできません。
パスワードリセットの基本フロー
パスワードリセットは、一般的に次の流れで実装します。
- ユーザーがメールアドレスを入力する
- ランダムなリセットトークンを生成する
- トークンのハッシュをDBに保存する
- リセットURLをメールで送る
- URLアクセス時にトークンを検証する
- 新しいパスワードを入力してもらう
- 新しいパスワードを
password_hash()で保存する
リセットトークンは random_bytes() で生成する
リセットトークンには、推測されにくいランダムな文字列を使います。
$token = bin2hex(random_bytes(32));
この $token をメールで送るURLに含めます。
$resetUrl = 'https://example.com/reset-password.php?token=' . urlencode($token);
DBにはトークンのハッシュを保存する
リセットトークン自体をDBに平文で保存するのは避けたほうが安全です。
DBには、トークンをハッシュ化した値を保存します。
$tokenHash = hash('sha256', $token);
$stmt = $pdo->prepare(
'INSERT INTO password_resets (email, token_hash, expires_at) VALUES (:email, :token_hash, :expires_at)'
);
$stmt->execute([
':email' => $email,
':token_hash' => $tokenHash,
':expires_at' => date('Y-m-d H:i:s', time() + 3600),
]);
ここで hash('sha256', $token) を使っているのは、パスワード保存ではなく、十分にランダムなトークンの照合が目的です。
パスワードにSHA-256を使う話とは別なので、混同しないようにしましょう。
やってはいけないパスワード保存
PHPでログイン機能を作るときには、避けるべき実装があります。
平文で保存する
次のように、入力されたパスワードをそのまま保存してはいけません。
$password = $_POST['password'];
$stmt = $pdo->prepare('INSERT INTO users (password) VALUES (:password)');
$stmt->execute([
':password' => $password,
]);
この実装では、DBが漏洩した瞬間に全ユーザーのパスワードが流出します。
パスワードは必ずハッシュ化して保存します。
MD5を使う
次のように、MD5でハッシュ化する実装も避けるべきです。
$passwordHash = md5($password);
MD5は高速すぎるため、パスワード保存には向いていません。
攻撃者は大量の候補パスワードを高速に試せるため、短いパスワードやよく使われるパスワードは簡単に破られる可能性があります。
SHA-256をそのまま使う
SHA-256を単体で使う実装もおすすめできません。
$passwordHash = hash('sha256', $password);
SHA-256自体は暗号学的ハッシュ関数ですが、パスワード保存用としては高速すぎます。
パスワード保存には、bcryptやArgon2idのように、計算コストを調整できるパスワードハッシュアルゴリズムを使うべきです。
自作のハッシュ処理を使う
次のような独自実装も避けましょう。
$passwordHash = hash('sha256', 'secret_salt_' . $password);
一見すると安全そうに見えますが、パスワード保存の設計には多くの注意点があります。
独自実装では、ソルト管理、計算コスト、将来的な移行、検証処理などでミスが起きやすくなります。
PHPには password_hash() と password_verify() が用意されているため、基本的にはこれらを使うのが安全です。
ペッパーを使う場合の注意点
パスワード保存では、ソルトとは別にペッパーという考え方があります。
ペッパーとは
ペッパーとは、アプリケーション側で管理する秘密値です。
ソルトはユーザーごとに異なるランダム値で、通常はハッシュ文字列の中に含まれます。
一方、ペッパーはDBとは別の場所に保管する秘密情報です。
たとえば、環境変数に保存します。
$pepper = $_ENV['PASSWORD_PEPPER'];
ペッパーを使った実装例
単純に文字列結合する方法もありますが、HMACを使う方法もあります。
$pepper = $_ENV['PASSWORD_PEPPER'];
$pepperedPassword = hash_hmac('sha256', $password, $pepper);
$passwordHash = password_hash($pepperedPassword, PASSWORD_DEFAULT);
この方法では、まずパスワードに対してHMACを行い、その結果を password_hash() に渡しています。
ペッパーは必須ではない
ペッパーは追加の防御層として使える場合があります。
しかし、管理が難しい点に注意が必要です。
ペッパーが漏洩すると効果が薄れます。
また、ペッパーを変更すると既存パスワードの検証が難しくなります。
そのため、一般的なWebアプリでは、まずは password_hash() と password_verify() を正しく使うことを優先すべきです。
パスワードハッシュ化とあわせて行うべき対策
パスワードをハッシュ化していても、それだけでログイン機能全体が安全になるわけではありません。
ログイン機能では、周辺のセキュリティ対策も重要です。
HTTPSを使う
ログインフォームは必ずHTTPSで提供します。
HTTPのままだと、ユーザーが入力したメールアドレスやパスワードが通信経路上で盗み見られる可能性があります。
SQLインジェクション対策をする
DB操作では、PDOのプリペアドステートメントを使います。
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute([
':email' => $email,
]);
ユーザー入力をSQL文に直接埋め込む実装は避けましょう。
セッションIDを再生成する
ログイン成功時には、セッションIDを再生成します。
session_regenerate_id(true);
これにより、セッション固定攻撃のリスクを下げられます。
ログイン試行回数を制限する
同じIPアドレスや同じアカウントに対して、短時間に大量のログイン試行があった場合は制限をかけるべきです。
たとえば、次のような対策が考えられます。
- 一定回数失敗したら一時的にロックする
- IPアドレス単位で試行回数を制限する
- アカウント単位で試行回数を制限する
- CAPTCHAを追加する
- 不審なログインを記録する
エラーメッセージを出し分けない
ログイン失敗時には、次のようなメッセージにします。
メールアドレスまたはパスワードが違います
次のように出し分けるのは避けましょう。
メールアドレスが存在しません
パスワードが違います
出し分けると、攻撃者が登録済みメールアドレスを推測しやすくなります。
CSRF対策を行う
登録、ログイン、パスワード変更、パスワードリセットなどのフォームでは、CSRF対策も検討します。
特にパスワード変更処理では、CSRFトークンを使って不正なリクエストを防ぐことが重要です。
実務でおすすめの実装方針
PHPでパスワードハッシュ化を実装するなら、基本方針はシンプルです。
基本は password_hash() と password_verify()
登録時やパスワード変更時は、password_hash() を使います。
$hash = password_hash($password, PASSWORD_DEFAULT);
ログイン時は、password_verify() を使います。
if (password_verify($password, $hash)) {
echo 'ログイン成功';
}
この2つを正しく使うことが、PHPのパスワード管理の基本です。
DBカラムは VARCHAR(255)
パスワードハッシュを保存するカラムは、次のようにします。
password_hash VARCHAR(255) NOT NULL
PASSWORD_DEFAULT の将来的な変更や、Argon2idへの移行を考えると、十分な長さを確保しておくべきです。
ログイン時に再ハッシュを確認する
ログイン成功時には、password_needs_rehash() を使って、ハッシュの更新が必要か確認します。
if (password_needs_rehash($user['password_hash'], PASSWORD_DEFAULT)) {
$newHash = password_hash($password, PASSWORD_DEFAULT);
// DBを更新
}
この処理を入れておくと、将来的にアルゴリズムやコストが変わった場合にも移行しやすくなります。
新規開発ではArgon2idも検討する
サーバー環境が対応している場合は、Argon2idも有力な選択肢です。
$passwordHash = password_hash($password, PASSWORD_ARGON2ID);
ただし、環境依存やサーバー負荷を考慮する必要があります。
互換性や運用のしやすさを重視するなら、PASSWORD_DEFAULT を使う設計も十分実用的です。
まとめ
PHPでパスワードを安全に保存するには、平文のままDBに保存せず、必ずハッシュ化します。
基本の実装は次の3つです。
保存時
$hash = password_hash($password, PASSWORD_DEFAULT);
検証時
$isValid = password_verify($password, $hash);
必要に応じて再ハッシュ
if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
$newHash = password_hash($password, PASSWORD_DEFAULT);
}
避けるべき実装は、平文保存、MD5、SHA-256単体、自作ハッシュ処理です。
現在の PASSWORD_DEFAULT はbcryptであるため、72バイト制限には注意が必要です。
また、将来のアルゴリズム変更やArgon2idへの移行を考えて、DBカラムは VARCHAR(255) にしておくと安心です。
PHPには、パスワード保存に適した password_hash()、password_verify()、password_needs_rehash() が用意されています。
基本的にはこれらを正しく使い、あわせてHTTPS、SQLインジェクション対策、セッションID再生成、ログイン試行制限などを行うことで、安全性の高いログイン機能を実装できます。
以上、PHPのパスワードのハッシュ化についてでした。
最後までお読みいただき、ありがとうございました。










