.htpasswdの作成と記述方法【Base64/MD5/SHA-256/bcrypt】

.htpasswdはBasic認証で使用されるパスワード記述ファイルである。パスワードを記述する時はMD5、SHA-256、bcryptなどの方法によって暗号化しておくことが必要である。また、Basic認証ではログインIDとパスワードをBase64へエンコードして送信してる。.htpasswdの作成方法と記述の文法をまとめる。

.htpasswdの記述方法

[ユーザー名1]: [暗号化されたパスワード1]
[ユーザー名2]: [暗号化されたパスワード2]

入力時のルール

  • ユーザー名と暗号化されたパスワードを、半角コロン(:)記号で区切って記述する
  • 1行に1組ずつ、アカウントを作成したい数だけ改行する
  • 暗号化がされていないバスワードを記述してもベーシック認証は動作しない仕組みになっている

パスワードの暗号化方法

Basic認証では、ログインIDとパスワードをBase64へエンコードして平文(暗号化されていない状態のデータ)で送信しているが、それとは別に.htpasswdに記述するパスワードは、ハッシュ化しておくことが望ましい。

暗号化の方法にはMD5、SHA-256、bcryptなどの方法がある。

エンコード、ハッシュ化、暗号化のちがい

エンコードデータをある一定の規則に従って、別の形式のデータに変換すること。符号化
⇔ デコード(復号)
ハッシュ化ハッシュ関数によってデータを不規則な文字列に変換する手法
元データを復号できない(不可逆性)
暗号化暗号アルゴリズムによって元のデータを変換する手法。encryption
暗号鍵を用いて元データを復号できる
⇔ 復号(decryption)

平文、暗号文、ハッシュ値の違い

平文パスワードが丸見えの状態で送信されるため、漏洩したら簡単に盗める
暗号文暗号文を復号する鍵があればパスワードを解読されてしまう
ハッシュ値漏洩してもハッシュ値を元の値に戻すことは事実上不可能

base64でエンコード

Basic認証では、ログインIDのとログインパスワードをBase64と呼ばれるコードにエンコード(変換)して送信する。これは暗号化ではなく、Base64にエンコード(変換)しているだけなので、データを盗聴しBase64でデコード(元に戻す=復号)することで、ログインIDのとログインパスワードを盗まれてしまう。

例えば、パスワードとして準備した123456をBase64でエンコードすると、MTIzNDU2が得られる。

反対に、MTIzNDU2をBase64でデコードすると、123456が得られる。

また、123456を何度Base64でエンコードしても得られる値はMTIzNDU2となる。

つまり、MTIzNDU2を盗聴すれば、元のパスワードへデコードできてしまう。

Base64でエンコードするPHP関数

$id = "testuser";
$pw = "123456";
echo base64_encode($id);
echo "<br>";
echo base64_encode($pw);

// 結果
// dGVzdHVzZXI=
// MTIzNDU2

Base64でデコードするPHP関数

$id = "dGVzdHVzZXI=";
$pw = "MTIzNDU2";

echo base64_decode($id);
echo "<br>";
echo base64_decode($pw);

// 結果
// testuser
// 123456

MD5でハッシュ化

MD5とは、Message Digest Algorithm 5(メッセージ・ダイジェスト・アルゴリズム・ファイブ)の略で、ハッシュ関数のひとつ。与える値の長さに限らず、128ビット(16進数では32桁)のハッシュ値を生成する。ハッシュ関数により得られたデータのことをハッシュ値と呼ぶ。

  • ハッシュ関数の特徴
    • 入力データが同じであれば、必ず同じハッシュ値を出力する
    • 入力データが少しでも異なればまったく異なるハッシュ値が出力される
    • ハッシュ値への変換は簡単に計算できるが、元に戻すことは非常に困難である(一方向性関数)

同一のハッシュ値を持つ異なる原文のペアを効率よく探索することなどができるようになり、セキュリティ用途でMD5を使用するのは十分安全とは言えなくなっている。

使用例

例えば、.htpasswd生成ツール(MD5対応)で、任意のIDとパスワードをハッシュ化してみる。

回数元の値MD5でハッシュ化
1回目testuser:123456testuser:$apr1$pzwXuTQ9$H.1u/jQdnmnWYCZ9sT7nB0
2回目testuser:123456testuser:$apr1$QyNqCclq$nGwB04Y21E/qwIjuX9SOr1
3回目testuser:123456testuser:$apr1$pHrUzyxb$l5K44vbJ7F.4H3JyiayXZ/

上記ハッシュ値で共通のapr1.htpasswdの保護方法らしい。毎回異なるハッシュ値が得られる理由はソルトだろうか?(不明)

user1:$apr1$e3I6gNRm$411gIIUFlAJNJPmxw0AUr/
user2:$apr1$wZX4VsAi$aAMRN9Gcq64Su0idZ0KPC/

※ここで、htpasswdは特殊な暗号方式であり、md5を使っているとはいえ、単純なものではないようだ。ちなみに、2つ目の$と3つ目の$に挟まれた文字がSaltで、3つ目の$の右側は、Saltとパスワードに対して暗号処理をしている。なので、user1とuser2はどちらも同じパスワード(passwd)なのであるが、ここに記載された値は違っている。また、apr1の1はMD5を意味しているようで、違うハッシュを使う場合は値が変わるようだ。
→htpasswdのアルゴリズムはよくわからない(apache特有らしい)。ただ、SHA1は単純なはずなので、-sでSHA1によるハッシュをしてみた。以下にあるように、パスワードをSHA1でハッシュしてBase64しているという記載があり、実際にやってみたが、計算が合わない。※時間があるときにやりなおしたい。

MD5でハッシュ化するPHP関数

testuser123456をそれぞれMD5でハッシュ化する場合、md5関数を使用する。

$id = "testuser";
$pw = "123456";
echo md5($id);
echo "<br>";
echo md5($pw);

// 結果
// 5d9c68c6c50ed3d02a2fcf54f63993b6
// e10adc3949ba59abbe56e057f20f883e

SHA-256でハッシュ化

SHA-2(シャー)とは、Secure Hash Algorithm(セキュア・ハッシュ・アルゴリズム)の略で、ハッシュ関数のひとつ。

SHA-2にはいくつかの種類があり、全6種類の総称をSHA-2という。

  • SHA-224
  • SHA-256
  • SHA-384
  • SHA-512
  • SHA-512/224
  • SHA-512/256

SHA-256でハッシュ化するPHP関数

$pw = "123456";

$hased_string = hash('sha256', $pw);
print_r($hased_string . PHP_EOL);

// 結果
// 8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92

bcryptでハッシュ化

bcrypt(ビー・クリプト)は、Blowfish暗号を基盤としたパスワードハッシュアルゴリズム(暗号学的ハッシュ関数)。

ハッシュ値をは漏洩しても元の値に戻すことは事実上不可能だが、パスワードのハッシュ値が漏洩してしまった場合、レインボーテーブル攻撃総当り攻撃(ブルートフォース攻撃)でハッシュ値からパスワードを推測されてしまう危険性がある。

そこで、ソルトストレッチングを考慮した形でハッシュ理へ変換してくれるのがbcrypt。

  • ソルト
    • パスワードをハッシュ値へと変換する際に、パスワードに付与するランダムな文字列のことパスワードにソルトを付与することで、ハッシュ値が全く異なる値で生成されるソルトは固定値ではなく、ユーザー毎にランダムな値で生成することが望ましい
  • ストレッチング
    • ハッシュ関数を用いてハッシュ値への計算を数千回~数万回繰り返し行うことストレッチングは回数が多いほど解析が困難になるも、サーバ負荷が増えるというデメリットもあるため、ストレッチングの回数は、サーバ負荷を考慮して検討する必要がある

bcryptが生成するハッシュ値の構造

bcryptを利用し、下記のようなハッシュ値を得られたとする。

$2y$10$1QVmWNzk.TsaZQLQ/zeI9OAZL02AWP.VdFPPyAc9hSc2Cp4yOXKtG

ハッシュアルゴリズム
先頭の2yは、ハッシュアルゴリズムのバージョンを示す。
他のバージョンは2, 2a, 2b, 2x, 2yなどがある。

ストレッチング回数(コストパラメータ)
10は、ストレッチング回数を示す。
この数字は2のn乗のn部分を示しているので、今回は2の10乗で1024回ストレッチングされている。

ソルト(22文字)
128ビット(22文字)は、ソルトを示す。
上記例では、1QVmWNzk.TsaZQLQ/zeI9O

結果のハッシュ値(31文字)
ソルトの後の184ビット(31文字)は、結果のハッシュ値を示す。
上記例では、AZL02AWP.VdFPPyAc9hSc2Cp4yOXKtG

bcryptでハッシュ化するPHP関数

$password = "123456";
$options = [
  'cost' => 12,
];
echo password_hash($password, PASSWORD_DEFAULT, $options);

// 1回目の結果
//$2y$12$dPO6R.jtYxzUTLKxFcEpLej9.4reXlwLwxcJSJzJqmZR3RWnWUoMG
// 2回目の結果
//$2y$12$e1gnohd9WS7QCOlWjntW0O6Fcvz3cfvqEDx2iBuNHrpAt97uI0pIm

password_hashの2つめの引数にPASSWORD_DEFAULTを指定し、bcrypt アルゴリズムを使用。生成される文字の長さは60文字。ハッシュ値は生成するごとに変更になる。