私「自分のパスワード忘れたので教えて下さい!」
管理者「こちらではパスワードわかりません。」
私「管理者なんだからわかるだろーーーが!!嫌がらせすんなや!」
っとずっと思ってました。。
迷惑かけた方々、すみません。。笑
アプリ開発をするまで知らなかったのですが、
パスワードはそのままデータベースに保存してはいけない
は当たり前のようですね。
では、どうすれば良いのでしょうか?
ウェブの歴史がながいので、既にイロイロとやり方があるみたいですね。。
本記事ではその内容を紹介していきたいと思います。
結論
ひとことで、アプリ開発者は、
- 自分で実装せず、ライブラリを使おう!
間違いないですね。笑
長年の知恵と経験が詰まってますので、コミュニティに感謝しながら使いましょ。
STEP 1: パスワードの暗号化
「パスワードはそのままデータベースに保存してはいけない」とはいっても、認証するには入力したパスワードに紐付いた情報は必要ですよね。
そこで一般的には、パスワードをハッシュ関数を用いて暗号化して、その結果を保存します。
なぜハッシュ関数を使うのか?疑問に思いますが、それはハッシュ関数で得られた文字列に以下の特徴があるからです。
- ハッシュ化で得られた文字列から、元の文字列に戻すことはできない (→ 一方通行の計算式が使われてる)
- ただ、文字列を何度このハッシュ関数に通しても、同じ結果が得られる
そんなハッシュ関数ですが、実は何種類もあります。。orz
詳しい説明は 、Wikipedia の 「Secure Hash Algorithm」を参照してください。
そのなかでも、SHA-256 が一般的に利用されているようです。
ブロックチェーン技術でも使われていることから、その安全性は理解できますね。
ちなみに、こちらが Python でコーディングしサンプルです。
import hashlib
password = bytes('my-password', 'utf-8')
# または password = b'my-password'
print(hashlib.sha256(password).hexdigest())
そしてこちらが “my-password” を SHA-256 を実行した値になります。
6fa2288c361becce3e30ba4c41be7d8ba01e3580566f7acc76a7f99994474c46
一方通行とはいっても、よく使われるパスワードのリスト (ここからよく使われるパスワードを確認できます – Github) をハッシュ化しておけば、簡単にもとのパスワードを探し当てることができます。
また、レインボーテーブルといって、少ないリソースで元の値を見つける手法まであったりもします。(えっ!!笑)
もちろん、それを防ぐ方法はちゃんとあります。
STEP 2: salt による強化
パスワードをハッシュ化しただけではあまりよろしくないことはわかりました。
そこで利用されるのが、salt になります。
料理の味を塩で調整するように、ハッシュ化するパスワードを salt で調整します。
salt とは、ある文字列をパスワードに追加してハッシュすることをいいます。
ユーザーごとに違う salt を設定するのが一般的のようです。
これが Python での実装例。
import hashlib
import base64, os
salt = base64.b64encode(os.urandom(32))
def get_hash_with_salt(password, salt):
salting_hash256 = hashlib.sha256(salt + password).hexdigest()
return salting_hash256
print(get_hash_with_salt(password, salt))
STEP 3: ストレッチングによる計算負荷の増やす
さらに、ハッシュ値をハッシュ化し、新たなハッシュ値をすることもします!
しかも 1000 回とか 10000 回とか!笑
これはストレッチングと言って、少しでも時間をかけさせることが目的です。
import hashlib
import base64, os
salt = base64.b64encode(os.urandom(32))
def get_hash_with_salt_and_stretching(password, salt):
salting_hash256 = hashlib.sha256(salt + password).hexdigest()
for _ in range(1000):
salting_hash256 = hashlib.sha256(bytes(salting_hash256, 'utf-8')).hexdigest()
return salting_hash256
print(get_hash_with_salt_and_stretching(password, salt))
ってか、ライブラリ使おうぜ
イロイロやってきましたが、やはりライブラリで標準的に準備されていますので、自力でコーディングする必要は全くありません。。笑
import hashlib
import base64, os
salt = base64.b64encode(os.urandom(32))
library_hashed = hashlib.pbkdf2_hmac(
'sha256', password, salt, 1000
)
print(library_hashed.hex())
他にも bcrypt というモジュールもあります。
import bcrypt
# ハッシュ化
hashed_pwd = bcrypt.hashpw(b'my-password', salt)
# チェック
bcrypt.hashpw(b'my-password', hashed_pwd)
salt 値が hashed_pwd から分かるので、salt 値を渡さなく良いみたいです。
それは、安全なのか??と疑問に思いますが、他言語でも比較的よく使われているので、問題ないみたいですね。
100% 安全なパスワードはない
感の良い方はお気づきかと思いますが、salt を付与してもストレッチングをしても、いずれパスワードは探し当てられます。
ただ問題は、どれだけ解読に時間がかかるか? だけです。
一瞬の可能性もありますし、数百年の可能性もあります。
パスワードのクラックは「よく使われるパスワード」→「辞書にある単語」などから実行されるので、ランダムな英数字 + 記号を使った長いパスワードを使ってすぐには解読されないようにする必要がありますね。
なんて、奥が深い。。