본문으로 건너뛰기
김신건의 로그

[Python] hashlib, secrets, hmac: 해시와 암호 보안

· 수정 · 📖 약 1분 · 530자/단어 #python #hashlib #secrets #hmac #security #stdlib
python hashlib, secrets, hmac, SHA-256, password hash, crypto safe random

정의

암호학 관련 표준 모듈 3개.

  • hashlib: 해시 함수 (SHA-256, MD5, BLAKE2 등)
  • secrets: 암호학적으로 안전한 난수 (random은 비안전)
  • hmac: HMAC 메시지 인증

암호화(대칭/비대칭 암호)는 표준 라이브러리에 없음 → cryptography 패키지 사용.

hashlib 기본

import hashlib

data = b"hello world"

print(hashlib.sha256(data).hexdigest())
print(hashlib.sha512(data).hexdigest())
print(hashlib.md5(data).hexdigest())     # 보안 X, 무결성 체크만
print(hashlib.blake2b(data).hexdigest())

해시는 단방향. 같은 입력 → 같은 출력. 작은 입력 변경에도 출력 완전 변경.

update로 큰 데이터 처리

import hashlib

h = hashlib.sha256()
with open("big.iso", "rb") as f:
    while chunk := f.read(64 * 1024):
        h.update(chunk)
print(h.hexdigest())

스트리밍 처리. 메모리 일정.

3.11+ 편의 함수:

with open("big.iso", "rb") as f:
    digest = hashlib.file_digest(f, "sha256")
print(digest.hexdigest())

알고리즘 선택

알고리즘권장
SHA-256, SHA-512일반 무결성, 디지털 서명
BLAKE2b, BLAKE3빠르고 안전 (SHA-3 대안)
SHA-3차세대 표준
MD5, SHA-1충돌 발견됨, 보안 X (체크섬용만)
bcrypt, scrypt, Argon2비밀번호 해싱 전용
hashlib.algorithms_guaranteed   # OS 무관 항상 지원
hashlib.algorithms_available    # 시스템 의존

비밀번호 해싱 - 일반 해시 절대 사용 X

# WRONG: 비밀번호에 일반 해시
hashlib.sha256(password.encode()).hexdigest()
# → GPU로 초당 수십억 시도 가능 (브루트 포스)

비밀번호는 **느린 KDF (Key Derivation Function)**을 써야 한다.

표준 라이브러리: scrypt, pbkdf2

import hashlib
import secrets

salt = secrets.token_bytes(16)
password = b"my-secret-password"

# scrypt (느림, 메모리 hard)
key = hashlib.scrypt(password, salt=salt, n=2**14, r=8, p=1, dklen=32)

# pbkdf2 (느림 = 보안)
key = hashlib.pbkdf2_hmac("sha256", password, salt, iterations=600_000)

저장: salt + iterations + hash. 검증 시 같은 salt/iter로 다시 계산해 비교.

Argon2 (현대 권장)

pip install argon2-cffi
from argon2 import PasswordHasher

ph = PasswordHasher()
hash = ph.hash("password")
print(hash)
# $argon2id$v=19$m=65536,t=3,p=4$...

try:
    ph.verify(hash, "password")
    print("OK")
except VerifyMismatchError:
    print("Wrong password")

Argon2는 비밀번호 해싱 전용 최신 표준 (OWASP 권장).

secrets: 안전한 난수

random 모듈은 비결정 시드에서도 통계용. 보안엔 secrets.

import secrets

# 토큰 생성
print(secrets.token_bytes(16))            # 16바이트
print(secrets.token_hex(16))              # 32자 hex
print(secrets.token_urlsafe(16))          # URL 안전 문자열

# 정수
print(secrets.randbelow(100))             # 0 ~ 99
print(secrets.choice([1, 2, 3, 4]))       # 랜덤 선택

# 비밀번호 생성
import string
alphabet = string.ascii_letters + string.digits
password = "".join(secrets.choice(alphabet) for _ in range(20))

세션 토큰, API 키, 비밀번호 재설정 토큰 등에 사용.

hmac: 메시지 인증

해시 + 비밀 키로 메시지가 변조 안 됐고 발신자가 키 소유자임을 검증.

import hmac
import hashlib

key = b"shared-secret"
message = b"transfer 100 to alice"

# 서명 생성
signature = hmac.new(key, message, hashlib.sha256).hexdigest()
print(signature)

# 검증
received_signature = signature
expected = hmac.new(key, message, hashlib.sha256).hexdigest()

if hmac.compare_digest(received_signature, expected):
    print("authentic")
else:
    print("tampered")

compare_digest

타이밍 공격 방지. 두 문자열 길이 무관 일정 시간 비교. 일반 == 쓰면 안 됨.

# WRONG: 타이밍 공격 가능 (다른 문자 위치 따라 시간 다름)
if signature == expected:
    ...

# CORRECT
if hmac.compare_digest(signature, expected):
    ...

자주 보는 패턴

파일 무결성 (체크섬)

import hashlib

def file_sha256(path):
    h = hashlib.sha256()
    with open(path, "rb") as f:
        while chunk := f.read(64 * 1024):
            h.update(chunk)
    return h.hexdigest()

print(file_sha256("download.iso"))
# 공식 사이트 해시와 비교

CSRF 토큰

import secrets

def generate_csrf_token():
    return secrets.token_urlsafe(32)

# 폼에 hidden field로
# 제출 시 세션의 토큰과 hmac.compare_digest로 비교

API 서명 (HMAC)

import hmac
import hashlib
import time

def sign_request(secret, method, path, body, ts):
    msg = f"{method}\n{path}\n{ts}\n{body}".encode()
    return hmac.new(secret.encode(), msg, hashlib.sha256).hexdigest()

# 클라이언트
ts = str(int(time.time()))
sig = sign_request(SECRET, "POST", "/api/transfer", "amount=100", ts)
# Header: X-Timestamp: ts, X-Signature: sig

# 서버 검증
expected = sign_request(SECRET, "POST", "/api/transfer", "amount=100", ts)
if not hmac.compare_digest(sig, expected):
    raise Unauthorized

AWS Signature V4, GitHub Webhook 등이 이 패턴.

비밀번호 저장 (Argon2)

from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError

ph = PasswordHasher()

# 가입
def register(username, password):
    hash = ph.hash(password)
    db.save_user(username, hash)

# 로그인
def login(username, password):
    user = db.get_user(username)
    try:
        ph.verify(user.hash, password)
        if ph.check_needs_rehash(user.hash):    # 옵션 변경 시 재해시
            user.hash = ph.hash(password)
            db.update(user)
        return user
    except VerifyMismatchError:
        return None

보안 체크리스트

  • 비밀번호는 절대 일반 해시 X. Argon2 / scrypt / pbkdf2.
  • 토큰·세션은 secrets. random 금지.
  • HMAC 검증은 hmac.compare_digest. == 금지.
  • MD5/SHA-1은 보안용 X. 무결성 체크만.
  • 솔트는 사용자별 고유. 전역 솔트 의미 없음.
  • 솔트는 secrets.token_bytes로. 시간/이름 기반 X.
  • 비밀 키는 환경변수 / KMS / Vault. 코드에 하드코딩 X.

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기