Python HMAC hmac.new() SHA-256
無料の 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を直接実装しています。
# 単純な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" のような文字列名を受け付けます。
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 であり、デコード手順は不要です。
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モジュールは文字列の入力を拒否します。
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() に渡します。
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実装でまだ必要とされます。コードは同一です — アルゴリズムを入れ替えるだけです。
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 — より長い出力
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 — レガシー専用
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.new() パラメータリファレンス
コンストラクタのシグネチャは hmac.new(key, msg=None, digestmod) です。モジュールの3つの主要関数はすべて、鍵とアルゴリズムの引数に同じパターンを使用します。
hmac.new() コンストラクタ
hmac.digest() ワンショット (Python 3.7+)
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実装を使用し、オブジェクト生成のオーバーヘッドを回避するため、処理が集中するループでは明確に高速です。
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署名の検証
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 "", 200hmac.compare_digest() 関数は2つの文字列またはバイト列を定数時間で比較します。通常の == 比較は最初の一致しないバイトで短絡評価します。攻撃者は多数のリクエストにわたってレスポンス時間を計測し、期待される署名をバイト単位で徐々に再構築できます。定数時間比較によりこのサイドチャネルを排除します。
GitHub Webhook検証
GitHubのWebhook形式はこのパターン全体を示しています。 X-Hub-Signature-256 ヘッダーに sha256= に続いてリポジトリ設定で構成したWebhookシークレットで署名された生リクエストボディのHMAC-SHA256の16進数エンコード値が含まれます。一般的なWebhook検証との主な違いは、比較前に sha256= プレフィックスを取り除く必要があること、そして生バイト列のリクエストボディを読み取る必要があることです — JSONを先にパースするとバイト表現が変わり検証が失敗します。
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メソッド、パス、タイムスタンプ、リクエストボディが含まれます。以下はサービス間の内部認証に使用するパターンです。
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リクエストへの署名
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 はどちらもターミナルから処理できます。
python3 -c " import hmac, hashlib print(hmac.new(b'my_secret', b'message_to_sign', hashlib.sha256).hexdigest()) " # 出力: 64文字の16進数文字列
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
# シェル履歴への露出を防ぐためキーを環境変数に保存
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 です — 不一致の場合にブール値を返す代わりに例外を送出するため、検証失敗をうっかり無視しにくくなっています。
pip install cryptography
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 が便利です。
pip install rich
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)大きなファイルの処理 — 逐次HMAC
50 MB程度を超えるファイルでは、HMACを計算するためだけにすべてをメモリに読み込むのは無駄です。HMACオブジェクトの .update() メソッドを使えばデータをチャンク単位で投入できます。これによりファイルサイズにかかわらずメモリ使用量を一定に保てます。
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}")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'}").update() アプローチはファイルサイズにかかわらず固定の chunk_size 分のメモリのみを使用します。デフォルトは64KBのチャンクサイズを推奨します — システムコールのオーバーヘッドを十分に吸収しつつ、ほとんどのハードウェアのL2キャッシュ内に収まるサイズです。PythonでセキュアなHMACキーを生成する
弱いまたは予測可能なキーはHMAC構造全体を損ないます。 secrets モジュール(Python 3.6以降)は暗号強度の高いランダムなバイト列を提供します。 HMAC-SHA256には32バイトのキーを使用します。HMAC-SHA512には64バイトを使用します。これらはそれぞれのハッシュアルゴリズムの内部ブロックサイズに一致し、RFC 2104による最適なキー長です。
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="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秒間に数万件のメッセージを処理する場合、こうした割り当ての排除がレイテンシとスループットで測定可能な差をもたらします。
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()を使用してください — 不一致が発生した位置にかかわらず定数時間で実行されます。
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()はバイト列オブジェクトを要求します。Python strを渡すとTypeError: "key: expected bytes or bytearray, but got 'str'"が発生します。
解決策: hmac.new()に渡す前に文字列のキーとメッセージに.encode()を呼び出してください。
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)問題: 3番目の引数なしでhmac.new(key, msg)を呼び出すとTypeErrorが発生します。Python 3.4以前はデフォルトでMD5でしたが、セキュリティ上の理由でデフォルトが削除されました。
解決策: アルゴリズムを常に明示的に指定してください:hashlib.sha256、hashlib.sha512、またはプロトコルが要求するもの。
# digestmodが省略されている — Python 3.4以降でTypeError sig = hmac.new(key, msg).hexdigest()
# 常にハッシュアルゴリズムを指定する sig = hmac.new(key, msg, hashlib.sha256).hexdigest()
問題: 多くのWebhookプロバイダー(Stripe、Shopify)は16進数ではなくBase64エンコードされた署名を送信します。16進数文字列をBase64値と比較すると常に失敗し、すべてのWebhookが拒否されます。
解決策: 署名形式についてプロバイダーのドキュメントを確認してください。Base64を使用している場合は、.hexdigest()文字列ではなく生の.digest()バイト列をエンコードしてください。
# プロバイダーは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 — 簡易比較
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()を使用します。
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()で比較します。署名の比較に==は絶対に使用しないでください。==演算子は最初に一致しないバイトで短絡評価するため、期待される署名の長さと内容に関する情報が漏洩するタイミング攻撃に対して脆弱です。
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は完全性と真正性の両方を証明します。
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を優先してください。
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が正しいモジュールです。
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が安全なデフォルトです — 既知の弱点がなく、実用的なあらゆる用途に十分な速度があります。
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に対応し、即座に結果が得られます。
関連ツール
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.
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.