hashlibとは、SHA-256やMD5などのハッシュ値を計算するPythonの標準ライブラリで、hashlib.sha256(バイト列).hexdigest() と書くだけで16進数のハッシュ値が得られます。 ハッシュ値は、データから計算される固定長の値です。同じデータからは必ず同じ値になり、1バイトでも違えばまったく別の値になるので、ファイルが同じかどうかの確認や、ダウンロードしたファイルの検証に使われます。

この記事では、文字列・バイト列・ファイルの3パターンをコピペで動く形で示し、大きなファイル向けのチャンク読みと Python 3.11 で追加された hashlib.file_digest を時間とメモリで比べます。アルゴリズムの早見表、base64 への変換、sha256sum コマンドとの突き合わせ、hmac、パスワード用途での注意まで扱います。掲載した出力は Python 3.11.15(OpenSSL 3.0.13、Linux)で実際に動かした結果です。

hashlibの早見表

やりたいこと コード 結果の型
バイト列の SHA-256 hashlib.sha256(b"hello").hexdigest() str(16進64文字)
文字列の SHA-256 hashlib.sha256("こんにちは".encode("utf-8")).hexdigest() str
MD5 hashlib.md5(b"hello").hexdigest() str(16進32文字)
生のバイト列で受け取る hashlib.sha256(b"hello").digest() bytes(32バイト)
少しずつ足して計算 h = hashlib.sha256(); h.update(b"...") ―
ファイル(3.11以降) hashlib.file_digest(f, "sha256").hexdigest()(f は "rb" で開く) str
ファイル(3.10以前も可) while chunk := f.read(1024 * 1024): h.update(chunk) ―
base64 で出力 base64.b64encode(hashlib.sha256(data).digest()).decode() str(44文字)
名前でアルゴリズムを選ぶ hashlib.new("sha256", b"hello") ―
鍵つきハッシュ(HMAC) hmac.new(key, msg, hashlib.sha256).hexdigest() str
ハッシュ値を比べる hmac.compare_digest(a, b) bool
パスワードから鍵を作る hashlib.scrypt(...) / hashlib.pbkdf2_hmac(...) bytes

渡すのは必ず bytes です。str はそのまま渡せないので .encode("utf-8") で変換します。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Python hashlibの使い方|SHA256・MD5とファイルのハッシュ値」

パターン1:バイト列のハッシュ値を求める

import hashlib

print(hashlib.sha256(b"hello").hexdigest())
print(hashlib.md5(b"hello").hexdigest())
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
5d41402abc4b2a76b9719d911017c592

流れは「ハッシュオブジェクトを作る → 結果を取り出す」の2段階です。取り出し方は2つあります。

メソッド 返すもの 例(SHA-256)
hexdigest() 16進数の文字列(str) 64文字
digest() 生のバイト列(bytes) 32バイト

データを分けて渡すこともできます。update() を何回かに分けて呼んでも、まとめて渡したときと同じ値になります。 これが、後で出てくるファイルのチャンク読みの土台です。

import hashlib

h = hashlib.sha256()
h.update(b"hel")
h.update(b"lo")
print(h.hexdigest() == hashlib.sha256(b"hello").hexdigest())
True

パターン2:文字列のハッシュ値を求める

文字列は encode() でバイト列に変えてから渡します。

import hashlib

text = "こんにちは"
print(hashlib.sha256(text.encode("utf-8")).hexdigest())
125aeadf27b0459b8760c13a3d80912dfa8a81a68261906f60d87f4a0268646c

strをそのまま渡すとTypeErrorになる

import hashlib

hashlib.sha256("hello")
TypeError: Strings must be encoded before hashing

h.update("hello") でも同じメッセージが出ます。直し方は "hello".encode("utf-8") か、リテラルなら b"hello" にすることです。

直す前 直した後
hashlib.sha256("hello") hashlib.sha256("hello".encode("utf-8"))
hashlib.sha256(text) hashlib.sha256(text.encode("utf-8"))
hashlib.sha256(open(path).read()) hashlib.sha256(open(path, "rb").read())

文字コードと改行でハッシュ値は変わる

ハッシュ値はバイト列から計算されるので、同じ文字でも文字コードが違えば別の値になります。「日本語」という3文字で確かめました(先頭16文字のみ表示)。

文字コード SHA-256(先頭16文字)
utf-8 77710aedc74ecfa3
cp932 7fa11a31677814a9
utf-16 594678acc1de24bd

改行の有無でも変わります。

入力 SHA-256
b"hello" 2cf24dba5fb0a30e...
b"hello\n" 5891b5b522d5df08...
b"hello\r\n" cd2eca3535741f27...

ほかのツールや他言語と値が合わないときは、まず文字コードと末尾の改行を確認すると早く解決します。

パターン3:ファイルのハッシュ値を求める

ファイルは必ずバイナリモード("rb")で開きます。書き方は3通りあります。

# filehash.py
import hashlib
import sys
import time
import tracemalloc


def hash_read_all(path):
    """1. 全部読んでから計算(小さいファイル向け)"""
    with open(path, "rb") as f:
        return hashlib.sha256(f.read()).hexdigest()


def hash_chunks(path, chunk_size=1024 * 1024):
    """2. 1MiBずつ読んで update(Python 3.8以降)"""
    h = hashlib.sha256()
    with open(path, "rb") as f:
        while chunk := f.read(chunk_size):
            h.update(chunk)
    return h.hexdigest()


def hash_file_digest(path):
    """3. hashlib.file_digest(Python 3.11以降)"""
    with open(path, "rb") as f:
        return hashlib.file_digest(f, "sha256").hexdigest()


if __name__ == "__main__":
    path = sys.argv[1]
    for func in (hash_read_all, hash_chunks, hash_file_digest):
        tracemalloc.start()
        start = time.perf_counter()
        digest = func(path)
        elapsed = time.perf_counter() - start
        _, peak = tracemalloc.get_traced_memory()
        tracemalloc.stop()
        print(f"{func.__name__:17} {digest[:16]}... {elapsed:.3f}s peak={peak / 1024 / 1024:.1f}MiB")

tracemalloc は Python が確保したメモリの最大値を測るための標準ライブラリで、比較のために入れています。普段使うときは3つの関数だけを取り出してください。

小さいファイルで3つの結果が同じことを確かめる

中身が hello と改行だけの hello.txt(6バイト)で実行します。

printf 'hello\n' > hello.txt
python filehash.py hello.txt
hash_read_all     5891b5b522d5df08... 0.000s peak=0.0MiB
hash_chunks       5891b5b522d5df08... 0.000s peak=1.0MiB
hash_file_digest  5891b5b522d5df08... 0.000s peak=0.3MiB

3つとも同じ値です。

200MBのファイルで時間とメモリを比べる

209,715,200 バイト(200MiB)のファイルを作って実行した結果です(2回目の計測)。

hash_read_all     bf375859eeb4cfaf... 0.262s peak=200.0MiB
hash_chunks       bf375859eeb4cfaf... 0.177s peak=2.0MiB
hash_file_digest  bf375859eeb4cfaf... 0.185s peak=0.3MiB
方法 時間 メモリの最大値 使える版 向いている場面
f.read() で全部読む 0.262秒 200.0MiB(ファイルと同じ大きさ) すべて 数MBまでの小さなファイル
チャンク読み(1MiBずつ) 0.177秒 2.0MiB 3.8以降(:= を使うため) 3.10以前でも動かしたいとき、進み具合を表示したいとき
hashlib.file_digest 0.185秒 0.3MiB 3.11以降 3.11以降なら第一候補

全部読む方法は、ファイルの大きさと同じだけメモリを使います。 チャンク読みと file_digest は、ファイルがどれだけ大きくてもメモリの使用量がほぼ一定でした。時間は計測のたびに少し変わりますが、この環境ではチャンク読みと file_digest のほうが短くなりました(数値は環境によって変わります)。

file_digestの使い方と注意点

hashlib.file_digest(fileobj, digest, /) は Python 3.11 で追加された関数です(公式ドキュメントの hashlib のページで確認)。2つ目の引数には、アルゴリズム名の文字列か、ハッシュオブジェクトを返す関数を渡せます。

import hashlib
import io

with open("hello.txt", "rb") as f:
    print(hashlib.file_digest(f, "sha256").hexdigest())

with open("hello.txt", "rb") as f:
    print(hashlib.file_digest(f, lambda: hashlib.blake2b(digest_size=16)).hexdigest())

# ファイルでなくても、バイナリを読めるオブジェクトなら渡せる
print(hashlib.file_digest(io.BytesIO(b"hello\n"), "md5").hexdigest())
5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03
ea41b4de6c03f13a95758b28dd75693e
b1946ac92492d2347c6235b4d2611184

テキストモードで開いたファイルを渡すと、次のエラーになります。

with open("hello.txt") as f:          # "rb" を付けていない
    hashlib.file_digest(f, "sha256")
ValueError: '<_io.TextIOWrapper name='hello.txt' mode='r' encoding='utf-8'>' is not a file-like object in binary reading mode.

open(path, "rb") に直せば解決します。 テキストモードは改行の変換や文字コードの解釈が入るため、ファイルのハッシュ値には必ずバイナリモードを使います。

Python 3.10以前でも動く書き方

:=(代入式)が使えない 3.7 以前も含めて動かしたい場合は、iter を使った形にします。次のコードは 3.11 で実行して、上と同じ値になることを確かめています。

import hashlib
from functools import partial


def hash_chunks_compat(path, chunk_size=1024 * 1024):
    h = hashlib.sha256()
    with open(path, "rb") as f:
        for chunk in iter(partial(f.read, chunk_size), b""):
            h.update(chunk)
    return h.hexdigest()


print(hash_chunks_compat("hello.txt"))
5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03

アルゴリズム早見表(出力長・安全性・用途)

b"hello" を各アルゴリズムで計算し、出力の長さを実測しました。

アルゴリズム hashlibでの名前 出力長(ビット) digest のバイト数 hexdigest の文字数 安全性の目安 主な用途
MD5 md5 128 16 32 同じ値になる別データを作る方法が知られている 転送時の破損チェック、重複ファイルの検出、既存の仕組みとの互換
SHA-1 sha1 160 20 40 同じ値になる別データの作成例が公表されている Git のオブジェクトIDなど既存の仕組みとの互換
SHA-224 sha224 224 28 56 現在広く使われている 出力を短くしたいとき
SHA-256 sha256 256 32 64 現在広く使われている 配布ファイルの検証、署名、迷ったらこれ
SHA-384 sha384 384 48 96 現在広く使われている より長い出力が指定されているとき
SHA-512 sha512 512 64 128 現在広く使われている 長い出力が必要なとき
SHA3-256 sha3_256 256 32 64 SHA-2 と別の設計の標準 SHA-3 が指定されているとき
SHA3-512 sha3_512 512 64 128 SHA-2 と別の設計の標準 同上
BLAKE2b blake2b 最大512(可変) 64 128 現在広く使われている 高速なハッシュ、鍵つきハッシュ、出力長の指定
BLAKE2s blake2s 最大256(可変) 32 64 現在広く使われている 32ビット環境や小さな機器向け
SHAKE128 / 256 shake_128 / shake_256 任意 任意 任意 SHA-3 系の可変長 長さを自由に決めたいとき(hexdigest(16) のように長さを渡す)

出力長と文字数は実測値です。安全性の欄は一般に知られた評価を短くまとめたもので、今回あらためて検証したものではありません。

選び方の目安は次のとおりです。

目的 選ぶもの
ダウンロードしたファイルが配布元と同じか確かめる 配布元が公開している方式(多くは SHA-256)に合わせる
自分で新しく方式を決める SHA-256
改ざんの検出が目的ではなく、重複ファイルを素早く見つけたい MD5 や BLAKE2 でも用は足りる。迷うなら SHA-256
共有した鍵で「送り主が正しいか」も確かめたい HMAC-SHA256(hmac モジュール)
パスワードを保存する scrypt や pbkdf2_hmac(後述)。sha256 を1回かけるだけでは足りない

MD5とSHA-1はどこまで使えるか

MD5 と SHA-1 は、同じハッシュ値になる別のデータを意図的に作る方法が知られています。 そのため、「誰かが中身をすり替えていないか」を確かめる用途や電子署名には、SHA-256 以降を選びます。

一方で、通信やコピーの途中で偶然ビットが変わっていないかを確かめる用途や、手元のフォルダから同じ内容のファイルを探す用途では、今も広く使われています。「意図的なすり替えを防ぎたいかどうか」が判断の分かれ目です。

セキュリティ目的ではないことを明示する引数もあります。

import hashlib

print(hashlib.md5(b"hello", usedforsecurity=False).hexdigest())
5d41402abc4b2a76b9719d911017c592

usedforsecurity は Python 3.9 で追加されたキーワード専用引数で、既定値は True です(公式ドキュメントで確認)。MD5 の利用が制限された環境でも、False を指定するとセキュリティ以外の目的として計算できる場合があります。

使えるアルゴリズムを調べる

import hashlib

print(sorted(hashlib.algorithms_guaranteed))
print(sorted(hashlib.algorithms_available - hashlib.algorithms_guaranteed))
['blake2b', 'blake2s', 'md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512', 'sha512', 'shake_128', 'shake_256']
['md5-sha1', 'ripemd160', 'sha512_224', 'sha512_256', 'sm3']
属性 意味
hashlib.algorithms_guaranteed どの環境の Python でも使えると決められているもの
hashlib.algorithms_available 今の環境で使えるもの(OpenSSL が提供するものを含む)。この環境では19種類

2行目のように OpenSSL 由来のものは、hashlib.new("名前", データ) で使います。存在しない名前を渡すと次のようになります。

hashlib.new("sha999")
ValueError: unsupported hash type sha999

hexdigestとbase64出力を変換する

API の仕様によっては、ハッシュ値を16進数ではなく base64 で渡すよう求められます。ポイントは、hexdigest() の文字列ではなく digest() のバイト列を base64 にすることです。

import base64
import hashlib

d = hashlib.sha256(b"hello").digest()
print(type(d), len(d))
print(d.hex())                                   # hexdigest() と同じ
print(base64.b64encode(d).decode())              # 標準の base64
print(base64.urlsafe_b64encode(d).decode())      # URL に入れられる形
print(base64.urlsafe_b64encode(d).rstrip(b"=").decode())  # 末尾の = を外した形
<class 'bytes'> 32
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=
LPJNul-wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=
LPJNul-wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ
形式 SHA-256 の長さ 使われる文字
16進数(hexdigest) 64文字 0〜9、a〜f
base64 44文字(末尾に = が1つ) 英数字と + /
URLセーフ base64 44文字(= を外すと43文字) 英数字と - _

16進数とbase64を相互に変換する

import base64
import hashlib

hex_value = hashlib.sha256(b"hello").hexdigest()

# 16進数 → base64
b64_value = base64.b64encode(bytes.fromhex(hex_value)).decode()
print(b64_value)

# base64 → 16進数
print(base64.b64decode(b64_value).hex())
LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

うまくいかない例:16進数の文字列をそのままbase64にする

hex_value = hashlib.sha256(b"hello").hexdigest()
print(base64.b64encode(hex_value.encode()).decode())
MmNmMjRkYmE1ZmIwYTMwZTI2ZTgzYjJhYzViOWUyOWUxYjE2MWU1YzFmYTc0MjVlNzMwNDMzNjI5MzhiOTgyNA==

88文字の別の値になりました。これは「64文字のテキスト」を base64 にした結果です。相手の仕様が「SHA-256 の base64」なら 44文字になるはずなので、長さを見ると取り違えに気づけます。

sha256sum・certutil・Get-FileHashと突き合わせる

Python で計算した値は、OS のコマンドの結果と一致します。hello.txt で確かめました。

Linux・macOS

sha256sum hello.txt
md5sum hello.txt
sha1sum hello.txt
openssl dgst -sha256 hello.txt
openssl dgst -sha256 -binary hello.txt | base64
5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03  hello.txt
b1946ac92492d2347c6235b4d2611184  hello.txt
f572d396fae9206628714fb2ce00f72e94f2258f  hello.txt
SHA2-256(hello.txt)= 5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03
WJG1tSLV3whtD/CxEPvZ0hu0/HFjrzTQgoai6Eb2vgM=

Python の hashlib.file_digest(f, "sha256").hexdigest() の結果 5891b5b5...6be03 と同じ値です。macOS では shasum -a 256 hello.txt が同じ役割を持ちます(macOS では未実行)。

Windows

Windows のコマンドは今回の検証環境(Linux)では未実行です。一般に知られた書き方を載せます。

# コマンドプロンプト・PowerShell のどちらでも
certutil -hashfile hello.txt SHA256

# PowerShell
Get-FileHash hello.txt -Algorithm SHA256
コマンド 対応OS 今回の確認
sha256sum ファイル Linux 実行済み(Python と一致)
openssl dgst -sha256 ファイル OpenSSL のある環境 実行済み(Python と一致)
shasum -a 256 ファイル macOS 未実行
certutil -hashfile ファイル SHA256 Windows 未実行
Get-FileHash ファイル -Algorithm SHA256 Windows(PowerShell) 未実行

Get-FileHash は16進数を大文字で表示することが知られています。大文字と小文字の違いで不一致と判定しないよう、比べる前に .lower() でそろえておくと確実です。

公開されているハッシュ値と照合するコード

import hashlib
import hmac

expected = "5891B5B522D5DF086D0FF0B110FBD9D21BB4FC7163AF34D08286A2E846F6BE03"

with open("hello.txt", "rb") as f:
    actual = hashlib.file_digest(f, "sha256").hexdigest()

if hmac.compare_digest(actual, expected.lower()):
    print("一致しました")
else:
    print("一致しません")
一致しました

同じ内容のファイルを探すコード

import hashlib
from pathlib import Path

seen = {}
for p in sorted(Path(".").glob("*.txt")):
    with p.open("rb") as f:
        digest = hashlib.file_digest(f, "sha256").hexdigest()
    seen.setdefault(digest, []).append(p.name)

for digest, names in seen.items():
    print(digest[:12], names)

hello.txt とそのコピー copy.txt、内容の違う other.txt を置いて実行した結果です。

5891b5b522d5 ['copy.txt', 'hello.txt']
abc6fd595fc0 ['other.txt']

hmacで鍵つきハッシュを作り、compare_digestで比べる

ハッシュ値だけでは「誰が作ったか」は分かりません。送り手と受け手だけが知る鍵を混ぜて計算するのが HMAC で、Webhook の署名検証などで使われます。標準ライブラリの hmac モジュールで計算します。

import hashlib
import hmac

key = b"secret-key"
message = b"order_id=1001&amount=3"

signature = hmac.new(key, message, hashlib.sha256).hexdigest()
print(signature)

# 受け取った署名と、自分で計算した署名を比べる
print(hmac.compare_digest(signature, signature))
print(hmac.compare_digest(signature, "0" * 64))
652b9002c59241e37ab133694621636c9f6feddaba6d0c16ddb2b6cc12fb3b48
True
False
比べ方 特徴
a == b 違う文字が見つかった時点で比較を終えるため、かかる時間から内容を推測される余地がある
hmac.compare_digest(a, b) 内容にかかわらず同じ手順で比較する。署名やトークンの比較に向く

署名・トークン・パスワード由来の値を比べるときは hmac.compare_digest を使う、と覚えておくと安心です。 引数は str 同士か bytes 同士でそろえます。

鍵と sha256(鍵 + メッセージ) のように自分でつなげる方法より、hmac を使うほうが確実です。HMAC は、つなげ方に由来する弱点が出ないように設計された方式だからです。

BLAKE2 は、key 引数だけで鍵つきハッシュを計算できます。

import hashlib

print(hashlib.blake2b(b"hello", digest_size=16).hexdigest())
print(hashlib.blake2b(b"hello", key=b"secret-key", digest_size=16).hexdigest())
46fb7408d4f285228f4af516ea25851b
b1fb20c57a78318a65275be0730592bc

パスワードにはscryptやpbkdf2_hmacを使う理由

SHA-256 は速く計算できることを目指して作られています。この環境で hashlib.sha256(b"correct horse").digest() を100万回繰り返すと、0.511秒で終わりました。ファイルの検証にはうれしい性質ですが、パスワードの保存では、保存した値が外に出たときに、候補を次々に試す総当たりも同じ速さでできてしまいます。

そこで、わざと時間とメモリを使う専用の関数(鍵導出関数)を使います。hashlib には2つ入っています。

import hashlib
import time

salt = bytes.fromhex("00112233445566778899aabbccddeeff")  # 説明用の固定値

start = time.perf_counter()
dk = hashlib.pbkdf2_hmac("sha256", b"correct horse", salt, 600_000)
print("pbkdf2", dk.hex(), round(time.perf_counter() - start, 3))

start = time.perf_counter()
dk = hashlib.scrypt(b"correct horse", salt=salt, n=2**14, r=8, p=1, dklen=32)
print("scrypt", dk.hex(), round(time.perf_counter() - start, 3))
pbkdf2 c86dc9e58e15a00450c9f7d3310c51a16b77142b24de7ac79c735ea118c5cf50 0.265
scrypt f5206d570fcd120bd1f23a8cd186bd87c04ac1db00e9ac1efca589774ae6ecb8 0.056
方法 1回の計算時間(この環境) 100万件の候補を試す時間の目安
sha256 を1回 約0.0000005秒 約0.5秒(実測)
scrypt(n=2**14, r=8, p=1) 0.056秒 約15時間(1回の時間 × 100万の計算値)
pbkdf2_hmac(sha256、60万回) 0.265秒 約3日(同上)

正しい利用者のログインは1回の計算で済むので 0.1〜0.3 秒は気になりませんが、総当たりには大きな差になります。 これが専用の関数を使う理由です。

関数 特徴 主な引数
hashlib.pbkdf2_hmac(hash_name, password, salt, iterations, dklen=None) 反復回数で時間を調整する。Python 3.4 で追加 公式ドキュメントは、2022年時点で SHA-256 なら数十万回の反復を勧めている
hashlib.scrypt(password, *, salt, n, r, p, maxmem=0, dklen=64) 時間に加えてメモリも使わせる。Python 3.6 で追加 n は CPU とメモリのコスト、r はブロックサイズ、p は並列度

シグネチャと推奨の記述は、公式ドキュメントの hashlib のページで確認しました。

ソルトつきで保存し、照合するコード

ソルトは、パスワードごとに付ける乱数です。同じパスワードでも保存される値が毎回変わるので、あらかじめ計算した一覧表との突き合わせを防げます。

import hashlib
import hmac
import os


def hash_password(password: str) -> str:
    salt = os.urandom(16)
    dk = hashlib.scrypt(password.encode("utf-8"), salt=salt, n=2**14, r=8, p=1, dklen=32)
    return f"scrypt$16384$8$1${salt.hex()}${dk.hex()}"


def verify_password(password: str, stored: str) -> bool:
    _, n, r, p, salt_hex, dk_hex = stored.split("$")
    dk = hashlib.scrypt(
        password.encode("utf-8"),
        salt=bytes.fromhex(salt_hex),
        n=int(n), r=int(r), p=int(p), dklen=32,
    )
    return hmac.compare_digest(dk, bytes.fromhex(dk_hex))


stored = hash_password("correct horse")
print(len(stored))
print(verify_password("correct horse", stored), verify_password("wrong", stored))
print(hash_password("correct horse") == stored)
114
True False
False

最後の False は、同じパスワードでもソルトが違うので保存値が変わることを示しています。パラメータ(n, r, p)を一緒に保存しておくと、後から強度を上げるときに以前の値も照合できます。

scrypt の n を大きくしすぎると、既定のメモリ上限に当たります。

hashlib.scrypt(b"p", salt=salt, n=2**20, r=8, p=1)
ValueError: [digital envelope routines] memory limit exceeded

この場合は maxmem 引数で上限を広げるか、n を見直します。

Web アプリケーションのログイン機能では、bcrypt や Argon2 を扱う専用ライブラリを使うことも多くあります。ここでは「標準ライブラリだけで作るなら scrypt か pbkdf2_hmac」と押さえておけば十分です。

つまずきやすい点と直し方

起きること 理由 直し方
TypeError: Strings must be encoded before hashing str を渡している .encode("utf-8") を付ける。ファイルは "rb" で開く
ValueError: ... is not a file-like object in binary reading mode. file_digest にテキストモードのファイルを渡している open(path, "rb") にする
AttributeError: module 'hashlib' has no attribute 'file_digest' Python 3.10 以前 チャンク読みに切り替える(このエラーは 3.11 の環境では未再現)
ほかのツールと値が合わない 文字コード、末尾の改行、大文字と小文字 バイト列をそろえ、.lower() で比べる
base64 の値が相手と合わない hexdigest の文字列を base64 にしている digest() を base64 にする
hexdigest() を2回呼んだら同じ値、update() 後は別の値 ハッシュオブジェクトは足した分をすべて覚えている 別のデータを計算するときは新しいオブジェクトを作る
大きなファイルでメモリが足りなくなる f.read() で全部読んでいる file_digest かチャンク読みにする

よくある質問

Q. hexdigest() と digest() はどちらを使えばよいですか? A. 画面表示やログ、テキストでの受け渡しには hexdigest() が向いています。base64 にしたい、バイナリのまま保存したい、HMAC の比較に使いたい、という場合は digest() を使います。

Q. ハッシュ値から元のデータに戻せますか? A. 戻せません。ハッシュは一方向の計算です。元に戻せる必要があるなら、暗号化という別の技術を使います。base64 は見た目を変えるだけの変換なので、ハッシュとも暗号化とも役割が違います。

Q. Python 組み込みの hash() 関数との違いは何ですか? A. hash() は辞書や集合の内部で使うための値で、文字列に対してはプロセスを起動するたびに結果が変わります。ファイルの検証や保存には、毎回同じ値になる hashlib を使います。

Q. チャンクの大きさはいくつにすればよいですか? A. 64KiB〜1MiB 程度がよく使われます。この記事では 1MiB で、200MiB のファイルを 0.177秒・最大 2.0MiB のメモリで計算できました。3.11 以降なら file_digest に任せると、大きさを考えずに済みます。

まとめ

  • hashlib に渡すのは bytes。文字列は .encode("utf-8")、ファイルは "rb" で開く
  • ファイルは 3.11 以降なら hashlib.file_digest、それ以前はチャンク読み。200MiB のファイルでもメモリは数MiB以内に収まった
  • 迷ったら SHA-256。MD5 と SHA-1 は破損チェックや重複検出、既存の仕組みとの互換に使う
  • base64 にするのは digest() のバイト列。SHA-256 なら 44文字になる
  • sha256sum や openssl dgst の結果と一致することを実行して確かめた(Windows のコマンドは未実行)
  • 署名やトークンの比較は hmac.compare_digest、鍵つきハッシュは hmac モジュール
  • パスワードには scrypt や pbkdf2_hmac。わざと時間をかけることで総当たりを難しくする

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Python hashlibの使い方|SHA256・MD5とファイルのハッシュ値」

株式会社RJCでは、Pythonのハッシュ計算の使い方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?