어떤 수업을 찾고 있나요?

예: PowerShell, 네트워크, XSS, Step 138

전체 커리큘럼 둘러보기 →

암호학

Step 232. Cryptopals Set 2: ECB/CBC, 패딩 오라클 — 블록 암호의 허점을 뚫는다

Step 232예상 실습 시간 · 6시간

Level 3 — CTF 실전과 공격 스킬 심화 | 난이도 ★★★★☆ | 예상 소요 시간 6시간

전제: Step 231(Set 1) — 오늘 만드는 오라클 공격들은 전부 지난 챕터의 점수 함수와 블록 감각 위에 섭니다.

⚠️ 이 챕터의 실습은 내 랩·합법 플랫폼 전용입니다. 허가 없는 시스템에 적용하면 범죄입니다.

  • 준비물: 파이썬 3(실측: 3.12.14), pycryptodome, Step 231의 xorlib.py.
  • 주의: Cryptopals(cryptopals.com)는 공개된 합법 학습 문제집입니다. 이 챕터의 모든 오라클(암호화해 주는 서버 역할)은 여러분의 파이썬 안에 직접 구현합니다 — 실제 서버를 공격하는 것이 아니라, 취약한 서버를 "내 랩에 세우고" 깨는 것이 오늘의 방식입니다. 출력은 전부 2026-09-09 실측입니다.

Set 1이 "XOR과 통계"였다면, Set 2는 "블록 암호 모드의 구조적 결함"입니다. ECB는 같은 평문 블록이 같은 암호문 블록이 되는 성질 하나로 "비밀 문자열을 한 글자씩 빼내는" 공격을 허용하고, CBC는 "패딩이 맞나요?"라는 참/거짓 응답 하나가 암호문 전체를 복호화하는 패딩 오라클 공격에 무너집니다. 암호학의 명작 공격들을 오늘 전부 손으로 재현합니다.


1. 학습 목표

이 챕터를 끝내면 다음을 할 수 있습니다:

  • PKCS#7 패딩을 구현하고 유효성 검증의 참/거짓이 왜 정보를 새는지 설명한다
  • CBC 모드를 ECB 프리미티브만으로 직접 구현하고 라이브러리 결과와 대조한다
  • ECB/CBC 탐지 오라클과 바이트 앳 어 타임(byte-at-a-time) 공격으로 비밀 문자열을 추출한다
  • CBC 비트 플리핑으로 평문의 특정 블록을 원하는 값으로 조작한다
  • 패딩 오라클 공격을 구현해 키 없이 CBC 암호문을 복호화한다

2. 배경 지식 — 오늘의 도구와 개념

오늘의 도구 한눈에 보기

구분 내용
언어·환경 파이썬 3 (실측: 3.12.14) + pycryptodome(AES 프리미티브)
오늘의 명령어 AES.new(key, AES.MODE_ECB/CBC), 직접 짜는 pkcs7_pad/unpad, cbc_encrypt/decrypt
필요한 개념 블록 암호 모드(ECB/CBC)의 연결 구조, XOR 연쇄, 오라클 공격
오늘의 산출물 오라클 4종(탐지·바이트 앳 어 타임·비트 플리핑·패딩) 공격 구현체

2-1. PKCS#7 패딩 — 마지막 바이트가 규칙이다

블록 암호는 입력이 16바이트 배수여야 하므로 빈자리를 채웁니다. PKCS#7의 규칙: 부족한 바이트 수 N을 값으로 N개 채웁니다 — 3바이트 부족하면 x03x03x03, 정확히 배수면 16바이트 전체를 x10으로 새 블록 추가. 복호화 후 이 규칙이 맞는지 검사하는데, 이 검사의 참/거짓 응답 자체가 오라클이 됩니다.

2-2. CBC의 연쇄 구조

암호화: C[i] = Enc(P[i] XOR C[i-1])      (C[0]의 자리는 IV)
복호화: P[i] = Dec(C[i]) XOR C[i-1]

핵심 직관: 암호문 블록 C[i]를 바꾸면 평문 블록 P[i+1]이 예측 가능하게 바뀝니다(대신 P[i]는 쓰레기가 됨). 이 성질이 비트 플리핑 공격이고, "Dec(C[i])만 알면 평문"이라는 관찰이 패딩 오라클의 출발점입니다.

2-3. 오라클 공격 — 물어보면 답해주는 시스템

오라클(oracle)은 "질문하면 부분 정보를 답해주는 시스템"입니다. 암호화 오라클은 내 입력을 암호화해 주고, 패딩 오라클은 변조된 암호문의 패딩 유효성을 알려줍니다. 정보 이론적으로 각 응답은 1비트 안팎이지만, 반복 질문으로 누적하면 평문 전체가 새어 나옵니다 — 오늘 실측에서 패딩 오라클은 48바이트 복호화에 6,105회의 질의를 썼습니다.


3. 따라 하기

이 챕터의 모든 출력은 2026-09-09 파이썬 3.12.14 + pycryptodome 실측입니다. 키와 IV는 매 실행마다 무작위이므로 세부 값은 다르게 나옵니다.

3-1. PKCS#7 패딩 구현 (챌린지 9)

BS = 16
def pkcs7_pad(data, bs=BS):
    n = bs - (len(data) % bs)
    return data + bytes([n]) * n

def pkcs7_unpad(data):
    n = data[-1]
    if n == 0 or n > BS or data[-n:] != bytes([n]) * n:
        raise ValueError("invalid padding")
    return data[:-n]

padded = pkcs7_pad(b"YELLOW SUBMARINE")
print("패딩 후:", padded, "-> 길이", len(padded))
try:
    pkcs7_unpad(b"ICE ICE BABY\x05\x05\x05\x05")
except ValueError as e:
    print("잘못된 패딩:", repr(e))
패딩 후: b'YELLOW SUBMARINE\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10' -> 길이 32
잘못된 패딩: ValueError('invalid padding')

출력 읽는 법: 16바이트 정확히 맞는 입력에도 x10 16개의 새 블록이 붙습니다 — "배수면 패딩 생략"은 금지입니다. 그리고 unpad가 거짓을 반환하는 대신 예외를 던지는 것에 주목하세요 — 이 예외가 서버의 에러 응답이 되어 패딩 오라클이 됩니다.

3-2. CBC 직접 구현 (챌린지 10)

라이브러리의 CBC를 쓰지 않고, ECB 프리미티브로 연쇄를 손으로 만듭니다.

from Crypto.Cipher import AES
import secrets

def cbc_encrypt(pt, key, iv):
    c = AES.new(key, AES.MODE_ECB)
    out, prev = b"", iv
    for i in range(0, len(pt), BS):
        blk = bytes(x ^ y for x, y in zip(pt[i:i+BS], prev))  # XOR 후 암호화
        prev = c.encrypt(blk)
        out += prev
    return out

def cbc_decrypt(ct, key, iv):
    c = AES.new(key, AES.MODE_ECB)
    out, prev = b"", iv
    for i in range(0, len(ct), BS):
        blk = ct[i:i+BS]
        out += bytes(x ^ y for x, y in zip(c.decrypt(blk), prev))  # 복호화 후 XOR
        prev = blk
    return out

key, iv = secrets.token_bytes(16), secrets.token_bytes(16)
pt = b"CBC mode chains every block to the previous one!!"
ct = cbc_encrypt(pkcs7_pad(pt), key, iv)
print("내 CBC 왕복:", pkcs7_unpad(cbc_decrypt(ct, key, iv)) == pt)
ref = AES.new(key, AES.MODE_CBC, iv=iv).decrypt(ct)
print("라이브러리 CBC와 일치:", pkcs7_unpad(ref) == pt)
내 CBC 왕복: True
라이브러리 CBC와 일치: True

출력 읽는 법: 손으로 만든 연쇄가 라이브러리와 비트 단위로 일치합니다. 이제 C[i]를 바꾸면 P[i+1]이 어떻게 바뀌는지 수식이 손에 있습니다 — 뒤의 두 공격이 이 구조를 직접 이용합니다.

3-3. ECB/CBC 탐지 오라클 (챌린지 11)

모르는 모드로 암호화해 주는 오라클에, 같은 바이트 64개를 넣습니다.

def encryption_oracle(data):
    k = secrets.token_bytes(16)
    mode = secrets.choice(["ECB", "CBC"])
    if mode == "ECB":
        return AES.new(k, AES.MODE_ECB).encrypt(pkcs7_pad(data)), mode
    return AES.new(k, AES.MODE_CBC, iv=secrets.token_bytes(16)).encrypt(pkcs7_pad(data)), mode

def detect_mode(ct):
    blocks = [ct[i:i+BS] for i in range(0, len(ct), BS)]
    return "ECB" if len(set(blocks)) < len(blocks) else "CBC"

ct3, real = encryption_oracle(b"A" * 64)
print("탐지 결과:", detect_mode(ct3), "| 실제 모드:", real, "| 정답:", detect_mode(ct3) == real)
탐지 결과: CBC | 실제 모드: CBC | 정답: True

출력 읽는 법: A 64개는 16바이트 블록 4개가 완전히 같습니다 — ECB라면 암호문도 4개 동일, CBC라면 연쇄 때문에 전부 다릅니다. 여러 번 실행하면 ECB/CBC 양쪽 다 나오는데, 탐지는 매번 정답입니다(실측으로 확인). Step 231의 반복 블록 탐지가 그대로 판별기가 됐습니다.

3-4. ECB 바이트 앳 어 타임 (챌린지 12)

오라클이 내 입력 + 비밀 문자열을 ECB로 암호화해 줍니다. 비밀을 한 글자씩 뺍니다.

import base64
target = base64.b64decode(b"Um9sbGluJyBpbiBteSA1LjA=")   # 오라클 안에만 존재하는 비밀
okey = secrets.token_bytes(16)
def oracle_ecb(user):
    return AES.new(okey, AES.MODE_ECB).encrypt(pkcs7_pad(user + target))

recovered = b""
for i in range(len(target)):
    pad_len = BS - 1 - (len(recovered) % BS)
    prefix = b"A" * pad_len
    base = oracle_ecb(prefix)[:pad_len + len(recovered) + 1]
    for g in range(256):
        trial = oracle_ecb(prefix + recovered + bytes([g]))[:pad_len + len(recovered) + 1]
        if trial == base:
            recovered += bytes([g]); break
print("바이트 앳 어 타임 추출:", recovered)
바이트 앳 어 타임 추출: b"Rollin' in my 5.0"

출력 읽는 법: 원리는 이렇습니다. A를 15개 넣으면 첫 블록은 A×15 + 비밀[0] — 비밀 첫 글자가 블록 끝에 옵니다. 이 암호문 블록과 같은 블록이 나오는 A×15 + 추측글자를 256번 시도해 찾으면 첫 글자 확정. 다음은 A 14개 + 지금까지 밝힌 글자 + 추측글자. 같은 입력=같은 암호문이라는 ECB 성질이 사전 대조표 역할을 합니다. 비밀 16글자가 정확히 추출됐습니다.

3-5. CBC 비트 플리핑 (챌린지 16)

목표: 복호화 결과에 ;admin=true;를 심습니다. 사용자 입력은 세미콜론이 제거되므로 정상 경로로는 불가능합니다.

bkey, biv = secrets.token_bytes(16), secrets.token_bytes(16)
def enc_userdata(u):
    u = u.replace(b";", b"").replace(b"=", b"")
    pt = b"comment1=cooking%20MCs;userdata=" + u + b";comment2=%20like%20a%20pound%20of%20bacon"
    return AES.new(bkey, AES.MODE_CBC, iv=biv).encrypt(pkcs7_pad(pt))

ct5 = enc_userdata(b"A" * 32)
print("조작 전 admin 검사:", b";admin=true;" in pkcs7_unpad(
    AES.new(bkey, AES.MODE_CBC, iv=biv).decrypt(ct5)))

mod = bytearray(ct5)
inject = b";admin=true;AAAA"   # 정확히 16바이트
orig   = b"A" * 16            # 3번째 블록 평문(우리가 아는 값)
for i in range(BS):
    mod[BS + i] ^= orig[i] ^ inject[i]   # 이전 블록 조작 -> 다음 블록 평문 치환
pt2 = pkcs7_unpad(AES.new(bkey, AES.MODE_CBC, iv=biv).decrypt(bytes(mod)))
print("조작 후 admin 검사:", b";admin=true;" in pt2)
print("조작 후 평문 일부:", pt2[32:64])
조작 전 admin 검사: False
조작 후 admin 검사: True
조작 후 평문 일부: b';admin=true;AAAAAAAAAAAAAAAAAAAA'

출력 읽는 법: P[3] = Dec(C[3]) XOR C[2]이므로 C[2]에 orig XOR inject를 XOR하면 P[3]이 inject로 치환됩니다. 대가로 P[2]는 쓰레기가 되지만(연쇄가 끊긴 자리), 검사는 통과했습니다. 키도 IV도 모른 채 평문의 한 블록을 원하는 값으로 바꿨습니다.

3-6. 패딩 오라클 (챌린지 17)

오라클은 "이 암호문의 패딩이 유효한가?"에만 참/거짓으로 답합니다.

pkey, piv = secrets.token_bytes(16), secrets.token_bytes(16)
pct = AES.new(pkey, AES.MODE_CBC, iv=piv).encrypt(pkcs7_pad(b"Attack at dawn!! The padding oracle talks."))

def padding_oracle(ct):
    try:
        pkcs7_unpad(AES.new(pkey, AES.MODE_CBC, iv=piv).decrypt(ct))
        return True
    except ValueError:
        return False

def attack_block(prev, cur):
    inter = bytearray(BS); plain = bytearray(BS)
    for pos in range(BS - 1, -1, -1):
        pad = BS - pos
        crafted = bytearray(prev)
        for j in range(pos + 1, BS):
            crafted[j] = inter[j] ^ pad
        for g in range(256):
            if g == prev[pos] and pos == BS - 1:
                continue   # 원문 그대로 → 진짜 패딩이 유효한 함정 후보
            crafted[pos] = g
            if padding_oracle(bytes(crafted) + cur):
                if pos > 0:   # 검증: 앞 바이트를 뒤집어도 유효하면 진짜
                    crafted[pos - 1] ^= 1
                    ok = padding_oracle(bytes(crafted) + cur)
                    crafted[pos - 1] ^= 1
                    if not ok: continue
                inter[pos] = g ^ pad
                plain[pos] = prev[pos] ^ inter[pos]
                break
    return bytes(plain)

blocks = [piv] + [pct[i:i+BS] for i in range(0, len(pct), BS)]
out = b"".join(attack_block(blocks[i-1], blocks[i]) for i in range(1, len(blocks)))
print("패딩 오라클 복구:", pkcs7_unpad(out))
패딩 오라클 복구: b'Attack at dawn!! The padding oracle talks.'

출력 읽는 법: 마지막 바이트부터 공략합니다. prev[15]를 조작해 복호화 마지막 바이트가 x01(유효 패딩)이 되는 g를 찾으면, 중간값 inter[15] = g ^ 1이고 평문은 prev[15] ^ inter[15]입니다. 다음은 x02x02를 목표로 두 바이트째. 바이트당 최대 256회, 실측 총 질의는 48바이트에 6,105회였습니다 — 참/거짓 1비트짜리 응답이 평문 전체를 새게 했습니다. 함정 후보(원문 그대로)와 검증 쿼리 두 줄이 없으면 마지막 블록에서 오판하니 주석의 위치를 확인하세요.


4. 미션과 연습문제

미션 — 오라클 공격 종합 세트

  1. oracle_attacks.py에 오늘의 네 공격을 함수로 정리합니다 — detect_mode, ecb_byte_at_a_time(oracle), cbc_bit_flip(ct, orig, inject, offset), padding_oracle_attack(ct, iv, oracle)
  2. 각 함수의 호출 횟수(오라클 질의 수)를 세어 출력합니다 — 패딩 오라클이 바이트당 평균 ~128회(실측 6,105회/48바이트)인 것을 확인합니다
  3. 네 공격 전부를 "방어 코드 한 줄"과 짝지어 주석으로 씁니다 — 예: 패딩 오라클 → "복호화 전에 MAC 검증(Encrypt-then-MAC)"
  4. (선택) 원본 Cryptopals Set 2의 챌린지 12·16·17에 같은 함수를 적용해 보세요

연습문제

문제 1. PKCS#7에서 입력이 정확히 16바이트 배수일 때도 16바이트 전체를 추가하는 이유는 무엇인가요? 생략하면 복호화 쪽에 어떤 모호함이 생기는지 답해 보세요.

문제 2. 바이트 앳 어 타임 공격이 ECB에서는 되고 CBC에서는 안 되는 이유를, "같은 입력 블록 → 같은 출력 블록" 성질의 유무로 설명해 보세요.

문제 3. 비트 플리핑에서 C[2]를 조작해 P[3]을 바꿨을 때 P[2]가 쓰레기가 되는 이유를 CBC 복호화 수식으로 보여 주세요.

문제 4. 패딩 오라클 공격이 "복호화 결과의 마지막 바이트"부터 확정하는 이유를, 패딩 검증이 끝에서 시작된다는 관찰로 설명해 보세요.


해설과 완료 기준 · 펼치기/접기

5. 모범 답안과 완료 기준

미션 모범 답안

def padding_oracle_attack(ct, iv, oracle):
    blocks = [iv] + [ct[i:i+BS] for i in range(0, len(ct), BS)]
    return b"".join(attack_block(blocks[i-1], blocks[i], oracle)
                    for i in range(1, len(blocks)))

판정 기준: ① 네 함수 모두 본문 실측과 같은 결과(비밀 추출·admin 심기·평문 복구) ② 질의 카운터가 실제로 증가(패딩 오라클 수천 회) ③ 방어 주석이 공격의 전제를 정확히 겨냥할 것 — 예: 바이트 앳 어 타임의 방어는 "비밀과 사용자 입력을 같은 블록에 섞지 않기 + ECB 폐기"입니다.

검증하는 법: 키를 새로 뽑아 두 번 실행하세요. 추출 결과가 키와 무관하게 성립해야 진짜 공격이고, 특정 키에서만 되면 구현 버그입니다.

연습문제 해답

문제 1 해답. 패딩 생략이 허용되면, 원문이 x01로 끝나는 16바이트 배수 메시지를 복호화할 때 "이 x01이 패딩인가 원문인가"를 구분할 방법이 없습니다. 항상 패딩을 추가하면 "마지막 바이트 값 N만큼은 무조건 패딩"이라는 규칙이 성립해 unpad가 결정론적이 됩니다.

문제 2 해답. 공격의 핵심 장치는 "추측 평문 블록과 같은 암호문 블록이 나오는지" 대조하는 사전표입니다. ECB는 블록이 독립이라 이 대조가 성립하지만, CBC는 앞 블록(또는 IV)이 매번 연쇄에 섞여 같은 평문 블록도 다른 암호문이 되므로 사전표를 만들 수 없습니다.

문제 3 해답. P[2] = Dec(C[2]) XOR C[1]인데 C[2]를 조작했으므로 Dec(C[2])가 원래와 무관한 무작위 값이 됩니다 — 블록 암호의 눈사태 효과로 한 비트 차이도 출력 전체를 뒤집습니다. 반면 P[3] = Dec(C[3]) XOR C[2]에서 Dec(C[3])은 그대로고 C[2]만 바뀌었으니, 바꾼 만큼 정확히 반영됩니다. 한 블록은 깨지고 다음 블록은 조종되는 것이 CBC 조작의 비용 구조입니다.

문제 4 해답. 유효한 패딩의 최소 형태는 마지막 1바이트 x01입니다. 그래서 마지막 바이트만 목표로 삼으면(나머지는 아무 값이어도 됨) 단일 바이트 조작으로 참/거짓 신호를 얻을 수 있습니다. 마지막 바이트가 확정되면 그다음 목표는 x02x02 — 목표 패딩이 한 칸씩 앞으로 자라는 형태라, 공격은 반드시 끝에서 시작해 앞으로 진행됩니다.

완료 기준 체크리스트

  • [ ] PKCS#7 pad/unpad를 구현하고 잘못된 패딩에서 ValueError를 확인했다
  • [ ] CBC를 ECB 프리미티브로 구현해 라이브러리와 일치를 봤다
  • [ ] 반복 블록 수로 ECB/CBC를 구별했다
  • [ ] 바이트 앳 어 타임으로 비밀 문자열 16바이트를 추출했다
  • [ ] 비트 플리핑으로 ;admin=true;를 심어 검사를 통과했다
  • [ ] 패딩 오라클로 키 없이 전체 평문을 복구했다
  • [ ] "암호문 블록 i 조작 → 평문 블록 i+1 조종, 블록 i 붕괴"를 수식으로 설명할 수 있다
  • [ ] 미션: 4개 공격 함수화 + 질의 횟수 출력 확인

6. 흔한 실수와 해결

벽 1. ValueError: Data must be aligned to block boundary in ECB mode

증상: ValueError: Data must be aligned to block boundary in ECB mode (2026-09-09 실측).
원인: pycryptodome은 패딩을 자동으로 해 주지 않습니다 — 16바이트 배수가 아닌 입력을 그대로 encrypt에 넣은 것입니다.
해결: 항상 pkcs7_pad를 거치세요. 이 에러가 "라이브러리가 패딩을 안 해준다"는 것을 알려주는 신호이며, 3-1을 먼저 구현하는 이유입니다.

벽 2. ValueError: Incorrect AES key length (5 bytes)

증상: ValueError: Incorrect AES key length (5 bytes) (2026-09-09 실측).
원인: AES 키는 16/24/32바이트만 허용됩니다. "YELLOW SUBMARINE"처럼 정확히 16바이트인지 확인하세요.
해결: len(key)를 출력해 확인하고, 임의 키는 secrets.token_bytes(16)을 쓰세요.

벽 3. CBC에서 IV를 빼먹었다

증상: AES.new(key, AES.MODE_CBC)가 에러 없이 되는데, 복호화가 첫 블록부터 깨집니다.
원인: pycryptodome은 IV 생략 시 무작위 IV를 자동 생성합니다 — 암호화 때 만든 IV를 모르면 복호화 첫 블록이 쓰레기가 됩니다.
해결: CBC는 항상 IV를 명시하고, IV를 암호문 앞에 붙여 보관하는 관행(iv + ct)을 들이세요. 3-2의 직접 구현이 이 구조 그대로입니다.

벽 4. 패딩 오라클이 마지막 블록에서 오판한다

증상: 복구 결과의 끝쪽이 x00이나 깨진 문자입니다.
원인: 마지막 바이트 공략에서 "원문 그대로"(g == prev[pos])는 진짜 패딩이 유효해서 True가 나오는 함정 후보입니다 — 목표 패딩 x01이 아닌 실제 패딩 길이로 통과합니다.
해결: 3-6의 두 장치 — ① 마지막 바이트에서 원문 그대로 후보 건너뛰기, ② 앞 바이트를 뒤집는 검증 쿼리 — 를 빠뜨리지 마세요. 검증 쿼리는 질의 수를 늘리지만 정확도를 보장합니다(실측 6,105회에 포함).

벽 5. 바이트 앳 어 타임에서 recovered가 중간에 멈춘다

증상: 일부 글자만 추출되고 루프가 break 없이 끝납니다.
원인: 블록 경계를 넘을 때 pad_len 계산이 틀렸거나, 대조 범위([:pad_len + len(recovered) + 1])가 블록 경계를 넘어 다음 블록까지 비교하고 있습니다.
해결: 비교는 "현재 작업 중인 블록까지만" 해야 합니다 — 3-4의 슬라이스 범위를 그대로 쓰고, 17바이트 이상의 비밀에서 경계 넘기를 반드시 시험하세요(실측 비밀은 정확히 17바이트라 경계를 넘습니다).


7. 정리

오늘의 개념

개념 한 줄 설명
PKCS#7 부족한 수 N을 N개 채우는 패딩 — 유효성 검사가 오라클이 됨
CBC 연쇄 C[i] = Enc(P[i] ⊕ C[i-1]) — 연결이 곧 조작점
바이트 앳 어 타임 ECB의 결정론적 성질로 비밀을 한 글자씩 사전 대조
비트 플리핑 C[i] 조작 → P[i+1] 정확히 조종(대신 P[i] 붕괴)
패딩 오라클 패딩 참/거짓 1비트로 중간값 역산, 평문 전체 복구(실측 ~128회/바이트)

오늘의 명령어·코드

명령 하는 일
pkcs7_pad / pkcs7_unpad 패딩 부착·검증 — 오라클의 심장
AES.new(key, AES.MODE_CBC, iv=iv) CBC 라이브러리(대조용)
crafted[j] = inter[j] ^ pad 목표 패딩으로 꾸민 조작 블록
bytes(x ^ y for x, y in zip(a, b)) 바이트열 XOR — 오늘 모든 공격의 기본 연산
oracle(prefix + recovered + bytes([g])) 한 글자 추측 사전 쿼리

명령어보다 중요한 감각

오늘의 네 공격은 모두 같은 구조입니다 — "시스템이 무심코 답해주는 것"을 질문으로 조직한 것. ECB의 결정론, CBC의 연쇄, 패딩 검증의 참/거짓은 각자 무해해 보이지만, 반복 질문과 결합하면 평문이 통째로 샙니다. 그래서 현대 암호 설계의 답은 "질문을 못 하게 하기" — 인증된 암호(AEAD)로 변조된 암호문은 복호화 전에 폐기하는 것입니다. 이 답의 구조가 다음 챕터 AES 총정리의 주제입니다.


전부 체크되면 Step 232 완료입니다.

한 단계 더 가까이

수업을 마쳤나요?

본문의 완료 기준을 확인한 뒤 직접 표시하세요.

화면이나 링크에 문제가 있나요? 알려주세요.
다음 수업