ToolDeck

Python HMAC hmac.new() SHA-256

·DevOps Engineer & Python Automation Specialist·レビュー担当Maria Santos·公開日

無料の HMAC Generator をブラウザで直接使用 — インストール不要。

HMAC Generator をオンラインで試す →

すべてのWebhookコールバック、すべての署名付きAPIリクエスト、すべてのStripeやGitHubのイベント通知は、ペイロードが改ざんされていないことを証明するためにHMAC署名を使用します。Pythonの hmac モジュールはPythonのHMAC-SHA256を1回の関数呼び出しで処理します: hmac.new(key, msg, hashlib.sha256)。 pip installも、C拡張も、サードパーティの依存関係も不要です。コードを書かずにすばやく署名を確認したい場合は、 オンラインHMACジェネレーター で即座に結果が得られます。 このガイドでは hmac.new() hmac.digest() hmac.compare_digest()、 Base64エンコード、Webhook検証、APIリクエスト署名、SHA-1からSHA-512までのすべてのハッシュアルゴリズムを解説します。すべてのサンプルはPython 3.7以降を対象としています。

  • hmac.new(key, msg, hashlib.sha256)が標準的なエントリーポイント — keyとmsgはbytesでなければならず、digestmodはPython 3.4以降で必須。
  • hmac.digest(key, msg, "sha256")はPython 3.7で追加された高速なワンショット代替手段 — 中間オブジェクトなしで生バイト列を返す。
  • 署名の検証には必ずhmac.compare_digest()を使用してタイミング攻撃を防ぐ — HMAC比較に==を使用しない。
  • HTTPヘッダーとWebhook署名向けに生の.digest()出力をBase64エンコードする:base64.b64encode(h.digest())。
  • hmacモジュールはhashlibのすべてのアルゴリズムを受け付ける:sha1、sha256、sha384、sha512、md5、blake2b。

HMACとは何か?

HMAC(Hash-based Message Authentication Code)は RFC 2104 で定義された構造であり、秘密鍵とハッシュ関数を組み合わせて固定サイズの認証タグを生成します。単純なハッシュ(誰でも計算できる)とは異なり、HMACは秘密鍵の知識を必要とします。これにより、メッセージの完全性と真正性の両方を検証するために使用できます。メッセージまたは鍵の1バイトでも変化すれば、出力は完全に異なります。この構造は、鍵を2つの異なるパディング定数(ipadとopad)とXORし、2回のハッシュ操作でメッセージを包み込むことで機能します。Pythonの hmac モジュールはこのRFCを直接実装しています。

Before · Python
After · Python
# 単純なSHA-256ハッシュ — 秘密鍵なし、誰でも計算可能
hashlib.sha256(b"payment:9950:USD").hexdigest()
# "7a3b1c..."  (決定論的、公開情報)
# HMAC-SHA256 — 生成には秘密鍵が必要
hmac.new(b"api_secret", b"payment:9950:USD", hashlib.sha256).hexdigest()
# "e4f2a8..."  (鍵の保有者のみ計算可能)

hmac.new() — 標準ライブラリのエントリーポイント

hmac モジュールはPython標準ライブラリの一部です。2つのインポートだけで準備完了です: import hmac, hashlib。 主な3つの関数は hmac.new() (HMACオブジェクトを生成)、 hmac.digest() (ワンショット、Python 3.7以降)、および hmac.compare_digest() (定数時間比較)です。pip installは不要です。

hmac.new(key, msg, digestmod) は3つの引数を取ります。 key msg はともにバイト列オブジェクト( bytes bytearray、または memoryview)でなければなりません。digestmod 引数はPython 3.4以降で必須であり、任意の hashlib コンストラクタ( hashlib.sha256 など)または "sha256" のような文字列名を受け付けます。

Python 3.7+ — 最小構成のHMAC-SHA256の例
import hmac
import hashlib

key = b"webhook_signing_key_2026"
message = b'{"event":"invoice.paid","invoice_id":"inv_8f3a","amount":19900}'

# HMACオブジェクトを生成して16進数署名を取得
signature = hmac.new(key, message, hashlib.sha256).hexdigest()
print(signature)
# "b4e74f6c9a1d3e5f8b2a7c0d4e6f1a3b5c7d9e0f2a4b6c8d0e1f3a5b7c9d0e2f"

HMACオブジェクトは2つの出力メソッドを持ちます。 .digest() は生バイト列を返します(SHA-256では32バイト、SHA-512では64バイト)。 .hexdigest() は小文字の16進数文字列を返します。この16進数文字列は通常のPythonの str であり、デコード手順は不要です。

Python 3.7+ — .digest() と .hexdigest() の比較
import hmac
import hashlib

key = b"service_auth_key"
msg = b"GET /api/v2/orders 2026-03-28T14:30:00Z"

h = hmac.new(key, msg, hashlib.sha256)

raw_bytes = h.digest()
print(type(raw_bytes), len(raw_bytes))
# <class 'bytes'> 32

hex_string = h.hexdigest()
print(type(hex_string), len(hex_string))
# <class 'str'> 64

# 同じデータを表している — 16進数はバイト列の文字列エンコードに過ぎない
assert raw_bytes.hex() == hex_string

キーまたはメッセージがPython文字列の場合は、 hmac.new() に渡す前に .encode() を呼び出してbytesに変換してください。これは初めて使うほぼすべての人がつまずく点です — Python 3の文字列はUnicodeであり、bytesではありません。hmacモジュールは文字列の入力を拒否します。

Python 3.7+ — 文字列をbytesにエンコードする
import hmac
import hashlib

# 文字列のキーとメッセージ — .encode()でUTF-8バイト列に変換
api_key = "sk_live_9f3a2b7c4d8e"
request_body = '{"customer_id":"cust_4421","plan":"enterprise"}'

signature = hmac.new(
    api_key.encode(),
    request_body.encode(),
    hashlib.sha256
).hexdigest()

print(signature)
# "3a9f1b..."  — 一貫した16進数文字列の出力
注意:digestmod パラメータはPython 3.4以降デフォルト値がありません。hmac.new(key, msg) のように指定せずに呼び出すと TypeError が発生します。3.4以前はデフォルトがMD5でしたが、 Pythonメンテナーは安全な明示的選択を強制するためにデフォルトを削除しました。

HMAC-SHA256 Base64、SHA-1、SHA-512、MD5

hmac.new() 関数は hashlib で利用可能な任意のハッシュアルゴリズムで動作します。ほとんどのWebhookプロバイダーとAPIゲートウェイはHMAC-SHA256を使用しますが、OAuth 1.0aではSHA-1、必要とするプロトコルではSHA-512、更新されていないレガシーシステムではMD5に遭遇することがあります。

Base64出力を使ったHMAC-SHA256

多くのWebhookプロバイダーはHTTPヘッダーにBase64エンコードされた文字列として署名を送信します。 同じ形式を生成するには、生の .digest() バイト列を base64.b64encode() に渡します。

Python 3.7+ — HMAC-SHA256 Base64エンコード
import hmac
import hashlib
import base64

key = b"whsec_MbkP7x9yFqHGn3tRdWz5"
payload = b'{"id":"evt_1Nq","type":"charge.succeeded","data":{"amount":4200}}'

# 生ダイジェスト → Base64(Authorizationヘッダーやwebhook署名でよく使われる)
raw_digest = hmac.new(key, payload, hashlib.sha256).digest()
b64_signature = base64.b64encode(raw_digest).decode("ascii")

print(b64_signature)
# "dGhpcyBpcyBhIHNhbXBsZSBzaWduYXR1cmU="

# X-Signatureヘッダーと比較する値
header_value = f"sha256={b64_signature}"
print(header_value)
# "sha256=dGhpcyBpcyBhIHNhbXBsZSBzaWduYXR1cmU="

HMAC-SHA1 — レガシープロトコルの互換性

SHA-1は新しい設計では弱いと見なされますが、HMAC-SHA1はOAuth 1.0aや一部の古いWebhook実装でまだ必要とされます。コードは同一です — アルゴリズムを入れ替えるだけです。

Python 3.7+ — HMAC-SHA1
import hmac
import hashlib

consumer_secret = b"oauth_consumer_secret_2026"
token_secret = b"oauth_token_secret_2026"

# OAuth 1.0aはconsumer_secret&token_secretを署名鍵として使用
signing_key = consumer_secret + b"&" + token_secret
base_string = b"GET&https%3A%2F%2Fapi.service.com%2Fv1%2Forders&oauth_nonce%3D7f3a91bc"

sig = hmac.new(signing_key, base_string, hashlib.sha1).digest()

import base64
oauth_signature = base64.b64encode(sig).decode("ascii")
print(oauth_signature)
# "Tza3R9sE..."  — Authorizationヘッダー向けにURLエンコードが必要

HMAC-SHA512 — より長い出力

Python 3.7+ — HMAC-SHA512
import hmac
import hashlib

key = b"high_security_signing_key_64_bytes_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
msg = b'{"transfer_id":"xfr_9c2e","amount":500000,"currency":"EUR"}'

h = hmac.new(key, msg, hashlib.sha512)

print(len(h.digest()))   # 64バイト (512ビット)
print(len(h.hexdigest()))  # 128文字の16進数
print(h.hexdigest()[:40] + "...")
# "8e3a1f9b2c4d6e7f0a1b3c5d7e9f0a2b4c6d8e0f..."

HMAC-MD5 — レガシー専用

Python 3.7+ — HMAC-MD5
import hmac
import hashlib

# MD5は暗号学的に破られている — レガシープロトコルの互換性のためのみに使用
key = b"legacy_api_key"
msg = b"action=charge&amount=1500&merchant=store_42"

sig = hmac.new(key, msg, hashlib.md5).hexdigest()
print(sig)  # 32文字の16進数文字列
# "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
警告:HMAC-MD5は移行できないシステムとの後方互換性のためのみ許容されます。 新しいプロジェクトでは、最低限HMAC-SHA256を使用してください。MD5にはHMACモードでは直接的には悪用しにくいものの、 既知の衝突脆弱性があるため、デフォルト選択としては不適切です。

hmac.new() パラメータリファレンス

コンストラクタのシグネチャは hmac.new(key, msg=None, digestmod) です。モジュールの3つの主要関数はすべて、鍵とアルゴリズムの引数に同じパターンを使用します。

hmac.new() コンストラクタ

パラメータ
デフォルト
説明
key
bytes | bytearray
(必須)
秘密鍵 — strではなくbytesでなければならない
msg
bytes | None
None
ハッシュする初期メッセージ。.update()でデータを追加可能
digestmod
str | callable
(必須)
ハッシュアルゴリズム — hashlib.sha256または文字列"sha256"を指定

hmac.digest() ワンショット (Python 3.7+)

パラメータ
説明
key
bytes
秘密鍵
msg
bytes
認証するメッセージ
digest
str | callable
ハッシュアルゴリズム — hmac.new()のdigestmodと同じ

digestmod パラメータはcallable( hashlib.sha256 など)または文字列名( "sha256" など)のどちらも受け付けます。callable形式が推奨されます — インポート時に検証されるため、文字列形式でのタイポは実行時にのみ失敗します。

hmac.digest() — 高速なワンショットHMAC(Python 3.7+)

Python 3.7ではモジュールレベルの関数として hmac.digest(key, msg, digest) が追加されました。中間HMACオブジェクトを生成せずに1回の呼び出しでHMACを計算します。戻り値は生バイト列(オブジェクトの .digest() を呼び出した結果と同等)です。この関数はCPythonで最適化されたC実装を使用し、オブジェクト生成のオーバーヘッドを回避するため、処理が集中するループでは明確に高速です。

Python 3.7+ — hmac.digest() ワンショット
import hmac
import hashlib

key = b"batch_signing_key_2026"
messages = [
    b'{"order_id":"ord_001","total":4500}',
    b'{"order_id":"ord_002","total":8900}',
    b'{"order_id":"ord_003","total":2200}',
]

# ワンショットダイジェスト — 中間HMACオブジェクトなし
signatures = [hmac.digest(key, msg, hashlib.sha256) for msg in messages]

# 表示用に16進数に変換
for msg, sig in zip(messages, signatures):
    print(f"{msg[:30]}... -> {sig.hex()[:24]}...")

制限として、 hmac.digest() は生バイト列のみを返します。16進数文字列が直接必要な場合は、依然として hmac.new() .hexdigest() メソッドを使用するか、バイト列の結果に .hex() をチェーンする必要があります。

注意:hmac.digest() は逐次的な .update() 呼び出しをサポートしません。大きなファイルをチャンク単位で読み込んでHMACを計算する必要がある場合は、 hmac.new() を使用してループ内で .update(chunk) を呼び出してください。

WebhookとAPIレスポンスのHMAC署名を検証する

PythonにおけるHMACの最も一般的な用途はWebhook署名の検証です。 主要なプロバイダー(Stripe、GitHub、Shopify、Twilio)はすべてHMAC-SHA256でペイロードに署名し、 ヘッダーで署名を送信します。パターンは常に同じです:生のリクエストボディに対してHMACを再計算し、hmac.compare_digest() で比較します。

Webhook署名の検証

Python 3.7+ — webhook HMAC検証(Flask)
import hmac
import hashlib
from flask import Flask, request, abort

app = Flask(__name__)
WEBHOOK_SECRET = b"whsec_MbkP7x9yFqHGn3tRdWz5"

@app.route("/webhooks/payments", methods=["POST"])
def handle_payment_webhook():
    # 生のボディを取得 — 署名されたものと完全に一致させる必要がある
    raw_body = request.get_data()

    # ヘッダーから署名を取得
    received_sig = request.headers.get("X-Signature-256", "")

    # 生のボディに対してHMACを再計算
    expected_sig = hmac.new(WEBHOOK_SECRET, raw_body, hashlib.sha256).hexdigest()

    # 定数時間比較 — タイミング攻撃を防ぐ
    if not hmac.compare_digest(f"sha256={expected_sig}", received_sig):
        abort(403, "Invalid signature")

    # 署名検証済み — イベントを処理
    event = request.get_json()
    print(f"Verified event: {event['type']} for {event['data']['amount']}")
    return "", 200

hmac.compare_digest() 関数は2つの文字列またはバイト列を定数時間で比較します。通常の == 比較は最初の一致しないバイトで短絡評価します。攻撃者は多数のリクエストにわたってレスポンス時間を計測し、期待される署名をバイト単位で徐々に再構築できます。定数時間比較によりこのサイドチャネルを排除します。

GitHub Webhook検証

GitHubのWebhook形式はこのパターン全体を示しています。 X-Hub-Signature-256 ヘッダーに sha256= に続いてリポジトリ設定で構成したWebhookシークレットで署名された生リクエストボディのHMAC-SHA256の16進数エンコード値が含まれます。一般的なWebhook検証との主な違いは、比較前に sha256= プレフィックスを取り除く必要があること、そして生バイト列のリクエストボディを読み取る必要があることです — JSONを先にパースするとバイト表現が変わり検証が失敗します。

Python 3.7+ — GitHub X-Hub-Signature-256検証
import hmac
import hashlib
from flask import Flask, request, abort

app = Flask(__name__)
GITHUB_WEBHOOK_SECRET = b"your_github_webhook_secret"

@app.route("/webhooks/github", methods=["POST"])
def handle_github_webhook():
    # GitHubが送信するヘッダー: X-Hub-Signature-256: sha256=<hex_digest>
    sig_header = request.headers.get("X-Hub-Signature-256", "")

    if not sig_header.startswith("sha256="):
        abort(403, "Missing or malformed signature header")

    received_hex = sig_header[len("sha256="):]
    raw_body = request.get_data()  # 生バイト列 — この前にJSONをパースしない

    expected_hex = hmac.new(
        GITHUB_WEBHOOK_SECRET, raw_body, hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(expected_hex, received_hex):
        abort(403, "Signature mismatch — payload may have been tampered with")

    event_type = request.headers.get("X-GitHub-Event", "unknown")
    payload = request.get_json()
    print(f"Verified GitHub {event_type} event: {payload.get('action', '')}")
    return "", 200

同じパターンはShopify(X-Shopify-Hmac-SHA256) とTwilio(X-Twilio-Signature) にも適用されます。ヘッダー名と署名が16進数かBase64かという点だけが異なります。エンコード形式を確認するために常にプロバイダーのドキュメントを参照してください — 16進数とBase64の混同が署名不一致エラーの最も一般的な原因です。

HMACによるAPIリクエスト認証

一部のAPIはクライアントが各リクエストにHMACで署名することを要求します。署名対象の文字列には通常、HTTPメソッド、パス、タイムスタンプ、リクエストボディが含まれます。以下はサービス間の内部認証に使用するパターンです。

Python 3.7+ — HMAC-SHA256によるAPIリクエスト署名
import hmac
import hashlib
import time
import json

def sign_request(secret: bytes, method: str, path: str, body: str) -> dict:
    """APIリクエスト向けにHMAC-SHA256署名を生成する。"""
    timestamp = str(int(time.time()))

    # 署名対象の文字列を構築 — メソッド + パス + タイムスタンプ + ボディ
    signing_string = f"{method}\n{path}\n{timestamp}\n{body}"

    signature = hmac.new(
        secret,
        signing_string.encode(),
        hashlib.sha256
    ).hexdigest()

    return {
        "X-Timestamp": timestamp,
        "X-Signature": signature,
    }

# 使用例
api_secret = b"sk_hmac_9f3a2b7c4d8e1a6f"
body = json.dumps({"customer_id": "cust_4421", "action": "suspend_account"})

headers = sign_request(api_secret, "POST", "/api/v2/customers/actions", body)
print(headers)
# {"X-Timestamp": "1711612200", "X-Signature": "a3f1b9c0..."}

requestsライブラリを使ったHTTPリクエストへの署名

Python 3.7+ — requestsライブラリを使ったHMAC署名付きリクエスト
import hmac
import hashlib
import time
import json
import requests

API_SECRET = b"sk_hmac_9f3a2b7c4d8e1a6f"
BASE_URL = "https://api.billing-service.internal"

def make_signed_request(method: str, path: str, payload: dict) -> requests.Response:
    body = json.dumps(payload, separators=(",", ":"))  # コンパクトJSON、決定論的
    timestamp = str(int(time.time()))

    signing_string = f"{method}\n{path}\n{timestamp}\n{body}"
    signature = hmac.new(API_SECRET, signing_string.encode(), hashlib.sha256).hexdigest()

    headers = {
        "Content-Type": "application/json",
        "X-Timestamp": timestamp,
        "X-Signature": f"hmac-sha256={signature}",
    }

    try:
        return requests.request(method, f"{BASE_URL}{path}", data=body, headers=headers)
    except requests.RequestException as e:
        raise RuntimeError(f"Signed request failed: {e}") from e

# 署名付きPOSTリクエストを送信
resp = make_signed_request("POST", "/api/v2/invoices", {
    "customer_id": "cust_4421",
    "line_items": [
        {"description": "Pro plan - March 2026", "amount": 4900},
        {"description": "Extra seats (3)", "amount": 2100},
    ],
})
print(resp.status_code, resp.json())

補足:ボディを署名用にシリアライズする際は separators=(",", ":") を使用してください。デフォルトの json.dumps() は区切り文字の後にスペースを追加するため、サーバーが異なる形式でシリアライズした場合にバイト表現が変わり署名検証が失敗します。コンパクトJSONにより正規化された形式が得られます。

コマンドラインからのHMAC生成

スクリプトを書かずにHMACを計算したい場合があります。Pythonの -c フラグと openssl はどちらもターミナルから処理できます。

bash — PythonのワンライナーでHMAC-SHA256
python3 -c "
import hmac, hashlib
print(hmac.new(b'my_secret', b'message_to_sign', hashlib.sha256).hexdigest())
"
# 出力: 64文字の16進数文字列
bash — openssl経由のHMAC-SHA256(比較用)
echo -n "message_to_sign" | openssl dgst -sha256 -hmac "my_secret"
# SHA2-256(stdin)= 7d11...

# ファイルをopenssl HMACに通す
openssl dgst -sha256 -hmac "my_secret" < payload.json
bash — 環境変数のキーからHMACを生成
# シェル履歴への露出を防ぐためキーを環境変数に保存
export HMAC_KEY="sk_live_9f3a2b"
echo -n '{"event":"test"}' | python3 -c "
import hmac, hashlib, sys, os
key = os.environ['HMAC_KEY'].encode()
msg = sys.stdin.buffer.read()
print(hmac.new(key, msg, hashlib.sha256).hexdigest())
"
注意:echo -n フラグは必須です — これがないとechoがメッセージに改行文字を追加し、HMACの出力が変わります。ターミナルからデバッグする際の署名不一致の最も一般的な原因です。

高性能な代替手段 — cryptographyライブラリ

ほとんどのアプリケーションでは、標準の hmac モジュールで十分な速度があります。TLSや証明書処理のために cryptography ライブラリをすでに使用している場合、このライブラリもOpenSSLを使ったHMACを提供します。stdlibとの主な実用的な違いは、以下で説明する例外ベースの .verify() API です — 不一致の場合にブール値を返す代わりに例外を送出するため、検証失敗をうっかり無視しにくくなっています。

bash — cryptographyのインストール
pip install cryptography
Python 3.7+ — cryptographyライブラリを使ったHMAC
from cryptography.hazmat.primitives import hashes, hmac as crypto_hmac

key = b"webhook_signing_key_2026"
message = b'{"event":"subscription.renewed","plan":"enterprise"}'

h = crypto_hmac.HMAC(key, hashes.SHA256())
h.update(message)
signature = h.finalize()  # 生バイト列

print(signature.hex())
# "9c4e2a..."

# 検証モード — 不一致の場合はInvalidSignatureを送出
h_verify = crypto_hmac.HMAC(key, hashes.SHA256())
h_verify.update(message)
h_verify.verify(signature)  # 誤りの場合はcryptography.exceptions.InvalidSignatureを送出

cryptography ライブラリの .verify() メソッドは特に便利です:不一致の場合にブール値を返す代わりに例外を送出します。これにより、検証失敗をうっかり無視することが難しくなります。標準ライブラリの hmac.compare_digest() True/ False を返すため、開発者が戻り値の確認を忘れた場合に暗黙的に無視される可能性があります。

ターミナル出力のシンタックスハイライト

ターミナルでHMAC署名をデバッグしていて色付き出力が欲しい場合は、 rich が便利です。

bash — richのインストール
pip install rich
Python 3.7+ — richを使った色付きHMAC出力
import hmac
import hashlib
from rich.console import Console
from rich.table import Table

console = Console()

key = b"debug_signing_key"
messages = {
    "/api/v2/orders": b'{"status":"active"}',
    "/api/v2/invoices": b'{"status":"pending"}',
    "/api/v2/customers": b'{"status":"verified"}',
}

table = Table(title="HMAC-SHA256 Signatures")
table.add_column("Endpoint", style="cyan")
table.add_column("Signature (first 32 chars)", style="green")

for endpoint, body in messages.items():
    sig = hmac.new(key, body, hashlib.sha256).hexdigest()
    table.add_row(endpoint, sig[:32] + "...")

console.print(table)
注意:richはターミナル表示専用です。HMAC署名をファイル、HTTPヘッダー、またはログ集約システムに書き込む際は使用しないでください — ANSIエスケープコードが出力を破損します。

大きなファイルの処理 — 逐次HMAC

50 MB程度を超えるファイルでは、HMACを計算するためだけにすべてをメモリに読み込むのは無駄です。HMACオブジェクトの .update() メソッドを使えばデータをチャンク単位で投入できます。これによりファイルサイズにかかわらずメモリ使用量を一定に保てます。

Python 3.7+ — 大きなファイルをチャンク単位でHMACする
import hmac
import hashlib

def hmac_file(key: bytes, filepath: str, chunk_size: int = 8192) -> str:
    """ファイル全体をメモリに読み込まずにHMAC-SHA256を計算する。"""
    h = hmac.new(key, digestmod=hashlib.sha256)

    try:
        with open(filepath, "rb") as f:
            while True:
                chunk = f.read(chunk_size)
                if not chunk:
                    break
                h.update(chunk)
    except OSError as e:
        raise OSError(f"Cannot read file '{filepath}': {e}") from e

    return h.hexdigest()

# 2GBのデータベースエクスポートに署名
signing_key = b"backup_integrity_key_2026"
signature = hmac_file(signing_key, "/var/backups/db-export-2026-03.sql.gz")
print(f"HMAC-SHA256: {signature}")
Python 3.7+ — ダウンロードしたファイルのHMAC署名を検証する
import hmac
import hashlib

def verify_file_hmac(key: bytes, filepath: str, expected_hex: str) -> bool:
    """ファイルのHMAC-SHA256署名を検証する。"""
    h = hmac.new(key, digestmod=hashlib.sha256)

    with open(filepath, "rb") as f:
        for chunk in iter(lambda: f.read(65536), b""):
            h.update(chunk)

    return hmac.compare_digest(h.hexdigest(), expected_hex)

# ダウンロードした成果物を検証
is_valid = verify_file_hmac(
    key=b"release_signing_key",
    filepath="/tmp/release-v3.2.0.tar.gz",
    expected_hex="8e3a1f9b2c4d6e7f0a1b3c5d7e9f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f",
)
print(f"File integrity: {'valid' if is_valid else 'CORRUPTED'}")
注意:ファイルが50〜100 MBを超える場合、またはWebサーバーでアップロードを処理しリクエストごとのメモリが重要な場合は、チャンク単位のHMACに切り替えてください。 .update() アプローチはファイルサイズにかかわらず固定の chunk_size 分のメモリのみを使用します。デフォルトは64KBのチャンクサイズを推奨します — システムコールのオーバーヘッドを十分に吸収しつつ、ほとんどのハードウェアのL2キャッシュ内に収まるサイズです。

PythonでセキュアなHMACキーを生成する

弱いまたは予測可能なキーはHMAC構造全体を損ないます。 secrets モジュール(Python 3.6以降)は暗号強度の高いランダムなバイト列を提供します。 HMAC-SHA256には32バイトのキーを使用します。HMAC-SHA512には64バイトを使用します。これらはそれぞれのハッシュアルゴリズムの内部ブロックサイズに一致し、RFC 2104による最適なキー長です。

Python 3.7+ — secretsを使ったHMACキーの生成
import secrets

# ハッシュアルゴリズムのブロックサイズに合わせたキーを生成
sha256_key = secrets.token_bytes(32)   # 256ビット — HMAC-SHA256用
sha512_key = secrets.token_bytes(64)   # 512ビット — HMAC-SHA512用

# 16進数表現 — 設定ファイルや環境変数に安全
print(f"HMAC-SHA256 key: {sha256_key.hex()}")
# 例: "a3f1b9c04e7d2f8a1b3c5d7e9f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f"

print(f"HMAC-SHA512 key: {sha512_key.hex()}")
# 128文字の16進数文字列

# URLセーフBase64 — コンパクトでHTTPヘッダーに安全
import base64
b64_key = base64.urlsafe_b64encode(sha256_key).decode("ascii")
print(f"Base64 key: {b64_key}")
# 例: "o_G5wE59L4obPF1-nwortG2ODwobPExdXnqLnA0dLi8="
警告:HMACキーに random モジュールの random.random() random.randbytes() を使用しないでください。デフォルトの random モジュールはメルセンヌツイスタPRNGを使用しており、 624個の出力を観察すると予測可能になります。セキュリティ上重要な乱数生成には必ず secrets.token_bytes() を使用してください。

キー長とRFC 2104の要件

RFC 2104はHMACキーが任意の長さでよいと定めていますが、少なくともLバイト(Lは基盤となるハッシュ関数の出力長)を推奨しています。HMAC-SHA256の場合、それは32バイト(256ビット)です。Lビットより短いキーはセキュリティマージンを比例して減少させます。ハッシュのブロックサイズ(SHA-256では64バイト、SHA-512では128バイト)より長いキーは最初にブロックサイズまでハッシュされるため、ブロックサイズを超えるキーを使用しても利点はありません。HMAC-SHA256には32バイト、HMAC-SHA512には64バイトを使用してください。

セキュアなキー保存とローテーション

HMACキーをソースコードにハードコードしないでください。本番環境での標準的なアプローチは、起動時に環境変数からキーを読み込むことです: os.environ["HMAC_SECRET"].encode()。 より高い保証が必要な環境では、AWS Secrets Manager、HashiCorp Vault、またはGCP Secret Managerなどのシークレット管理システムにキーを保存し、実行時に取得します。これらのシステムは監査ログ、アクセス制御、コードのデプロイを必要としない自動ローテーションを提供します。

最初からキーローテーションを計画してください。キーがローテーションされると、処理中のリクエストが古いキーで署名されており、新しいキーに対して検証が失敗するウィンドウが生じます。標準的な対策は短い重複期間です:短時間(数分から数時間)新旧両方のキーからの署名を受け入れ、その後古いキーを廃棄します。キーが危殆化した場合 — ログへの露出、gitコミットへの漏洩、インシデントでの開示 — 直ちにローテーションし、危殆化したキーで生成されたすべての署名を信頼できないものとして扱います。キャッシュされた検証結果を再検証し、鍵変更を下流の消費者に通知してください。

hmac.new()でbytearrayとmemoryviewを使用する

hmac.new() 関数はkeyとmsgパラメータの両方で任意のバイト列オブジェクトを受け付けます。つまり、コピーや変換なしに bytes bytearray、または memoryview を直接渡せます。これが最も重要なのは2つのシナリオです: socket.recv_into() が事前に確保した bytearray バッファにデータを書き込むネットワークプロトコル実装と、中間コピーを排除することでGCの負荷を軽減する高スループットシステムです。 memoryview スライスはゼロコピーです:新しいメモリを割り当てずに元のバッファへのウィンドウを公開します。1秒間に数万件のメッセージを処理する場合、こうした割り当ての排除がレイテンシとスループットで測定可能な差をもたらします。

Python 3.7+ — HMACでbytearrayとmemoryviewを使用する
import hmac
import hashlib

# bytearray — バイナリプロトコルバッファに便利な可変バイト列
key = bytearray(b"protocol_signing_key")
frame = bytearray(b'\x01\x02\x03\x04payload_data_here')

sig = hmac.new(key, frame, hashlib.sha256).hexdigest()
print(f"Frame signature: {sig[:32]}...")

# memoryview — より大きなバッファのゼロコピースライス
large_buffer = bytearray(4096)
large_buffer[:20] = b"sensor_reading_12345"

# コピーなしで最初の20バイトのみHMACする
view = memoryview(large_buffer)[:20]
sig = hmac.new(key, view, hashlib.sha256).hexdigest()
print(f"Sensor signature: {sig[:32]}...")

よくある間違い

最初の2つの間違いはWebhookハンドラーに関わるほぼすべてのコードレビューで目にします。時間的なプレッシャーの下で簡単に発生し、何に注意すべきかを知らないと発見しにくいです。

hmac.compare_digest()の代わりに==でHMAC署名を比較する

問題: ==演算子は最初のバイト不一致で短絡評価し、攻撃者が期待される署名を段階的に再構築できるタイミング情報を漏洩します。

解決策: 署名の比較には常にhmac.compare_digest()を使用してください — 不一致が発生した位置にかかわらず定数時間で実行されます。

Before · Python
After · Python
received_sig = request.headers["X-Signature"]
expected_sig = hmac.new(key, body, hashlib.sha256).hexdigest()

if received_sig == expected_sig:  # タイミング攻撃に対して脆弱
    process_webhook(body)
received_sig = request.headers["X-Signature"]
expected_sig = hmac.new(key, body, hashlib.sha256).hexdigest()

if hmac.compare_digest(received_sig, expected_sig):  # 定数時間
    process_webhook(body)
hmac.new()にbytesの代わりに文字列を渡す

問題: hmac.new()はバイト列オブジェクトを要求します。Python strを渡すとTypeError: "key: expected bytes or bytearray, but got 'str'"が発生します。

解決策: hmac.new()に渡す前に文字列のキーとメッセージに.encode()を呼び出してください。

Before · Python
After · Python
key = "my_api_secret"  # strであり、bytesではない
msg = '{"event":"test"}'  # strであり、bytesではない
sig = hmac.new(key, msg, hashlib.sha256)  # TypeError!
key = "my_api_secret"
msg = '{"event":"test"}'
sig = hmac.new(key.encode(), msg.encode(), hashlib.sha256)
digestmodの省略(Python 3.4以降)

問題: 3番目の引数なしでhmac.new(key, msg)を呼び出すとTypeErrorが発生します。Python 3.4以前はデフォルトでMD5でしたが、セキュリティ上の理由でデフォルトが削除されました。

解決策: アルゴリズムを常に明示的に指定してください:hashlib.sha256、hashlib.sha512、またはプロトコルが要求するもの。

Before · Python
After · Python
# digestmodが省略されている — Python 3.4以降でTypeError
sig = hmac.new(key, msg).hexdigest()
# 常にハッシュアルゴリズムを指定する
sig = hmac.new(key, msg, hashlib.sha256).hexdigest()
プロバイダーがBase64を期待しているのに.hexdigest()を使用する

問題: 多くのWebhookプロバイダー(Stripe、Shopify)は16進数ではなくBase64エンコードされた署名を送信します。16進数文字列をBase64値と比較すると常に失敗し、すべてのWebhookが拒否されます。

解決策: 署名形式についてプロバイダーのドキュメントを確認してください。Base64を使用している場合は、.hexdigest()文字列ではなく生の.digest()バイト列をエンコードしてください。

Before · Python
After · Python
# プロバイダーはBase64を送信するが、16進数を計算している — 決して一致しない
expected = hmac.new(key, body, hashlib.sha256).hexdigest()
# "a3f1b9c0..."  vs  "o/G5wE59..."  — 常に不一致
import base64
# プロバイダーの形式に合わせる: 生バイト列 → Base64
raw = hmac.new(key, body, hashlib.sha256).digest()
expected = base64.b64encode(raw).decode("ascii")
# "o/G5wE59..."  — ヘッダーと一致

stdlib hmac vs cryptography — 簡易比較

メソッド
アルゴリズム
出力
ストリーミング
カスタム型
インストール
hmac.new() + hexdigest()
hashlibすべて
16進数文字列
✓ .update()経由
N/A
不要 (stdlib)
hmac.new() + digest()
hashlibすべて
生バイト列
✓ .update()経由
N/A
不要 (stdlib)
hmac.digest()
hashlibすべて
生バイト列
✗ (ワンショット)
N/A
不要 (stdlib, 3.7+)
hashlib.sha256(単純ハッシュ)
SHA-256のみ
16進数またはバイト列
✓ .update()経由
N/A
不要 (stdlib)
cryptography (HMAC)
任意
生バイト列
✓ .update()経由
N/A
pip install
pyca/cryptography + CMAC
AES-CMAC
生バイト列
✓ .update()経由
N/A
pip install

Webhook検証、API署名、一般的なHMAC操作には標準の hmac モジュールを使用してください — 依存関係ゼロで標準アルゴリズムをすべてカバーします。バッチ操作でワンショットの速度が重要な場合は hmac.digest() を使用します。他の操作(TLS、X.509、対称暗号化)のためにすでに依存していて例外ベースの .verify() APIが必要な場合にのみ cryptography ライブラリを使用してください。Pythonを書かずに素早く署名を確認したい場合は、 HMACジェネレーターツール にキーとメッセージを貼り付けて即座に結果を取得してください。

よくある質問

PythonのhmacにおけるHmac.new()とhmac.digest()の違いは何ですか?

hmac.new()は逐次的な.update()呼び出しをサポートするHMACオブジェクトを返し、.digest()(生バイト列)と.hexdigest()(16進数文字列)の両方を提供します。hmac.digest()はPython 3.7で追加されたワンショット関数で、中間オブジェクトを生成せずに直接生バイト列を返します。メッセージ全体が揃っていて結果だけが必要な場合はhmac.digest()を使用します。データをチャンク単位で投入する必要がある場合や16進数出力が必要な場合はhmac.new()を使用します。

Python
import hmac, hashlib

key = b"webhook_secret_2026"
body = b'{"event":"payment.completed","amount":9950}'

# ワンショット — 生バイト列を返す
raw = hmac.digest(key, body, hashlib.sha256)

# オブジェクトベース — 逐次更新と16進数出力をサポート
h = hmac.new(key, body, hashlib.sha256)
hex_sig = h.hexdigest()

PythonでHMAC署名を検証するには?

共有シークレットを使って元のメッセージに対してHMACを再計算し、hmac.compare_digest()で比較します。署名の比較に==は絶対に使用しないでください。==演算子は最初に一致しないバイトで短絡評価するため、期待される署名の長さと内容に関する情報が漏洩するタイミング攻撃に対して脆弱です。

Python
import hmac, hashlib

def verify_signature(secret: bytes, message: bytes, received_sig: str) -> bool:
    expected = hmac.new(secret, message, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, received_sig)

PythonのHMAC-SHA256はhashlib.sha256によるハッシュと同じですか?

異なります。hashlib.sha256は入力の単純なSHA-256ハッシュを計算するもので、誰でも再現できます。HMAC-SHA256はRFC 2104に従って秘密鍵をハッシュ計算に混ぜ込むため、鍵を持つ者だけが正しい出力を生成または検証できます。単純なハッシュはデータの完全性を証明します。HMACは完全性と真正性の両方を証明します。

Python
import hmac, hashlib

msg = b"transfer:9950:USD"
key = b"api_secret_k8x2"

plain_hash = hashlib.sha256(msg).hexdigest()  # 誰でも計算可能
hmac_hash = hmac.new(key, msg, hashlib.sha256).hexdigest()  # 鍵が必要

Python 3でHMAC-SHA1を使用できますか?

はい、digestmod引数にhashlib.sha1を渡します。HMAC-SHA1はPython 3でも問題なく動作し、hmacモジュールには非推奨の警告はありません。ただし、SHA-1は新しい設計には弱いと見なされています — 衝突耐性は80ビット未満であり、NISTは2015年にほとんどのデジタル署名用途でSHA-1を非推奨としました。今日HMAC-SHA1を使用する主な理由は、OAuth 1.0aや一部のレガシーWebhookシステムなど、SHA-1を必須とする既存プロトコルとの後方互換性です。両側を制御できる場合は、すべての新規開発でHMAC-SHA256またはHMAC-SHA512を優先してください。

Python
import hmac, hashlib

key = b"oauth_consumer_secret"
base_string = b"GET&https%3A%2F%2Fapi.example.com&oauth_nonce%3Dabc123"
sig = hmac.new(key, base_string, hashlib.sha1).digest()

PythonでセキュアなHMACキーを生成するには?

標準ライブラリのsecrets.token_bytes()を使用します。HMAC-SHA256の場合、ハッシュのブロックサイズに合わせた32バイトのキーが標準的な推奨値です。HMAC-SHA512の場合は64バイトを使用します。アプリケーションコードのキー生成にrandom.random()やos.urandom()を使用しないでください — Python 3.6以降、セキュリティ上重要な乱数生成にはsecretsが正しいモジュールです。

Python
import secrets

hmac_sha256_key = secrets.token_bytes(32)  # 256ビット
hmac_sha512_key = secrets.token_bytes(64)  # 512ビット

# 設定ファイル向けに16進数で保存
print(hmac_sha256_key.hex())
# 例: "a3f1b9c04e..."

Python 3でhmac.new()にdigestmodが必要なのはなぜですか?

Python 3.4以前、digestmodはデフォルトでMD5を使用していましたが、MD5は暗号学的に破られています — MD5には既知の衝突攻撃があり、新しいセキュリティ重視のコードでは決して使用すべきではありません。Pythonメンテナーはデフォルトを削除し、開発者が明示的にアルゴリズムを選択するよう強制しました。これにより、気づかずにMD5ベースのMACを出荷してしまう事態を防ぎます。digestmodなしでhmac.new(key, msg)を呼び出すとTypeErrorが発生します。常にアルゴリズムを明示的に指定してください:hashlib.sha256、hashlib.sha512、またはその他のhashlibコンストラクタ。迷った場合はhashlib.sha256が安全なデフォルトです — 既知の弱点がなく、実用的なあらゆる用途に十分な速度があります。

Python
import hmac, hashlib

key = b"secret"
msg = b"data"

# Python 3.4+ではTypeErrorが発生する
# hmac.new(key, msg)  # TypeError: missing required argument: 'digestmod'

# 常にアルゴリズムを指定する
h = hmac.new(key, msg, hashlib.sha256)

Pythonスクリプトを起動せずにすばやくHMACを確認したい場合は、キーとメッセージを オンラインHMACジェネレーター に貼り付けてください — SHA-256、SHA-384、SHA-512に対応し、即座に結果が得られます。

関連ツール

DV
Dmitri VolkovDevOps Engineer & Python Automation Specialist

Dmitri is a DevOps engineer who relies on Python as his primary scripting and automation language. He builds internal tooling, CI/CD pipelines, and infrastructure automation scripts that run in production across distributed teams. He writes about the Python standard library, subprocess management, file processing, encoding utilities, and the practical shell-adjacent Python that DevOps engineers use every day.

MS
Maria Santos技術レビュアー

Maria is a backend developer specialising in Python and API integration. She has broad experience with data pipelines, serialisation formats, and building reliable server-side services. She is an active member of the Python community and enjoys writing practical, example-driven guides that help developers solve real problems without unnecessary theory.