投稿日:2020年1月21日
パスワード認証を作成する際には注意しなくてはいけない点がいくつもあります。Djangoにはデフォルトでパスワード認証を実装するための関数が用意されています。この記事ではそれらの関数を紹介します。
Djangoにデフォルトで用意されているUserモデルは非常に便利です。しかし、きちんとしたWebサービスを作る際には独自の認証を作成することがよくあると思います。
そのような場合、いくつかの点に気をつけなくてはいけません。Djangoにはデフォルトでパスワード認証に関して必須の機能を備えた関数があるのでそれを使ってみましょう。
make_password()関数はpassword引数に渡された値からハッシュ値を生成する関数です。
基本的に情報漏えいなどの危険性の観点からユーザーから入力されたパスワードを平文で保存してはいけません。平文を保存しないようにユーザーから入力されたパスワードでの認証機能を作る際によく使われるのが、このmake_password()関数でもつかわれているハッシュ関数です。
では実際に使ってみます!
>>> from django.contrib.auth.hashers import make_password
>>> make_password("abcd")
'pbkdf2_sha256$150000$Q2xLAbr3JUK0$6X9vwqjU4gv4uHb13jTb0dPe67dIoWtlNo9zRWcu0Gw='
>>> make_password("abcd")
'pbkdf2_sha256$150000$knsXScoQEcSq$FXJ5Ne8/xeKt8VThBMSSKL4gAIyy6/R9pF+ns9K4LvI='
>>> make_password("abcd")
'pbkdf2_sha256$150000$DEZNU9s3KtXy$E61ZXUcUAkCX/pa7m4ew/IalioAk27nZ2gfVSmo7Za4='
ここでハッシュ関数を知っている方は上の動作に以下のような疑問を感じるでしょう。
「ハッシュ関数に同じ値を与えたら同じ値がかえるはず!!」
上の結果を見ると違う値が生成されています。
この理由はmake_password()関数の内部を見るとわかります。
def make_password(password, salt=None, hasher='default'):
if password is None:
return UNUSABLE_PASSWORD_PREFIX + get_random_string(UNUSABLE_PASSWORD_SUFFIX_LENGTH)
hasher = get_hasher(hasher)
salt = salt or hasher.salt()
return hasher.encode(password, salt)
つまり関数の中でsalt(入力値に加える値)を毎回違う値にしているため、毎回違うパスワードになっていたというわけです。
つまりsaltに毎回同じ値を与えると、同じ値が生成されるわけですね。
やってみましょう!
>>> from django.contrib.auth.hashers import make_password
>>> make_password("abcd0000","aaaaaaa")
'pbkdf2_sha256$150000$aaaaaaa$gd7pgbvUOvXR3gGUvUMHxupMIGZdsAcOJpfJXj8vDfc='
>>> make_password("abcd0000","aaaaaaa")
'pbkdf2_sha256$150000$aaaaaaa$gd7pgbvUOvXR3gGUvUMHxupMIGZdsAcOJpfJXj8vDfc='
実際にはこのようにsaltの値を一意に決めてしまっては、せっかくsaltを使う意味があまりないのでやめましょうね。
生成される値にただのハッシュ値ではなく後々確認するための必要な情報が含まれた値になっているのです。
例えば今回生成された値は$マークでくぎられて、以下のようになっています。
{ハッシュ関数}${ラウンド数}${ソルト値}${ハッシュにより生成された値}
これらの情報から自分でsplit()関数などを使ってif-elseで分岐して...とパスワードの確認処理を書いてもいいと思いますが、便利な関数をDjangoが提供してくれているのでそれを使ってみましょう。
check_password()はpassword引数で与えられた平文の値が、encoded引数で与えられたハッシュ値の元のパスワードと一致するのかを確認する関数です。
>>> from django.contrib.auth.hashers import make_password, check_password
>>> encoded = make_password("abcde0088")
>>> check_password("abcde0088", encoded)
True
>>> check_password("aaaaa0000",encoded)
False
Djangoの関数を使えば独自のパスワード認証を簡単に実装できそうですね。make_password()関数やcheck_password()関数のソースコードは非常に勉強になるので、興味があればみてみてください