パスワードのハッシュ化は常識!初心者向けセキュアコーディング実践ガイド

2025年4月28日月曜日

セキュアコーディング

パスワードのハッシュ化、聞いたことはあるけど実はよく知らない…なんてこと、ありませんか? 

ユーザーの大切なパスワードをそのまま保存するのは、セキュリティ的にめちゃくちゃ危ないんです! 

この記事では、セキュアコーディングの基本中の基本であるパスワードハッシュ化について、なぜ必要なのか、どうやるのか、どんな点に気をつければ良いのかを、初心者の方にも分かりやすく解説していきます。

読み終わる頃には、パスワード管理に自信が持てるようになっているはず!

この記事で学べること

  • パスワードをそのまま保存する危険性
  • パスワードハッシュ化の基本的な仕組み
  • 安全なハッシュ化の実装方法(ソルトやストレッチングも!)
  • PHPやPythonでの具体的なコード例
  • ハッシュ化でやりがちなミスとその対策

なぜパスワードのハッシュ化が必要不可欠なのか?

想像してみてください。もしあなたが運営するサービスのデータベースが攻撃を受けて、保存していたユーザーのパスワードが全部漏洩してしまったら…?

大変なことになりますよね。ユーザーからの信頼は失墜し、サービスの存続すら危うくなります。

パスワードをそのままの文字列、いわゆる「平文(ひらぶん)」で保存していると、万が一データベースが盗まれた場合、悪意のある攻撃者にパスワードが丸見えになってしまいます。

そうなると、不正ログインされ放題。他のサービスで同じパスワードを使っているユーザーがいれば、被害はさらに拡大します。

パスワードのハッシュ化は、こうした最悪の事態を防ぐための、セキュアコーディングにおける基本的な防御策なのです。

言わば、ウェブサービスにおける玄関の鍵のようなもの。鍵をかけない家に入る泥棒はいませんよね? パスワードハッシュ化は、そのくらい当たり前で、絶対にやるべき対策なんです。

パスワードハッシュ化の基本的な仕組みを理解しよう

では、パスワードハッシュ化とは一体何なのでしょうか? 難しく考える必要はありませんよ!

簡単に言うと、元のパスワードを、ある計算ルール(ハッシュ関数)を使って、別の意味不明な文字列(ハッシュ値)に変換することです。

この変換は一方通行で、ハッシュ値から元のパスワードを割り出すことは、ものすごーく難しい(ほぼ不可能)というのがポイントです。

例えるなら、ミキサーのようなもの。リンゴとバナナをミキサーにかけると美味しいスムージーができますが、そのスムージーから元の形のリンゴとバナナを取り出すことはできませんよね? パスワードハッシュ化もこれと同じイメージです。

  パスワード      ハッシュ関数       ハッシュ値
 (リンゴとバナナ)    (ミキサー)       (スムージー)
+-------------+      +-------------+      +----------------------+
|             |---->|             |---->|                      |
|  "password" |      |   SHA-256   |      | 5e884898da28...      |
|             |---->|             |---->|                      |
+-------------+      +-------------+      +----------------------+
                      (一方向!)

データベースには、このハッシュ値の方を保存しておきます。ユーザーがログインするときは、入力されたパスワードを同じハッシュ関数でハッシュ化し、保存されているハッシュ値と一致するかどうかを比較します。

一致すれば、正しいパスワードだと判断できるわけです。これなら、万が一データが漏れても、元のパスワードは誰にも分かりません。

ハッシュ関数とは?一方向性が重要な理由

パスワードハッシュ化の心臓部となるのが「ハッシュ関数」です。これは、入力されたデータ(パスワード)をもとに、固定長のユニークな(ように見える)値を計算する関数です。主な特徴は以下の2つ。

  • 同じ入力からは必ず同じハッシュ値が出力される
  • ハッシュ値から元の入力を推測するのは極めて困難(一方向性)

この「一方向性」こそが、パスワード保護のキモです。一度ハッシュ化してしまえば、元のパスワードに戻す方法がない。

だから、データベースに保存されているハッシュ値が盗まれたとしても、攻撃者はそれを使って直接ログインすることはできないのです。セキュアコーディングにおいて、この性質を利用しない手はありません。

危険!パスワードを平文保存するリスク

しつこいようですが、パスワードを平文で保存するのは本当に危険です! 具体的にどんなリスクがあるか、見てみましょう。

  • 不正アクセス
    データベースが侵害されたら、攻撃者はユーザー名とパスワードのリストをそのまま手に入れ、簡単にアカウントに侵入できます。
  • なりすまし
    攻撃者は盗んだパスワードを使ってユーザーになりすまし、個人情報を盗んだり、悪意のある投稿をしたりするかもしれません。
  • 他サービスへの波及
    多くの人は複数のサービスで同じパスワードを使い回しています。もしあなたのサービスからパスワードが漏れたら、そのユーザーが利用している他のサービス(銀行やSNSなど)も危険に晒される可能性があります。これは「パスワードリスト攻撃」と呼ばれ、非常に深刻な問題です。

パスワードハッシュ化は、こうした連鎖的な被害を防ぐためにも、絶対に欠かせないセキュアコーディングの実践項目なのです。

安全なパスワードハッシュ化の実装手順

さて、パスワードハッシュ化の仕組みと重要性が分かったところで、いよいよ実装の話です。ただ単にハッシュ関数を通せばOK!というわけではありません。

より安全にするための重要なポイントがいくつかあります。セキュアコーディングの実践として、以下の点をしっかり押さえましょう。

  1. 適切なハッシュアルゴリズムを選ぶ

  2. ソルト(Salt)を使う

  3. ストレッチング(Stretching)を行う

これらを順番に見ていきましょう!

どのハッシュアルゴリズムを選ぶべきか?

ハッシュ関数には様々な種類(アルゴリズム)がありますが、どれでも良いわけではありません。昔よく使われていたMD5やSHA-1といったアルゴリズムは、現在では脆弱性が見つかっており、安全とは言えません。

現在推奨されているのは、bcryptやArgon2といった、パスワードハッシュ化専用に設計されたアルゴリズムです。

これらは、単にハッシュ値を計算するだけでなく、後述するソルトやストレッチングの仕組みも組み込まれており、より安全性が高められています。もし、既存のシステムでSHA-256などを使う場合でも、ソルトやストレッチングは別途しっかり実装する必要があります。

迷ったら、まずはbcryptやArgon2の利用を検討するのが良いでしょう。多くのプログラミング言語で、これらのアルゴリズムを簡単に利用できるライブラリが提供されています。

パスワードハッシュ化の強度を高めるソルト(Salt)とは?

「ソルト」とは、パスワードをハッシュ化する際に付け加える、ユーザーごとに異なるランダムなデータのことです。料理に塩(Salt)を加えて味を変えるように、パスワードにソルトを加えてハッシュ値を変えるイメージです。

なぜソルトが必要なのでしょうか? もしソルトがないと、同じパスワードを使っているユーザーは、ハッシュ値も同じになってしまいます。「password123」のようなよく使われるパスワードは、あらかじめ計算されたハッシュ値のリスト(レインボーテーブル)と照合されることで、元のパスワードが特定されてしまう危険性があります。

ソルトを使うことで、たとえ同じパスワードでも、ユーザーごとに異なるハッシュ値が生成されるようになります

これにより、レインボーテーブルを使った攻撃が非常に困難になるのです。ソルトは、生成したハッシュ値と一緒にデータベースに保存しておくのが一般的です。これもセキュアコーディングの重要なテクニックです。

【ソルトなし】
"password" --(ハッシュ化)--> hash1
"password" --(ハッシュ化)--> hash1  ← 同じパスワードだと同じハッシュ値

【ソルトあり】
"password" + salt_A --(ハッシュ化)--> hashA
"password" + salt_B --(ハッシュ化)--> hashB ← 同じパスワードでも異なるハッシュ値

さらに強度を高めるストレッチングとは?

「ストレッチング」とは、ハッシュ計算を意図的に何千回、何万回と繰り返すことです。キー派生関数(Key Derivation Function, KDF)とも呼ばれます。

なぜそんな面倒なことをするのか? それは、攻撃者がパスワードを推測しようとする際の計算時間を増やすためです。攻撃者は、考えられるパスワードを片っ端からハッシュ化して、盗んだハッシュ値と一致するかどうかを試します(総当たり攻撃、ブルートフォースアタック)。

ストレッチングによってハッシュ計算1回あたりの時間を長くしておけば、攻撃者がパスワードを特定するのに膨大な時間がかかるようになり、現実的に解読を困難にできます

bcryptやArgon2といった推奨アルゴリズムには、このストレッチングの仕組みが組み込まれています。計算回数(コストファクター)は、サーバーの性能とセキュリティ要件のバランスを見て調整します。

PHPでのパスワードハッシュ化の実装例

PHPには、パスワードハッシュ化のための便利な関数が標準で用意されています。

これらを使えば、ソルトの生成やアルゴリズムの選択も安全に行ってくれます。セキュアコーディングがとても楽になりますね!

▼ パスワードをハッシュ化する (password_hash)

<?php
// ハッシュ化したいパスワード
$password = 'my_secure_password';

// パスワードをハッシュ化 (bcryptアルゴリズムがデフォルト)
// ソルトは自動で生成・利用される
$hashed_password = password_hash($password, PASSWORD_DEFAULT);

// 生成されたハッシュ値をデータベースに保存する
echo 'ハッシュ化されたパスワード: ' . $hashed_password;
// 例: $2y$10$abcdefghijklmnopqrstuvwx/yzabcdefghijklmnopqrstuvwx.yzabcde
?>

▼ ログイン時に入力されたパスワードとハッシュ値を比較する (password_verify)

<?php
// データベースから取得したハッシュ値(例)
$hashed_password_from_db = '$2y$10$abcdefghijklmnopqrstuvwx/yzabcdefghijklmnopqrstuvwx.yzabcde';

// ユーザーがログイン時に入力したパスワード
$input_password = 'my_secure_password'; // 正しい場合
// $input_password = 'wrong_password'; // 間違っている場合

// パスワードが一致するか検証
if (password_verify($input_password, $hashed_password_from_db)) {
    echo 'パスワードが一致しました!ログイン成功!';
} else {
    echo 'パスワードが間違っています。';
}
?>

これだけで、ソルト付きの安全なハッシュ化と検証が実現できます。とっても簡単ですね!

Pythonでのパスワードハッシュ化の実装例

Pythonでは、標準ライブラリの`hashlib`を使う方法もありますが、パスワードハッシュ化には、より安全で便利な`bcrypt`ライブラリを使うのが一般的です。

まず、ライブラリをインストールします。

pip install bcrypt

▼ パスワードをハッシュ化する (bcrypt)

import bcrypt

# ハッシュ化したいパスワード
password = b'my_secure_password' # バイト文字列で渡す

# ソルトを生成
salt = bcrypt.gensalt()

# パスワードとソルトを使ってハッシュ化
hashed_password = bcrypt.hashpw(password, salt)

# 生成されたハッシュ値をデータベースに保存する (バイト文字列なので必要に応じてデコード)
print(f"生成されたソルト: {salt.decode()}")
print(f"ハッシュ化されたパスワード: {hashed_password.decode()}")
# 例: $2b$12$abcdefghijklmnopqrstuvwx/yzabcdefghijklmnopqrstu

▼ ログイン時に入力されたパスワードとハッシュ値を比較する (bcrypt)

import bcrypt

# データベースから取得したハッシュ値(例、バイト文字列)
hashed_password_from_db = b'$2b$12$abcdefghijklmnopqrstuvwx/yzabcdefghijklmnopqrstu'

# ユーザーがログイン時に入力したパスワード (バイト文字列)
input_password = b'my_secure_password' # 正しい場合
# input_password = b'wrong_password' # 間違っている場合

# パスワードが一致するか検証
if bcrypt.checkpw(input_password, hashed_password_from_db):
    print('パスワードが一致しました!ログイン成功!')
else:
    print('パスワードが間違っています。')

bcryptライブラリを使うことで、ソルト生成やストレッチング(コストファクターによる計算回数調整)を含んだ安全なパスワードハッシュ化がPythonでも簡単に実装できます

パスワードハッシュ化でよくある間違いと対策

パスワードハッシュ化を実装する上で、いくつか注意したい点や、やりがちな間違いがあります。セキュアコーディングの観点から、しっかり確認しておきましょう。

危険!MD5やSHA-1を使ってはいけない理由

これは本当にやってはいけない間違いです。MD5やSHA-1は、かつて広く使われたハッシュアルゴリズムですが、現在では深刻な脆弱性が見つかっています。

  • 衝突可能性
    異なる入力から同じハッシュ値が生成されてしまう「衝突」が見つかっています。
  • 計算速度
    現代のコンピューターでは非常に高速に計算できてしまうため、総当たり攻撃に対する耐性が低いです。

これらのアルゴリズムは、パスワードハッシュ化のようなセキュリティ用途には絶対に使用しないでください。

もし既存システムで使われている場合は、bcryptやArgon2など、より安全なアルゴリズムへの移行を強く推奨します

ソルトの適切な生成と保存方法

ソルトを使う際にも注意点があります。

  • ランダム性
    ソルトは十分にランダムな値でなければ意味がありません。暗号論的に安全な乱数生成器を使って生成しましょう。
  • ユニーク性
    ユーザーごとに必ず異なるソルトを使用する必要があります。
  • 長さ
    ある程度の長さ(例 16バイト以上)を確保しましょう。
  • 保存場所
    生成したソルトは、対応するパスワードのハッシュ値と一緒にデータベースに保存するのが一般的です。ソルト自体は秘密情報ではありません。

よくある間違いは、全ユーザーで同じ固定のソルト(ペッパーと呼ばれることもありますが、ここではソルトの文脈で)を使ってしまうことです。これではソルトの意味がなくなってしまいます

必ずユーザーごとにランダムなソルトを生成・保存するようにしましょう。

パスワードリセット機能実装時の注意点

パスワードを忘れたユーザーのために、パスワードリセット機能は多くのサービスで必要になります。しかし、この機能の実装にもセキュリティ上の配慮が必要です。

  • リセットトークンの安全性
    パスワードリセット用のリンクに含まれるトークンは、推測困難で、かつ一度しか使えないようにする必要があります。
  • トークンの有効期限
    トークンには短い有効期限(数時間〜1日程度)を設定しましょう。
  • メールアドレスの検証
    リセット要求があったメールアドレスが、本当にそのユーザーのものであるかを確認するプロセスが推奨されます。
  • リセット後の通知
    パスワードがリセットされたことをユーザーに通知するのも有効な対策です。

パスワードリセット機能は、攻撃者にとってアカウント乗っ取りの新たな侵入口になり得ます。安易な実装は避け、関連するセキュアコーディングのベストプラクティスに従いましょう

まとめ パスワードハッシュ化を理解してセキュアなサービスを

パスワードハッシュ化について、その重要性から具体的な実装方法、注意点まで見てきました。これであなたもパスワード管理の基本はバッチリですね!

最後に、この記事で学んだ重要なポイントをまとめておきましょう。

  • パスワードの平文保存は超危険!必ずハッシュ化する。
  • ハッシュ化にはbcryptやArgon2など専用のアルゴリズムを使う。
  • ソルトを使ってレインボーテーブル攻撃を防ぐ。
  • ストレッチングで総当たり攻撃への耐性を高める。
  • MD5やSHA-1のような古いアルゴリズムは使わない。

パスワードハッシュ化は、ユーザーの情報を守り、信頼されるサービスを構築するための、セキュアコーディングにおける必須の知識です。

今日学んだことを活かして、ぜひあなたの開発プロジェクトに取り入れてみてください。安全なサービス作りは、こうした地道な対策の積み重ねから始まります。自信を持って、セキュアコーディングの道を歩んでいきましょう!

このブログを検索

  • ()

自己紹介

自分の写真
リモートワークでエンジニア兼Webディレクターとして活動しています。プログラミングやAIなど、日々の業務や学びの中で得た知識や気づきをわかりやすく発信し、これからITスキルを身につけたい人にも役立つ情報をお届けします。 note → https://note.com/yurufuri X → https://x.com/mnao111

QooQ