Random Oracles, Length-Extension Attacks, and Domain Separation TODO
Concept
The random oracle model is a proof methodology that treats a hash function as an idealized function returning a uniformly random output for every input, and proves security under that assumption; real hashes fall short of that ideal, so such proofs only give heuristic assurance. The classic gap case is the length-extension attack: for Merkle-Damgård hashes like SHA-256, the output is effectively the internal state, so an attacker who knows only H(m) and its length — not m itself — can compute H(m || padding || m'). That means using H(k || m) with a prepended secret as a MAC is forgeable; you need a two-stage construction like HMAC, or a sponge construction like Keccak/SHA-3, instead. Domain separation is the principle of tagging or prefixing hash inputs by purpose so hashes from one context can never collide with another, preventing a signature or commitment from being replayed across contexts. Simply concatenating variable-length fields creates ambiguity where different inputs produce the same byte string, so length prefixes or fixed-width encoding are needed to make parsing uniquely determined.
Code that concatenates arbitrary fields before hashing the message to be signed opens the door to replay or forgery attacks — and that's a vulnerability the application introduces, not the library.
Code & Formula
# 랜덤 오라클·길이 연장 공격·도메인 분리 — 장난감 Merkle-Damgard 해시로 실제 forge 를 수행해
# naive H(key||msg) MAC이 위조 가능함을 검증하고, HMAC은 같은 구조로 위조되지 않음을 대조한다.
# (교육용 토이 해시 — 진짜 SHA-256이 아니며 프로덕션 MAC은 반드시 hmac 모듈을 쓸 것)
import hmac
import hashlib
BLOCK = 16 # bytes
def compress(state, block): # 토이 압축 함수 — 진짜 암호학적 강도는 없음, 구조 시연용
x = int.from_bytes(block[:8], "big") ^ int.from_bytes(block[8:], "big")
state = (state ^ x) & 0xFFFFFFFF
return ((state * 2654435761 + 0x9E3779B9) ^ (state >> 15)) & 0xFFFFFFFF
def pad(total_len_bytes, tail):
tail = tail + b"\x80"
while (total_len_bytes + len(tail)) % BLOCK != 8 % BLOCK:
tail += b"\x00"
return tail + (total_len_bytes * 8).to_bytes(8, "big")
def toy_hash(data, state=0x6A09E667):
padded = data + pad(len(data), b"")
for i in range(0, len(padded), BLOCK):
state = compress(state, padded[i : i + BLOCK])
return state
KEY = b"super-secret-16b" # 공격자는 값을 모르지만 길이(16)는 안다고 가정 — 흔한 실전 조건
msg = b"amount=100&to=alice"
tag = toy_hash(KEY + msg) # naive_mac(key, msg) — 서버가 공개하는 값
# --- 공격자: key 없이, tag 와 (key 길이 + msg) 만으로 확장 위조 ---
key_len_guess = 16
orig_len = key_len_guess + len(msg)
glue = pad(orig_len, b"") # 원본 메시지 뒤에 실제로 붙었을 패딩을 그대로 재구성
def resume(state, processed_len, tail_data):
tail = pad(processed_len + len(tail_data), tail_data)
for i in range(0, len(tail), BLOCK):
state = compress(state, tail[i : i + BLOCK])
return state
forged_tag = resume(tag, orig_len + len(glue), b"&admin=true")
forged_message = msg + glue + b"&admin=true" # 공격자가 서버에 제출할, key 없이 만든 메시지
genuine_tag = toy_hash(KEY + forged_message) # 실제로 key를 아는 쪽이 계산하면 이 값이 나온다
print("forged tag == genuine tag (forged without knowing KEY):", forged_tag == genuine_tag)
# --- HMAC은 key를 안팎으로 감싸는 구조라 같은 방식의 확장이 통하지 않는다 ---
hmac_tag = hmac.new(KEY, msg, hashlib.sha256).digest()
hmac_forged = hmac.new(b"?" * 16, forged_message, hashlib.sha256).digest() # key 없이는 흉내조차 불가
print("HMAC has no equivalent forge path (unrelated tags):", hmac_tag != hmac_forged)
# --- 도메인 분리: 같은 바이트열도 용도 태그를 접두사로 넣으면 문맥이 섞이지 않는다 ---
h_mac_ctx = hashlib.sha256(b"mac:" + msg).hexdigest()[:12]
h_sig_ctx = hashlib.sha256(b"sig:" + msg).hexdigest()[:12]
print("domain-separated hashes differ even for the same msg:", h_mac_ctx != h_sig_ctx)
docs/code/algorithms/algorithms-82.py
Exercise
Implement an H(secret || message) MAC with SHA-256, forge it successfully with a public length-extension tool, then switch to HMAC and confirm the forgery fails.
Practical Connection
EIP-712's domain separator (chainId, contract, version) is exactly domain separation — without it, Verex's order signatures could be replayed on a different chain or a different contract.
If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/dev-100-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.