【Python】hashlibによるハッシュ化

Python でデータをハッシュ化するための hashlib モジュールについて解説します。
目次
ハッシュ化
ハッシュ関数(一方向関数)
パスワードなどのデータを解読できないように変換するによく使われる関数がハッシュ関数です。ハッシュ関数は、任意の長さのデータを通すと特定の長さのデータ(ハッシュ値)に変換します。
ハッシュ関数の特徴は、ハッシュ値を生成するのは簡単である一方、ハッシュ値から元のデータを復元することが非常に困難である点です。このような性質からハッシュ関数は、一方向関数と呼ばれます。
ハッシュ関数で代表的で一般的によく使用される SHA-256 は、長さが 256 ビットのハッシュ値に変換されます。暗号資産で有名なビットコインのハッシュ値計算でも SHA-256 が使用されています。
この記事では、Python でハッシュ化するためのモジュールである hashlib について紹介します。
ハッシュ化と暗号化の違い
ハッシュ化の他に暗号化という技術もあります。ハッシュ関数は、説明したように元に戻すことが非常に困難である一方向関数です。
暗号化は、鍵となる情報を用いることでもとのデータに戻すための復号化ができます。ハッシュ化と暗号化の違いは「データに戻すことができるかできないか」です。
ハッシュが使われる代表的な例がパスワード保管です。システムのデータベースに、パスワードをハッシュ化した値を保存します。パスワード認証では、入力値にハッシュ関数を通して、データベースのハッシュ値の一致を確認して認証します。
このようにしておくことで、クラッカーが悪意を持ってデータベースからパスワードを盗んでも元の文字列を復元することはできないため、システムの認証を通過することはできません。
hashlib によるハッシュ化
Python ではハッシュ化のためのモジュールとして hashlib が提供されています。以降では、簡単な例を使ってハッシュ化の方法を説明します。
hashlib で提供されているアルゴリズム
hashlib では、複数のハッシュ化アルゴリズムが提供されています。対応可能なハッシュについては以下のように algorithms_guaranteed で確認することができます。
import hashlib print(hashlib.algorithms_guaranteed)
【実行結果】
{'sha1', 'blake2s', 'shake_128', 'md5', 'shake_256', 'sha3_384', 'sha256', 'sha224', 'sha512', 'sha3_224', 'blake2b', 'sha384', 'sha3_512', 'sha3_256'}hashlib では上記のような多くのハッシュ化アルゴリズムが提供されています。
hashlib を用いたハッシュ化の方法
以降では、一般的によく使用される SHA-256 でのハッシュ化を例に hashlib の使い方を説明します。SHA-256 を例に使い方を記載しますが、SHA-256 以外のアルゴリズムを使いたい場合は、関数名や引数に指定する文字列を変更するだけでアルゴリズムの変更が可能です。
基本的な使い方
hashlib で SHA-256 でハッシュ化する場合には、SHA-256 に対応する sha256 を使用します。
import hashlib
def main():
password = "P@ssw0rd"
print(password)
# SHA-256でハッシュ化
digest = hashlib.sha256(password.encode("utf-8")).hexdigest()
print(digest)
if __name__ == "__main__":
main()【実行結果】 P@ssw0rd b03ddf3ca2e714a6548e7495e2a03f5e824eaac9837cd7f159c67b90fb4b7342
引数としては、上記のようにハッシュ化文字列をエンコードした値を渡します。上記では返却値に hexdigest() を適用して16進数表記のハッシュ値にしています。
例えば、SHA-512 を使いたい場合には、sha256 の部分を sha512 とするだけでアルゴリズムの変更が可能です。
update で複数対象を結合してハッシュ化
複数の対象を結合してハッシュしたい場合には、以下のように update で対象を順番に追加してからハッシュ化を実行します。
import hashlib
def main():
target_str1 = "sample01"
target_str2 = "sample02"
joined_str = target_str1 + target_str2
print(target_str1)
print(target_str2)
print(joined_str, "\n")
# updateで複数対象をつなげてハッシュ化
h = hashlib.sha256()
h.update(target_str1.encode("utf-8"))
h.update(target_str2.encode("utf-8"))
digest = h.hexdigest()
print(digest)
# 文字列を結合したもののハッシュ化と同じ
digest = hashlib.sha256(joined_str.encode("utf-8")).hexdigest()
print(digest)
if __name__ == "__main__":
main()【実行結果】 sample01 sample02 sample01sample02 af239deb316d2daad434f8ec780f2e89184d81ae60f3d98c76ca3de4dcca5e45 af239deb316d2daad434f8ec780f2e89184d81ae60f3d98c76ca3de4dcca5e45
update を使用する場合には、まず引数なしで sha256 を呼び出します。その後、ハッシュ化対象の文字列を順に update に渡していきます。
上記では、update で文字を順に追加してからハッシュ化したものと、文字列をあらかじめ結合してからハッシュ化したものを表示していますが、ハッシュ値は同じになっていることが分かります。
ファイルのデータをハッシュ化 (Python 3.11 で追加)
ファイル内のデータに対してハッシュ化したい場合には、以下のように file_digest を使用することができます。file_digest は、Python 3.11 で追加されているため、それ以前のバージョンの場合は使えませんのでご注意ください。
import hashlib
def main():
filepath = "targetfile.txt"
# ファイルを読み込んでハッシュ化
with open(filepath, "rb") as f:
h = hashlib.file_digest(f, "sha256")
digest = h.hexdigest()
print(digest)
if __name__ == "__main__":
main()【実行結果】 b03ddf3ca2e714a6548e7495e2a03f5e824eaac9837cd7f159c67b90fb4b7342
上記の結果は「P@ssw0rd」と書かれているだけの targetfile.txt というテキストファイルを用意しておいて実行した結果です。
対象パスのファイルをバイナリ読み取り専用 ("rb") で開き、file_digest にファイルオブジェクトとハッシュアルゴリズム文字列を指定します。
このようにファイルの内容をハッシュ化する際に file_digest は非常に便利です。
解読耐性のあるハッシュ値を取得する pdkbf2_hmac
上記で見てきた単純なハッシュ化は、ハッシュ値の対応表がある場合にクラッキングされる恐れがあります。解読対策として、以下のような方法が有効です。
- ハッシュ化するデータに Salt(ソルト)と呼ばれるデータを付け足してからハッシュ化する。
- 得られたハッシュ値に対して、一定回数以上のハッシュ化を繰り返す。この処理を Stretching(ストレッチング)という。
hashlib には、Salt(ソルト)、Stretching(ストレッチング)に対応した pdkbf2_hmac が用意されています。pdkbf2_hmac は以下のように使用します。
import base64
import hashlib
import os
# saltの生成
salt = base64.b64encode(os.urandom(32))
# hash化の回数
iter_num = 100000
def main():
password = "P@ssw0rd"
print(password)
# ハッシュ化
digest = hashlib.pbkdf2_hmac(
"sha256", password.encode("utf-8"), salt, iter_num
).hex()
print(digest)
if __name__ == "__main__":
main()【実行結果】 P@ssw0rd 3e4e252b3d998a58b28cdce579264439d458c1c1228831b239c86458b244d7f3
Salt(ソルト)値は、os.urandom で 32 バイトのランダムな文字列を生成し、base64 モジュールでエンコードして生成しています。Salt(ソルト)値は、os.urandom で 16 バイトかそれ以上のバイト列にするべきと言われています。ここは、こういった書き方ぐらいに覚えてしまってもよいかと思います。
pdkdf2_hmac には、ハッシュ化方法である "sha256" と Salt(ソルト)値、Stretching(ストレッチング)の繰り返し回数を指定してハッシュ値を計算します。
Salt や繰り返し回数が分からないとハッシュ値の計算は非常に困難になるため、クラッカーによる解読に対する耐性を持たせることができます。このように pdkdf2_hmac を使うことで解読耐性のあるハッシュ値を取得して利用することが可能です。
ログインを想定したハッシュ化の使用例
ハッシュ化の使用例として、ログインパスワードをハッシュ化する例を紹介します。
import base64
import hashlib
import os
# ユーザーとパスワードのDB
db = {}
# saltの生成
salt = base64.b64encode(os.urandom(32))
# hash化の回数
iter_num = 100000
def get_digest(password: str):
"""パスワードのハッシュ値計算
Args:
password: パスワード文字列
Returns:
ハッシュ値
"""
digest = hashlib.pbkdf2_hmac(
"sha256", password.encode("utf-8"), salt, iter_num
).hex()
return digest
def is_login(user_name: str, password: str):
"""ログイン処理
Args:
user_name: ユーザー名
password: パスワード
Returns:
ログイン結果
"""
return get_digest(password) == db[user_name]
def main():
user_name = "user01"
user_password = "P@ssw0rd"
digest = get_digest(user_password)
db[user_name] = digest
print(f"db: {db}\n")
print(f"正常なログインの場合: {user_name} {user_password}")
print(is_login(user_name, user_password))
user_password_ng = "password"
print(f"パスワード不正の場合: {user_name} {user_password_ng}")
print(is_login(user_name, user_password_ng))
if __name__ == "__main__":
main()db: {'user01': '4320c4509b9c068312131f719cac5380572eb5062000a95d9ae1ce5fca139ecd'}
正常なログインの場合: user01 P@ssw0rd
True
パスワード不正の場合: user01 password
False例において、get_digest 関数は、入力されたパスワードに対して hashlib の sha256 でハッシュ値を求めるものです。また、is_login 関数はログイン時の認証のための関数で、与えられたパスワードに対してハッシュ化を行い、データベースに登録されているユーザー名に対応するパスワードと一致すれば True を返す関数です。
main 関数では、与えられたパスワードに対してハッシュ値を求めて、DB を想定した辞書にユーザー名と合わせて記録しています。ここが、ユーザー名とパスワードをシステムのデータベースへ登録する部分をイメージしています。
main 関数内でのログイン認証に該当する部分では、ユーザー名とパスワードを is_login 関数へ渡します。パスワードが一致する場合には、True が返却されますが、一方で、パスワードが誤っている場合には、False となっていることが分かります。
このように hashlib によるハッシュ化を使用することで、ハッシュ値を用いた様々制御を実装することが可能です。
まとめ
Python でデータをハッシュ化するための hashlib モジュールについて解説しました。
基本的なハッシュ化の使い方や pdkdf2_hmac による解読耐性のあるハッシュ値を取得について説明しました。また、ログインを想定した時のパスワードのハッシュ化の使用例についても紹介しています。
ハッシュは、一方向関数である特徴から色々な場面で使用されます。ぜひ、hashlib の使い方を覚えてもらい活用していただければと思います。
上記で紹介しているソースコードについては GitHub にて公開しています。参考にしていただければと思います。

