Python HMAC hmac.new() SHA-256
ใช้ เครื่องสร้าง HMAC ฟรีโดยตรงในเบราว์เซอร์ของคุณ — ไม่ต้องติดตั้ง
ลอง เครื่องสร้าง HMAC ออนไลน์ →ทุก webhook callback ทุก API request ที่มีการเซ็น ทุกการแจ้งเตือน event จาก Stripe หรือ GitHub ล้วนใช้ HMAC signature เพื่อพิสูจน์ว่า payload ไม่ถูกดัดแปลง module hmac ของ Python จัดการ HMAC-SHA256 ใน Python ด้วย การเรียกฟังก์ชันเดียว: hmac.new(key, msg, hashlib.sha256). ไม่ต้อง pip install ไม่ต้อง C extension ไม่ต้องพึ่ง third-party สำหรับการตรวจสอบ signature แบบ one-off โดยไม่ต้องเขียนโค้ด เครื่องมือ HMAC Generator ออนไลน์ ให้ผลลัพธ์ได้ทันที คู่มือนี้ครอบคลุม hmac.new(), hmac.digest(), hmac.compare_digest(), การเข้ารหัส Base64, การตรวจสอบ webhook, การเซ็น API request และอัลกอริทึม hash ทุกตัวตั้งแต่ SHA-1 ถึง SHA-512 ตัวอย่างทั้งหมดใช้ Python 3.7+
- ✓hmac.new(key, msg, hashlib.sha256) คือ entry point มาตรฐาน — key และ msg ต้องเป็น bytes, digestmod บังคับตั้งแต่ Python 3.4
- ✓hmac.digest(key, msg, "sha256") คือทางเลือก one-shot ที่เร็วกว่า เพิ่มมาใน Python 3.7 — คืนค่า raw bytes ไม่ต้องสร้าง object กลาง
- ✓ตรวจสอบ signature ด้วย hmac.compare_digest() เสมอเพื่อป้องกัน timing attack — ห้ามใช้ == สำหรับการเปรียบเทียบ HMAC
- ✓เข้ารหัส raw .digest() output เป็น Base64 สำหรับ HTTP header และ webhook signature: base64.b64encode(h.digest())
- ✓module hmac รองรับ hashlib algorithm ทุกตัว: sha1, sha256, sha384, sha512, md5, blake2b
HMAC คืออะไร?
HMAC (Hash-based Message Authentication Code) คือโครงสร้างที่กำหนดใน RFC 2104 ที่ผสมคีย์ลับกับฟังก์ชัน hash เพื่อสร้าง authentication tag ขนาดคงที่ ต่างจาก plain hash (ที่ทุกคนสามารถคำนวณได้) HMAC ต้องการความรู้เกี่ยวกับคีย์ลับ ซึ่งหมายความว่าคุณสามารถใช้มันเพื่อตรวจสอบทั้งความสมบูรณ์และความถูกต้องของข้อความ หากแม้แต่หนึ่ง byte ของข้อความหรือคีย์เปลี่ยนแปลง output จะแตกต่างกันอย่างสิ้นเชิง โครงสร้างทำงานโดยการแฮชคีย์ที่ XOR กับค่าคงที่ padding สองตัว (ipad และ opad) โดยครอบข้อความระหว่างการดำเนินการ hash สองครั้ง module hmac ของ Python ใช้งาน RFC นี้โดยตรง
# Plain SHA-256 hash — ไม่มีคีย์ลับ ทุกคนสามารถคำนวณได้ 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() — Entry Point ของ Standard Library
module hmac เป็นส่วนหนึ่งของ Python standard library ต้อง import เพียงสองบรรทัดและพร้อมใช้งาน: import hmac, hashlib. ฟังก์ชันหลักสามตัวคือ hmac.new() (สร้าง HMAC object), hmac.digest() (one-shot Python 3.7+) และ hmac.compare_digest() (การเปรียบเทียบแบบ constant-time) ไม่ต้อง pip install
hmac.new(key, msg, digestmod) รับ argument สามตัว ทั้ง key และ msg ต้องเป็น bytes-like object ( bytes, bytearray หรือ memoryview). argument digestmod บังคับตั้งแต่ Python 3.4 และรับ hashlib constructor ใดก็ได้ (เช่น hashlib.sha256) หรือชื่อ string เช่น "sha256"
import hmac
import hashlib
key = b"webhook_signing_key_2026"
message = b'{"event":"invoice.paid","invoice_id":"inv_8f3a","amount":19900}'
# สร้าง HMAC object และรับ hex signature
signature = hmac.new(key, message, hashlib.sha256).hexdigest()
print(signature)
# "b4e74f6c9a1d3e5f8b2a7c0d4e6f1a3b5c7d9e0f2a4b6c8d0e1f3a5b7c9d0e2f"HMAC object มีเมธอด output สองตัว .digest() คืนค่า raw bytes (32 bytes สำหรับ SHA-256, 64 สำหรับ SHA-512) .hexdigest() คืนค่า hex string พิมพ์เล็ก hex string เป็น Python str ธรรมดา — ไม่ต้องผ่านขั้นตอน decode
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 # ทั้งสองแทนข้อมูลเดียวกัน — hex คือ string encoding ของ bytes assert raw_bytes.hex() == hex_string
หาก key หรือ message เป็น Python string ให้เรียก .encode() เพื่อแปลงเป็น bytes ก่อนส่งเข้า hmac.new()เรื่องนี้ทำให้เกือบทุกคนสะดุดในครั้งแรก — Python 3 string เป็น Unicode ไม่ใช่ bytes และ module hmac จะปฏิเสธ string
import hmac
import hashlib
# String key และ message — .encode() แปลงเป็น UTF-8 bytes
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..." — hex string output ที่สม่ำเสมอdigestmod ไม่มีค่าเริ่มต้นตั้งแต่ Python 3.4 การเรียก hmac.new(key, msg) โดยไม่ระบุจะเกิด TypeError ก่อนหน้า 3.4 มีค่าเริ่มต้นเป็น MD5 ซึ่งเป็นเหตุผลที่ Python maintainer ลบค่าเริ่มต้นออก — เพื่อบังคับให้เลือกอย่างชัดเจนและปลอดภัยHMAC-SHA256 Base64, SHA-1, SHA-512 และ MD5
ฟังก์ชัน hmac.new() ทำงานร่วมกับ hash algorithm ใดก็ได้ที่มีอยู่ใน hashlibผู้ให้บริการ webhook และ API gateway ส่วนใหญ่ใช้ HMAC-SHA256 แต่คุณจะพบ SHA-1 ใน OAuth 1.0a SHA-512 ใน protocol ที่กำหนดไว้ และ MD5 ในระบบเก่าที่ยังไม่ได้อัปเดต
HMAC-SHA256 พร้อม Base64 Output
ผู้ให้บริการ webhook หลายรายส่ง signature เป็น Base64-encoded string ใน HTTP header เพื่อให้ได้รูปแบบเดียวกัน ส่ง raw .digest() bytes ไปยัง base64.b64encode()
import hmac
import hashlib
import base64
key = b"whsec_MbkP7x9yFqHGn3tRdWz5"
payload = b'{"id":"evt_1Nq","type":"charge.succeeded","data":{"amount":4200}}'
# Raw digest → Base64 (ทั่วไปสำหรับ Authorization header และ webhook signature)
raw_digest = hmac.new(key, payload, hashlib.sha256).digest()
b64_signature = base64.b64encode(raw_digest).decode("ascii")
print(b64_signature)
# "dGhpcyBpcyBhIHNhbXBsZSBzaWduYXR1cmU="
# ค่านี้คือค่าที่จะนำไปเปรียบเทียบกับ X-Signature header
header_value = f"sha256={b64_signature}"
print(header_value)
# "sha256=dGhpcyBpcyBhIHNhbXBsZSBzaWduYXR1cmU="HMAC-SHA1 — ความเข้ากันได้กับ Protocol เก่า
SHA-1 ถือว่าอ่อนแอสำหรับการออกแบบใหม่ แต่ HMAC-SHA1 ยังคงจำเป็นสำหรับ OAuth 1.0a และ webhook รุ่นเก่าบางตัว โค้ดเหมือนกันทุกอย่าง — เพียงแค่เปลี่ยน algorithm
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
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..." — URL-encode นี้สำหรับ Authorization headerHMAC-SHA512 — Output ที่ยาวขึ้น
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 bytes (512 bits)
print(len(h.hexdigest())) # 128 hex characters
print(h.hexdigest()[:40] + "...")
# "8e3a1f9b2c4d6e7f0a1b3c5d7e9f0a2b4c6d8e0f..."HMAC-MD5 — สำหรับระบบเก่าเท่านั้น
import hmac import hashlib # MD5 เสียหายในทางการเข้ารหัส — ใช้เฉพาะเพื่อความเข้ากันได้กับ protocol เก่า key = b"legacy_api_key" msg = b"action=charge&amount=1500&merchant=store_42" sig = hmac.new(key, msg, hashlib.md5).hexdigest() print(sig) # hex string 32 ตัวอักษร # "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
การอ้างอิง Parameter ของ hmac.new()
signature ของ constructor คือ hmac.new(key, msg=None, digestmod)ฟังก์ชันทั้งสามใน module ใช้รูปแบบ key และ algorithm argument เดียวกัน
hmac.new() constructor
hmac.digest() one-shot (Python 3.7+)
parameter digestmod รับทั้ง callable (เช่น hashlib.sha256) หรือชื่อ string (เช่น "sha256") แนะนำให้ใช้รูปแบบ callable เพราะได้รับการตรวจสอบตอน import — การพิมพ์ผิดในรูปแบบ string จะล้มเหลวตอน runtime เท่านั้น
hmac.digest() — HMAC แบบ One-Shot ที่เร็วกว่า (Python 3.7+)
Python 3.7 เพิ่ม hmac.digest(key, msg, digest) เป็นฟังก์ชันระดับ module ทำการคำนวณ HMAC ในการเรียกครั้งเดียวโดยไม่ต้องสร้าง HMAC object กลาง ค่าที่คืนกลับเป็น raw bytes (เทียบเท่ากับการเรียก .digest() บน object) ฟังก์ชันนี้ใช้ C implementation ที่ปรับปรุงแล้วบน CPython และหลีกเลี่ยง overhead จากการสร้าง object ทำให้เร็วขึ้นอย่างเห็นได้ชัดในการวนซ้ำหนาแน่น
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}',
]
# One-shot digest — ไม่มี HMAC object กลาง
signatures = [hmac.digest(key, msg, hashlib.sha256) for msg in messages]
# แปลงเป็น hex สำหรับแสดงผล
for msg, sig in zip(messages, signatures):
print(f"{msg[:30]}... -> {sig.hex()[:24]}...")ข้อจำกัดคือ: hmac.digest() คืนค่าเฉพาะ raw bytes ถ้าต้องการ hex string โดยตรง คุณยังต้องใช้ hmac.new() เพื่อเรียก .hexdigest() หรือต่อ .hex() กับผลลัพธ์ bytes
hmac.digest() ไม่รองรับการเรียก .update() แบบทีละส่วน ถ้าต้องอ่านไฟล์ขนาดใหญ่เป็นชิ้นๆ และต้องการ HMAC เนื้อหา ให้ใช้ hmac.new() และเรียก .update(chunk) ในลูปการตรวจสอบ HMAC Signature จาก Webhook และ API Response
การใช้งาน HMAC ที่พบบ่อยที่สุดใน Python คือการตรวจสอบ webhook signature ผู้ให้บริการรายใหญ่ทุกราย (Stripe, GitHub, Shopify, Twilio) เซ็น payload ด้วย HMAC-SHA256 และส่ง signature ใน header รูปแบบเสมอเหมือนกัน: คำนวณ HMAC ใหม่จาก raw request body จากนั้นเปรียบเทียบด้วย hmac.compare_digest()
การตรวจสอบ Webhook Signature
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 — ต้องตรงกับสิ่งที่ถูกเซ็นพอดี
raw_body = request.get_data()
# รับ signature จาก header
received_sig = request.headers.get("X-Signature-256", "")
# คำนวณ HMAC ใหม่จาก raw body
expected_sig = hmac.new(WEBHOOK_SECRET, raw_body, hashlib.sha256).hexdigest()
# การเปรียบเทียบแบบ constant-time — ป้องกัน timing attack
if not hmac.compare_digest(f"sha256={expected_sig}", received_sig):
abort(403, "Invalid signature")
# ตรวจสอบ signature แล้ว — ดำเนินการ event
event = request.get_json()
print(f"Verified event: {event['type']} for {event['data']['amount']}")
return "", 200ฟังก์ชัน hmac.compare_digest() เปรียบเทียบ string หรือลำดับ byte สองชุดในเวลาคงที่ การเปรียบเทียบด้วย == ธรรมดาจะหยุดทำงานเมื่อพบ byte แรกที่ไม่ตรงกัน ผู้โจมตีสามารถวัดเวลาตอบสนองจากหลาย request และค่อยๆ สร้าง signature ที่คาดหวังขึ้นใหม่ทีละ byte การเปรียบเทียบแบบ constant-time ขจัดช่องทางด้านข้างนี้
การตรวจสอบ GitHub Webhook
รูปแบบ webhook ของ GitHub แสดงให้เห็นรูปแบบครบถ้วน GitHub ส่ง header X-Hub-Signature-256 ที่มี sha256= ตามด้วย HMAC-SHA256 แบบ hex-encoded ของ raw request body ที่เซ็นด้วย webhook secret ที่คุณกำหนดในการตั้งค่า repository ข้อแตกต่างหลักจากการตรวจสอบ webhook ทั่วไปคือ คุณต้องตัด prefix sha256= ออกก่อนเปรียบเทียบ และต้องอ่าน raw bytes ของ request body — การ parse JSON ก่อน จะเปลี่ยน byte representation และทำลายการตรวจสอบ
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() # raw bytes — อย่า parse 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), โดยความแตกต่างเพียงอย่างเดียวคือชื่อ header และ signature เป็น hex หรือ Base64-encoded ตรวจสอบเอกสารของผู้ให้บริการเสมอเพื่อยืนยันรูปแบบการเข้ารหัส — การสับสนระหว่าง hex กับ Base64 เป็นสาเหตุที่พบบ่อยที่สุดของข้อผิดพลาด signature mismatch
การยืนยันตัวตน API Request ด้วย HMAC
บาง API กำหนดให้ client เซ็นแต่ละ request ด้วย HMAC string ที่เซ็นมักประกอบด้วย HTTP method, path, timestamp และ request body นี่คือรูปแบบที่ใช้สำหรับ internal service-to-service authentication
import hmac
import hashlib
import time
import json
def sign_request(secret: bytes, method: str, path: str, body: str) -> dict:
"""สร้าง HMAC-SHA256 signature สำหรับ API request"""
timestamp = str(int(time.time()))
# สร้าง signing string — method + path + timestamp + body
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..."}การเซ็น HTTP Request ด้วย requests Library
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=(",", ":")) # compact 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
# ส่ง signed POST request
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=(",", ":") เมื่อ serialize body สำหรับการเซ็น ค่าเริ่มต้นของ json.dumps() จะเพิ่มช่องว่างหลัง separator ซึ่งเปลี่ยน byte representation และทำลายการตรวจสอบ signature หาก server serialize ต่างออกไป JSON แบบ compact ให้รูปแบบที่กำหนดแน่นอน
การสร้าง HMAC จาก Command Line
บางครั้งคุณต้องการคำนวณ HMAC โดยไม่ต้องเขียน script flag -c ของ Python และ openssl ทั้งสองจัดการได้จาก terminal
python3 -c " import hmac, hashlib print(hmac.new(b'my_secret', b'message_to_sign', hashlib.sha256).hexdigest()) " # outputs: hex string 64 ตัวอักษร
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
# เก็บ key ใน env var เพื่อหลีกเลี่ยงการแสดงใน shell history
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 จะเพิ่ม newline character ต่อท้ายข้อความซึ่งเปลี่ยน HMAC output นี่คือสาเหตุที่พบบ่อยที่สุดของ signature mismatch เมื่อ debug จาก terminalทางเลือกสำหรับประสิทธิภาพสูง — cryptography Library
สำหรับ application ส่วนใหญ่ module hmac มาตรฐานเพียงพอแล้ว ถ้าคุณใช้ library cryptography สำหรับ TLS หรือการจัดการ certificate อยู่แล้ว มันก็มี HMAC ที่ขับเคลื่อนด้วย OpenSSL ด้วย ความแตกต่างในทางปฏิบัติหลักจาก stdlib คือ API แบบ exception-based ของ .verify() — มัน raise เมื่อไม่ตรงกันแทนที่จะคืนค่า boolean ที่คุณอาจลืมตรวจสอบ
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() # raw bytes
print(signature.hex())
# "9c4e2a..."
# โหมดตรวจสอบ — raise InvalidSignature เมื่อไม่ตรงกัน
h_verify = crypto_hmac.HMAC(key, hashes.SHA256())
h_verify.update(message)
h_verify.verify(signature) # raise cryptography.exceptions.InvalidSignature ถ้าผิดเมธอด .verify() ของ library cryptography มีประโยชน์มาก: มัน raise exception เมื่อไม่ตรงกันแทนที่จะคืนค่า boolean ทำให้ยากขึ้นที่จะ ละเลยความล้มเหลวในการตรวจสอบโดยไม่ตั้งใจ hmac.compare_digest() ของ standard library คืนค่า True/ Falseซึ่งสามารถถูกละเลยโดยไม่รู้ตัวหากนักพัฒนาลืมตรวจสอบ return value
แสดงผล Terminal พร้อม Syntax Highlighting
ถ้ากำลัง debug HMAC signature ใน terminal และต้องการ output สีสัน 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)การทำงานกับไฟล์ขนาดใหญ่ — Incremental HMAC
สำหรับไฟล์ขนาดใหญ่กว่า 50 MB หรือมากกว่า การโหลดทุกอย่างเข้าหน่วยความจำเพียงเพื่อคำนวณ HMAC เป็นการสิ้นเปลือง เมธอด .update() บน HMAC object ให้คุณส่งข้อมูลเป็นชิ้นๆ ทำให้การใช้หน่วยความจำคงที่ไม่ว่าขนาดไฟล์จะเป็นเท่าไร
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()
# เซ็น database export ขนาด 2 GB
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 signature ของไฟล์"""
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)
# ตรวจสอบ artifact ที่ดาวน์โหลด
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 คงที่โดยไม่คำนึงถึงขนาดไฟล์ ค่าเริ่มต้น 64 KB — ใหญ่พอที่จะลด syscall overhead เล็กพอที่จะอยู่ใน L2 cache ของ hardware ส่วนใหญ่การสร้าง HMAC Key ที่ปลอดภัยในทางการเข้ารหัสใน Python
คีย์ที่อ่อนแอหรือคาดเดาได้ทำลายโครงสร้าง HMAC ทั้งหมด module secrets (Python 3.6+) ให้ random bytes ที่แข็งแกร่งในทางการเข้ารหัส สำหรับ HMAC-SHA256 ใช้คีย์ขนาด 32 bytes สำหรับ HMAC-SHA512 ใช้ 64 bytes ขนาดเหล่านี้ตรงกับขนาด block ภายในของ hash algorithm ซึ่งเป็นความยาวคีย์ที่เหมาะสมที่สุด ตาม RFC 2104
import secrets
# สร้างคีย์ที่ตรงกับขนาด block ของ hash algorithm
sha256_key = secrets.token_bytes(32) # 256 bits — สำหรับ HMAC-SHA256
sha512_key = secrets.token_bytes(64) # 512 bits — สำหรับ HMAC-SHA512
# รูปแบบ hex — ปลอดภัยสำหรับไฟล์ config และ environment variable
print(f"HMAC-SHA256 key: {sha256_key.hex()}")
# เช่น "a3f1b9c04e7d2f8a1b3c5d7e9f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f"
print(f"HMAC-SHA512 key: {sha512_key.hex()}")
# hex string 128 ตัวอักษร
# URL-safe Base64 — กะทัดรัด ปลอดภัยสำหรับ HTTP header
import base64
b64_key = base64.urlsafe_b64encode(sha256_key).decode("ascii")
print(f"Base64 key: {b64_key}")
# เช่น "o_G5wE59L4obPF1-nwortG2ODwobPExdXnqLnA0dLi8="random.random() หรือ random.randbytes() จาก module random สำหรับ HMAC key ค่าเริ่มต้น module random ใช้ Mersenne Twister PRNG ซึ่งสามารถคาดเดาได้ หลังจากสังเกต 624 output ให้ใช้ secrets.token_bytes() เสมอสำหรับความสุ่มที่ต้องการความปลอดภัยความยาวคีย์และข้อกำหนดของ RFC 2104
RFC 2104 ระบุว่า HMAC key สามารถมีความยาวใดก็ได้ แต่แนะนำให้ใช้คีย์อย่างน้อย L bytes โดยที่ L คือความยาว output ของ hash function พื้นฐาน สำหรับ HMAC-SHA256 คือ 32 bytes (256 bits) คีย์ที่สั้นกว่า L bits จะลด security margin ตามสัดส่วน คีย์ที่ยาวกว่าขนาด block ของ hash (64 bytes สำหรับ SHA-256, 128 bytes สำหรับ SHA-512) จะถูก hash ลงมาเป็นขนาด block ก่อน ดังนั้นจึงไม่มีประโยชน์ใดในการใช้คีย์ที่ยาวกว่าขนาด block ใช้ 32 bytes สำหรับ HMAC-SHA256 และ 64 bytes สำหรับ HMAC-SHA512
การเก็บรักษาและการหมุนเวียนคีย์อย่างปลอดภัย
ห้าม hardcode HMAC key ในซอร์สโค้ด แนวทางมาตรฐานสำหรับ production deployment คือ โหลดคีย์จาก environment variable ตอน startup: os.environ["HMAC_SECRET"].encode()สำหรับสภาพแวดล้อมที่ต้องการความปลอดภัยสูง เก็บคีย์ใน secrets management system เช่น AWS Secrets Manager, HashiCorp Vault หรือ GCP Secret Manager และดึงมาตอน runtime ระบบเหล่านี้มี audit log การควบคุมการเข้าถึง และการหมุนเวียนอัตโนมัติ โดยไม่ต้อง deploy โค้ดใหม่
วางแผนสำหรับการหมุนเวียนคีย์ตั้งแต่ต้น เมื่อหมุนเวียนคีย์จะมีช่วงที่ request ระหว่างทาง ถูกเซ็นด้วยคีย์เก่าและจะล้มเหลวในการตรวจสอบกับคีย์ใหม่ วิธีแก้มาตรฐานคือช่วงทับซ้อนสั้นๆ: ยอมรับ signature จากทั้งคีย์เก่าและใหม่ในช่วงเวลาสั้น (นาทีถึงชั่วโมง) จากนั้นเลิกใช้คีย์เก่า หากคีย์ถูกเปิดเผย — รั่วผ่าน log, รั่วผ่าน git commit หรือถูกเปิดเผยในเหตุการณ์ — หมุนเวียนทันที และถือว่า signature ทั้งหมดที่ผลิตด้วยคีย์ที่ถูกเปิดเผยเป็นที่ไม่น่าเชื่อถือ ตรวจสอบผลลัพธ์การตรวจสอบที่ cache ไว้ใหม่และแจ้ง downstream consumer ของการเปลี่ยนแปลงคีย์
การใช้ bytearray และ memoryview กับ hmac.new()
ฟังก์ชัน hmac.new() รับ bytes-like object ใดก็ได้สำหรับทั้ง parameter key และ msg ซึ่งหมายความว่าคุณสามารถส่ง bytes, bytearray หรือ memoryview โดยตรงโดยไม่ต้องคัดลอกหรือแปลง เรื่องนี้สำคัญมากในสองสถานการณ์: การใช้งาน network protocol ที่ socket.recv_into() เขียนข้อมูลลงใน bytearray buffer ที่จัดสรรไว้ล่วงหน้า และระบบ high-throughput ที่การหลีกเลี่ยง intermediate copy ลด GC pressure slice ของ memoryview เป็น zero-copy: มันเปิดหน้าต่างสู่ buffer ต้นฉบับโดยไม่จัดสรรหน่วยความจำใหม่ ที่หลายหมื่น message ต่อวินาที การขจัดการจัดสรรเหล่านั้นให้ความแตกต่างที่วัดได้ใน latency และ throughput
import hmac
import hashlib
# bytearray — bytes ที่เปลี่ยนแปลงได้ มีประโยชน์สำหรับ binary protocol buffer
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 — zero-copy slice ของ buffer ขนาดใหญ่
large_buffer = bytearray(4096)
large_buffer[:20] = b"sensor_reading_12345"
# HMAC เฉพาะ 20 bytes แรกโดยไม่ต้องคัดลอก
view = memoryview(large_buffer)[:20]
sig = hmac.new(key, view, hashlib.sha256).hexdigest()
print(f"Sensor signature: {sig[:32]}...")ข้อผิดพลาดที่พบบ่อย
สองข้อผิดพลาดแรกที่เห็นในเกือบทุก code review ที่เกี่ยวกับ webhook handler พวกมันเกิดขึ้นง่ายภายใต้ความกดดันด้านเวลาและยากที่จะสังเกตโดยไม่รู้ว่าต้องมองหาอะไร
ปัญหา: ตัวดำเนินการ == หยุดทำงานเมื่อพบ byte แรกที่ไม่ตรงกัน ทำให้รั่วข้อมูล timing ที่ให้ผู้โจมตีสร้าง signature ที่คาดหวังขึ้นใหม่ทีละขั้น
วิธีแก้ไข: ใช้ hmac.compare_digest() สำหรับการเปรียบเทียบ signature เสมอ — มันทำงานในเวลาคงที่โดยไม่คำนึงว่าความไม่ตรงกันเกิดขึ้นที่ไหน
received_sig = request.headers["X-Signature"]
expected_sig = hmac.new(key, body, hashlib.sha256).hexdigest()
if received_sig == expected_sig: # มีความเสี่ยงต่อ timing attack
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): # constant-time
process_webhook(body)ปัญหา: hmac.new() ต้องการ bytes-like object การส่ง Python str จะเกิด TypeError: "key: expected bytes or bytearray, but got 'str'"
วิธีแก้ไข: เรียก .encode() กับ string key และ message ก่อนส่งเข้า hmac.new()
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)ปัญหา: การเรียก hmac.new(key, msg) โดยไม่ระบุ argument ที่สามจะเกิด TypeError ก่อน Python 3.4 มีค่าเริ่มต้นเป็น MD5 แต่ถูกลบออกด้วยเหตุผลด้านความปลอดภัย
วิธีแก้ไข: ระบุ algorithm อย่างชัดเจนเสมอ: hashlib.sha256, hashlib.sha512 หรือ algorithm ที่ protocol กำหนด
# ไม่มี digestmod — เกิด TypeError ใน Python 3.4+ sig = hmac.new(key, msg).hexdigest()
# ระบุ hash algorithm เสมอ sig = hmac.new(key, msg, hashlib.sha256).hexdigest()
ปัญหา: ผู้ให้บริการ webhook หลายราย (Stripe, Shopify) ส่ง signature แบบ Base64-encoded ไม่ใช่ hex การเปรียบเทียบ hex string กับค่า Base64 จะล้มเหลวเสมอ ทำให้ webhook ทั้งหมดถูกปฏิเสธ
วิธีแก้ไข: ตรวจสอบเอกสารของผู้ให้บริการเพื่อยืนยันรูปแบบ signature ถ้าใช้ Base64 ให้เข้ารหัส raw .digest() bytes ไม่ใช่ .hexdigest() string
# ผู้ให้บริการส่ง Base64 แต่เราคำนวณ hex — ไม่ตรงกันเลย expected = hmac.new(key, body, hashlib.sha256).hexdigest() # "a3f1b9c0..." vs "o/G5wE59..." — ไม่ตรงกันเสมอ
import base64
# ตรงกับรูปแบบของผู้ให้บริการ: raw bytes → Base64
raw = hmac.new(key, body, hashlib.sha256).digest()
expected = base64.b64encode(raw).decode("ascii")
# "o/G5wE59..." — ตรงกับ headerstdlib hmac เทียบกับ cryptography — การเปรียบเทียบสั้นๆ
ใช้ module hmac ของ stdlib สำหรับการตรวจสอบ webhook การเซ็น API และการทำงาน HMAC ทั่วไป — ไม่ต้อง dependency และรองรับทุก standard algorithm ใช้ hmac.digest() สำหรับการดำเนินการแบบ batch ที่ความเร็ว one-shot มีความสำคัญ เลือก library cryptography เฉพาะเมื่อคุณพึ่งพามันสำหรับการดำเนินการอื่นอยู่แล้ว (TLS, X.509, symmetric encryption) และต้องการ API แบบ exception-based ของ .verify() สำหรับการตรวจสอบ signature อย่างรวดเร็วโดยไม่ต้องเขียน Python ใดๆ ใช้ เครื่องมือ HMAC Generator เพื่อวาง key และข้อความและรับผลลัพธ์ทันที
คำถามที่พบบ่อย
hmac.new() และ hmac.digest() ใน Python ต่างกันอย่างไร?
hmac.new() คืนค่า HMAC object ที่รองรับการเรียก .update() แบบทีละส่วน และให้ทั้ง .digest() (raw bytes) และ .hexdigest() (hex string) ส่วน hmac.digest() เป็นฟังก์ชัน one-shot ที่เพิ่มมาใน Python 3.7 ซึ่งคืนค่า raw bytes โดยตรงโดยไม่ต้องสร้าง object กลาง ใช้ hmac.digest() เมื่อมีข้อความครบถ้วนและต้องการผลลัพธ์เท่านั้น ใช้ hmac.new() เมื่อต้องการส่งข้อมูลเป็นชิ้นๆ หรือต้องการ hex output
import hmac, hashlib
key = b"webhook_secret_2026"
body = b'{"event":"payment.completed","amount":9950}'
# One-shot — คืนค่า raw bytes
raw = hmac.digest(key, body, hashlib.sha256)
# Object-based — รองรับการอัปเดตแบบทีละส่วนและ hex output
h = hmac.new(key, body, hashlib.sha256)
hex_sig = h.hexdigest()จะตรวจสอบ HMAC signature ใน Python ได้อย่างไร?
คำนวณ HMAC ใหม่จากข้อความต้นฉบับโดยใช้คีย์ลับร่วม จากนั้นเปรียบเทียบโดยใช้ hmac.compare_digest() อย่าใช้ == สำหรับการเปรียบเทียบ signature เพราะตัวดำเนินการ == มีความเสี่ยงต่อ timing attack — มันหยุดทำงานเมื่อพบ byte แรกที่ไม่ตรงกัน ทำให้รั่วข้อมูลเกี่ยวกับความยาวและเนื้อหาของ signature ที่คาดหวัง
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 SHA-256 เหมือนกับการแฮชด้วย hashlib.sha256 หรือไม่?
ไม่เหมือน hashlib.sha256 คำนวณ plain SHA-256 hash ของ input ซึ่งทุกคนสามารถคำนวณซ้ำได้ HMAC-SHA256 ผสมคีย์ลับเข้าสู่กระบวนการแฮชตาม RFC 2104 ดังนั้นเฉพาะผู้ที่มีคีย์เท่านั้นที่สามารถสร้างหรือตรวจสอบ output ที่ถูกต้องได้ plain hash พิสูจน์ความสมบูรณ์ของข้อมูล ส่วน 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() # ต้องใช้คีย์
ใช้ HMAC-SHA1 ใน Python 3 ได้หรือไม่?
ได้ ส่ง hashlib.sha1 เป็น argument digestmod HMAC-SHA1 ยังทำงานได้ดีใน Python 3 และ module hmac ไม่มีคำเตือน deprecation อย่างไรก็ตาม SHA-1 ถือว่าอ่อนแอสำหรับการออกแบบใหม่ — ความต้านทานการชนกันต่ำกว่า 80 บิต และ NIST ได้ยกเลิกการใช้งานสำหรับการเซ็นลายมือดิจิทัลส่วนใหญ่ในปี 2015 เหตุผลหลักที่ใช้ HMAC-SHA1 ในปัจจุบันคือความเข้ากันได้แบบย้อนหลังกับ protocol เดิมที่กำหนดไว้ เช่น OAuth 1.0a หรือระบบ webhook รุ่นเก่า เมื่อควบคุมทั้งสองฝั่งของการเชื่อมต่อ ให้ใช้ 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()
จะสร้าง HMAC key ที่ปลอดภัยใน Python ได้อย่างไร?
ใช้ secrets.token_bytes() จาก standard library สำหรับ HMAC-SHA256 คีย์ขนาด 32 bytes เป็นคำแนะนำมาตรฐาน เนื่องจากตรงกับขนาด block ของ hash สำหรับ HMAC-SHA512 ใช้ 64 bytes อย่าใช้ random.random() หรือ os.urandom() สำหรับการสร้างคีย์ใน application code — secrets เป็น module ที่ถูกต้องสำหรับความสุ่มที่ต้องการความปลอดภัยตั้งแต่ Python 3.6
import secrets hmac_sha256_key = secrets.token_bytes(32) # 256 bits hmac_sha512_key = secrets.token_bytes(64) # 512 bits # เก็บเป็น hex สำหรับไฟล์ config print(hmac_sha256_key.hex()) # เช่น "a3f1b9c04e..."
ทำไม hmac.new() ถึงต้องระบุ digestmod ใน Python 3?
ก่อน Python 3.4 digestmod มีค่าเริ่มต้นเป็น MD5 ซึ่งเสียหายในทางการเข้ารหัส — MD5 มีการโจมตีแบบ collision ที่รู้จักกันดีและไม่ควรใช้กับ code ที่ต้องการความปลอดภัย Python maintainer ลบค่าเริ่มต้นนี้ออกเพื่อบังคับให้เลือก algorithm อย่างชัดเจน ป้องกันไม่ให้นักพัฒนาส่ง MAC แบบ MD5 โดยไม่รู้ตัว หากเรียก hmac.new(key, msg) โดยไม่ระบุ digestmod จะได้รับ TypeError เสมอ ระบุ algorithm อย่างชัดเจน: hashlib.sha256, hashlib.sha512 หรือ hashlib constructor อื่นๆ เมื่อไม่แน่ใจ hashlib.sha256 คือตัวเลือกที่ปลอดภัย — ไม่มีจุดอ่อนที่รู้จักและเร็วพอสำหรับทุก workload ในทางปฏิบัติ
import hmac, hashlib key = b"secret" msg = b"data" # บรรทัดนี้จะเกิด TypeError ใน Python 3.4+ # hmac.new(key, msg) # TypeError: missing required argument: 'digestmod' # ระบุ algorithm เสมอ h = hmac.new(key, msg, hashlib.sha256)
สำหรับการตรวจสอบ HMAC อย่างรวดเร็วโดยไม่ต้องเรียกใช้ Python script วาง key และข้อความของคุณใน เครื่องมือ HMAC Generator ออนไลน์ — รองรับ 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.