PostgreSQLのUUID型とpgcryptoモジュールを使って会員パスワード認証を実装してみる


はじめまして、堀井と申します。

このたびTECHSCORE BLOGに記事を書かせていただくことになりました。よろしくお願いします。

普段の仕事ではデータベースは専らMySQLを使っていますが、この機会をお借りして、久しぶりにPostgreSQLを触ってみました。(Ver7系が主流だった頃以来なので多分10年ぶりくらい?)

会員制Webサイトという想定でパスワード認証を実装するという基礎的な内容ですが、せっかくなのでこれまで未経験の機能を使ってみます。

以下、動作を確認した環境です。

  • Windows 7
  • PostgreSQL 9.5.0

SQLはSQL Shell(psql)から実行しました。データベースやロールの作成については割愛します。

pgcryptoモジュールを有効にする

まずはpgcryptoをインストールします。(インストール対象データベースへのスーパーユーザ権限が必要です)

pgcryptoが有効になりました。

gen_random_uuid()関数でUUIDを生成する

普段のMySQLの仕事ではレコードのIDには大抵の場合AUTO INCREMENTを利用するのですが、今回はPostgreSQLならではのUUID型を使ってみることにします。

PostgreSQL 9.4.5文書 / UUID型
http://www.postgresql.jp/document/9.4/html/datatype-uuid.html

PostgreSQLの標準機能ではUUIDを生成する関数は提供されていませんが、pgcryptoにはランダムなUUID(UUID ver4)を生成するgen_random_uuid()関数が用意されていますので、これを利用します。

こんな会員管理用のテーブルを作成してみます。

member_idがレコードのIDで、gen_random_uuid()関数でUUIDを自動発行します。

login_idの方は会員自身が設定するIDですが、同様に初期値としてUUIDを利用することにしました。

会員のレコードをまとめて登録してみます。

期待通り、UUIDが自動で発行されました。これは楽ちんですね。

以後、織田信長さんのデータを操作していきますが、UUIDを毎回入力するのは大変なので、まずはログインIDを変更しておきます。

crypt()関数でパスワードをハッシュ化

次はこの会員テーブルを使ったパスワード認証を実装してみます。

パスワードのハッシュ化のために設計されたという、crypt()関数とgen_salt()関数を利用します。

PostgreSQL 9.4.5文書 / pgcrypto / F.25.2. パスワードハッシュ化関数
http://www.postgresql.jp/document/9.4/html/pgcrypto.html#AEN161582

ドキュメントに記載されているcrypt()関数のポイントとしては、実行結果にソルトやアルゴリズムの種類など生成時の処理内容が含まれているため、異なるアルゴリズムで生成した値が同じテーブルに混在していても問題なく処理できることでしょうか。

更に「適応型」のアルゴリズムを利用した場合は、将来的にコンピュータの性能向上によってアルゴリズムを変更せざるを得なくなったとしても、互換性の問題が起きないとのこと。

アルゴリズムはdes, xdes, md5, bfが利用できますが、desとxdesはパスワードの最大長が8バイト、MD5は衝突耐性の問題がありますし適応型でもありません。Blowfishベースというbfが最大長72で適応型でもあり、今回の用途ではこれが良さそうです。

以下のSQLで、パスワードをハッシュ化して保存します。

こんな値が入りました。

格納されているハッシュ値をソルトとしてcrypt()関数を実行し、その結果が格納されているハッシュ値と一致するかどうかで、パスワードの正当性を検証できます。

認証に成功しました。

汎用ハッシュ関数でパスワードにソルトを加えつつハッシュ値をストレッチング

crypt()関数は便利ですが、それだけに頼る方法だと、データベースまでの経路では生のパスワードを扱うことになります。

また、経路を暗号化できない場合の対策として、クライアント側でパスワードのハッシュ値を生成する方式を採用することがありますが、その場合クライアント側とサーバ側でハッシュ値の生成手順を合わせなければいけません。

そこで、pgcryptoモジュールで提供されている汎用ハッシュ関数を利用し、多くの言語環境でサポートされているSHA-2ファミリのアルゴリズムにより、パスワードにソルトを加えてハッシュ化し、更に任意回数のストレッチングを行うこととします。

PostgreSQL 9.4.5文書 / pgcrypto / F.25.1. 汎用ハッシュ関数
http://www.postgresql.jp/document/9.4/html/pgcrypto.html#AEN161546

ハッシュ生成手順を以下の様な関数として定義します。

第1引数が生のパスワード、第2引数がアルゴリズム、第3引数がソルト、第4引数がストレッチング回数です。

digest()はバイナリハッシュを計算する関数で、標準のアルゴリズムとして md5, sha1, sha224, sha256, sha384, sha512 に対応しています。そして、これを16進数文字列に変換するために標準の文字列関数encode()も併用しています。

会員テーブルのpassword_hashカラムには、この関数で生成したハッシュ値を更にcrypt()関数をかけて保存します。

ソルトについては、ユーザ毎に異なり、推測困難で、ある程度の長さのある値が適している……ということで、UUID ver4が適任と思われますので、member_idをそのまま利用します。

先ほど定義したstretch_password_hash()関数を利用して、アルゴリズムはsha256、ストレッチング回数は1000回として、パスワードハッシュを生成してみます。

こんな値になりました。

念のため、PHPで同じ処理を実装して結果を確認してみます。(実は私は普段PHPばかり書いている人です)

同じ値になりました。

これを踏まえて、もう一度crypt()関数を通してパスワードハッシュを保存し直します。

先ほどPHPで算出した値をcrypt()関数に通して、正当性を検証してみます。

認証に成功しました。

パスワードハッシュのアルゴリズムとストレッチング回数をデータベースで管理する

SHA-1の衝突耐性の問題が指摘されて以来、SHA-2への移行が急務となっている昨今ですが、将来的には更にSHA-3への移行が推奨される状況になるはずです。

そこで、ハッシュ生成時に指定したアルゴリズムとストレッチング回数をパスワードハッシュのバージョンと捉えて、データベースで管理してみます。

今回は深く考えず、会員テーブルにそれぞれのカラムを追加します。

アルゴリズムは初期値をsha256に固定、ストレッチング回数はレコード登録時に500-1000の間で自動生成しておきます。

カラムの定義を追加すると、こんな感じで初期値が入りました。

この設定で織田信長さんのパスワードハッシュを保存してみます。アルゴリズムはsha256、ストレッチング回数は835回のはず。

先程のPHPの関数でハッシュ値を算出します。

この値をcrypt()関数に通して、正当性を検証してみます。

認証に成功しました。

次はアルゴリズムをsha512に、ストレッチング回数を100回に変更してパスワードを保存し直し、もう一度認証してみます。

先程のPHPの関数でハッシュ値を算出します。

SHA-512なので先程より長くなりました。この値をcrypt()関数に通して、正当性を検証してみます。

認証に成功しました。

今回の例ではハッシュ化のアルゴリズムとストレッチング回数を会員毎に保持しましたが、通常の認証ではそれらを頻繁に変更することはないため、アルゴリズムとストレッチング回数の組をパスワードハッシュのバージョンとして、会員に紐付けて別途管理しても良さそうです。

また、チャレンジ/レスポンス方式を採用する場合は、発行したチャレンジには有効期限を設けるべきですし、認証完了時に無効化しないとリプレイ攻撃への対策にはなりませんので、それらを会員に紐付くトランザクションデータとして別途管理する必要があると思います。

以上、あまり目新しさのない内容ですが、アプリケーション言語で実装されることが多いであろうパスワードのハッシュ化をPostgreSQLの機能で実装してみました。


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です