← ~/notes · 3 min read

DES를 직접 구현하면서 부품을 망가뜨려보기 — '왜 16라운드인가'

라운드 수를 줄이면 어떻게 되나, S-box를 항등으로 바꾸면 어떻게 되나. 부품을 하나씩 망가뜨려보면 보안의 출처가 보인다.

목차
  1. DES 사양 한 장 정리
  2. 직접 구현
  3. 실험 1 — 라운드를 줄이면?
  4. 실험 2 — S-box를 항등함수로 바꾸면?
  5. 실험 3 — IP/FP를 빼면?
  6. 실험 4 — 취약 키 (Weak Key)
  7. 만들면서 알게 된 것들
  8. 한 줄 결론

암호 알고리즘 강의에서 DES를 배우면 보통 “16라운드”, “S-box 8개”, “Feistel 구조”라는 사실을 외운다. 외우긴 외우는데 — 왜 그래야 하는가는 안 외운다.

직접 만든 다음에 부품을 하나씩 망가뜨리면 보인다.


DES 사양 한 장 정리

항목값
블록 크기64 bit
키 크기 (명목)64 bit
키 크기 (실효)56 bit (8 bit는 패리티)
라운드 수16
라운드 키48 bit (PC-2 압축 출력)
S-box8개, 각각 6 bit 입력 → 4 bit 출력
구조Feistel

직접 구현

def des_encrypt(plaintext_64: bytes, key_64: bytes) -> bytes:
    # 1) IP (Initial Permutation)
    block = permute(plaintext_64, IP_TABLE)

    # 2) 키 스케줄: 56-bit → 16개 48-bit 라운드 키
    round_keys = key_schedule(key_64)

    # 3) 16 라운드 Feistel
    L, R = block[:32], block[32:]
    for k in round_keys:
        L, R = R, xor(L, f_function(R, k))

    # 4) 좌우 스왑 후 FP (Final Permutation)
    return permute(R + L, FP_TABLE)


def f_function(R32: bits, k48: bits) -> bits:
    # R(32) → 48 (Expansion E-box) → XOR 라운드 키 → S-box 8개 → P-box
    expanded = permute(R32, E_TABLE)        # 48 bit
    xored    = xor(expanded, k48)           # 48 bit
    s_out    = sbox_substitute(xored)       # 32 bit (6 → 4 ×8)
    return permute(s_out, P_TABLE)          # 32 bit

전체 구현은 ~250줄. 테이블(IP/FP/E/P/S-box 8개) 박는 게 제일 귀찮다.


실험 1 — 라운드를 줄이면?

Avalanche 효과: 평문 1비트가 바뀌면 암호문은 평균 50%(=32비트) 가량 바뀌어야 한다.

def avalanche(rounds: int, n: int = 1000):
    bits_changed = []
    for _ in range(n):
        p1 = random_64()
        p2 = flip_one_bit(p1, random_position())
        c1 = des_encrypt_n_rounds(p1, KEY, rounds)
        c2 = des_encrypt_n_rounds(p2, KEY, rounds)
        bits_changed.append(hamming(c1, c2))
    return mean(bits_changed)
라운드평균 비트 변화 (이상값 32)
1~6
2~13
4~22
8~31
16~32 ✓

8라운드면 거의 도달한다. 16라운드는 여유로 두는 마진이라기보다, 차분/선형 분석에 대한 안전 계수다. 1990년대 차분 공격이 2^47 chosen plaintext로 16라운드를 깰 수 있음이 알려졌으니, 사실 16라운드도 빠듯하다.


실험 2 — S-box를 항등함수로 바꾸면?

S-box는 DES의 유일한 비선형 구성요소다. 나머지(permutation, XOR, expansion)는 전부 선형.

# 정상 S-box 대신 입력의 lower 4 bits를 그대로 출력
def identity_sbox(input_6bits):
    return input_6bits & 0xF

이렇게 바꿔서 동일한 Avalanche 측정:

구성16-round Avalanche
정상 S-box~32 ✓
항등 S-box~32 (겉으론 같음)

겉으론 비슷하다. 그런데 선형 공격 가능 여부를 확인하면:

# 선형 근사 식: input bits XOR == output bits XOR 의 확률을 1000개 샘플로 측정
def linear_bias(rounds):
    matches = 0
    for _ in range(1000):
        p, k = random_64(), KEY
        c = des_encrypt_n_rounds(p, k, rounds)
        if (p[5] ^ p[12]) == (c[3] ^ c[18]):  # 임의 비트 조합
            matches += 1
    return matches / 1000  # 0.5에 가까울수록 안전
구성1라운드 bias16라운드 bias
정상 S-box0.510.50
항등 S-box0.950.78

S-box를 항등으로 바꾸면 1라운드에서 거의 100% 예측 가능. 16라운드 누적 후에도 0.78로 충분히 깨짐. S-box가 비선형성의 유일한 출처임이 수치로 나온다.


실험 3 — IP/FP를 빼면?

Initial Permutation과 Final Permutation은 보안에 기여 안 한다는 게 정설이다. 직접 빼보면:

def des_no_ip_fp(p, k):
    # IP 생략, 라운드 진행, FP 생략
    L, R = p[:32], p[32:]
    for rk in key_schedule(k):
        L, R = R, xor(L, f_function(R, rk))
    return R + L  # 좌우 스왑만

같은 키/평문에 대한 차이:

구성같은 키/평문에서 결과
정상 DESc4 a3 7d ...
IP/FP 없음1c b2 e5 ... (다른 비트지만)

둘 다 동일한 보안 강도다 (Avalanche, 선형 bias 동일). IP/FP가 들어간 이유는 하드웨어 회로 효율 — 70년대 IBM이 칩 배선을 단순화하려고 넣은 것이지 보안과 무관하다.

DES 시험에서 “IP는 왜 있나?” 물어보면 하드웨어 배선용이라고 답하면 된다. 보안 아니다.


실험 4 — 취약 키 (Weak Key)

DES에는 64개의 weak/semi-weak 키가 있다. 이 키들로 두 번 암호화하면 평문이 그대로 복원된다.

weak keys:
  0x0101010101010101
  0x1F1F1F1F0E0E0E0E
  0xE0E0E0E0F1F1F1F1
  0xFEFEFEFEFEFEFEFE
WEAK = bytes.fromhex('0101010101010101')
plaintext = b'helloDES'
c1 = des_encrypt(plaintext, WEAK)
c2 = des_encrypt(c1,        WEAK)
assert c2 == plaintext  # E(E(P)) == P

원인: 키 스케줄이 weak key에서 모든 라운드 키가 동일하게 되거나, 16라운드 키가 회문 패턴이 된다. 그래서 두 번 암호화하면 자기 자신이 역함수가 됨.

실무적 영향은 작지만 (2^56 키 공간에서 64개), 자체 구현하면 키 검증 단계에서 reject 해야 한다.


만들면서 알게 된 것들

S-box가 모든 것이다. 나머지 부품을 다 빼도 보안은 일부 유지되지만, S-box를 비선형이 아닌 걸로 바꾸면 즉시 무너진다.

16라운드는 마진이 아니라 빠듯한 수치. 차분 공격이 2^47이면 8라운드는 일상적 컴퓨터로 깬다. 16이 적당히 안전한 최소치.

IP/FP는 보안이 아닌 배선 편의. 70년대 하드웨어 컨텍스트가 알고리즘에 박혀 있다.

키 스케줄도 직접 짜봐야 weak key의 의미가 와닿는다. “왜 64개?”는 PC-1/PC-2 테이블 직접 다뤄야 보임.


한 줄 결론

알고리즘은 부품을 빼봐야 비로소 이해된다. 이 알고리즘이 왜 안전한가는 이 부품을 빼면 어떻게 깨지는가를 통해서만 보인다.