다운로드 페이지 옆에 붙은 SHA256: 2cf24dba… 같은 긴 문자열, DB에 저장된 비밀번호 칸의 알 수 없는 글자, 웹훅 요청 헤더의 X-Hub-Signature-256. 전부 해시인데, 막상 "MD5랑 SHA-256은 뭐가 다르고, 해시는 암호화랑 뭐가 다르냐"고 물으면 설명이 꼬이더라고요. 결론부터 말하면 해시는 어떤 데이터든 고정 길이 지문으로 바꾸는 단방향 함수라서 원래 값으로 되돌릴 수 없고, 암호화는 키만 있으면 되돌릴 수 있는 양방향 변환이에요. 그리고 MD5와 SHA-1은 서로 다른 두 입력이 같은 지문을 갖는 "충돌"이 실제로 만들어져서 보안 용도로는 끝났고, 지금 기본값은 SHA-256입니다.
이 글에서는 해시·암호화·인코딩의 차이, MD5·SHA-1·SHA-256의 길이와 안전성 비교, 다운로드한 파일의 체크섬을 운영체제별로 확인하는 명령어, MD5와 SHA-1이 깨진 실제 사례, 비밀번호 저장에 SHA-256이 아니라 bcrypt·Argon2를 써야 하는 이유, 그리고 API 서명에 쓰는 HMAC까지 순서대로 정리할게요. 문자열을 바로 해시해 보고 싶다면 해시 생성기에서 알고리즘별 결과를 한 번에 볼 수 있어요.
핵심 요약
해시는 단방향 지문이에요. 같은 입력은 항상 같은 값, 한 글자만 바꿔도 완전히 다른 값이 나오고, 결과에서 원래 입력을 계산해 낼 수 없어요.
MD5(128비트)는 2004년, SHA-1(160비트)은 2017년 SHAttered로 실제 충돌이 공개됐어요. 보안·서명 용도는 SHA-256 이상을 쓰세요.
파일 체크섬은 Windows
certutil -hashfile 파일 SHA256, macOSshasum -a 256, 리눅스sha256sum으로 확인해요.비밀번호는 빠른 해시(SHA-256)가 아니라 일부러 느린 bcrypt·Argon2id로 저장하고, 메시지 위변조 확인에는 키가 들어간 HMAC을 써요.
해시 함수란? 암호화·인코딩과의 차이
해시 함수는 길이가 제각각인 입력을 받아 항상 같은 길이의 값을 내놓는 함수예요. "hello" 다섯 글자를 넣어도, 4GB짜리 ISO 파일을 넣어도 SHA-256 결과는 똑같이 256비트(16진수 64자)예요. 실제로 계산해 보면 이렇습니다.
| 입력 | SHA-256 결과 (16진수 64자) |
|---|---|
hello | 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 |
Hello | 185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969 |
hello. | 1589999b0ca6ef8814283026a9f166d51c70a910671c3d44049755f07f2eb910 |
| (빈 문자열) | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 |
hello 와 Hello 는 첫 글자 대소문자 하나 차이인데, 결과 256비트 중 125비트가 달라요. 거의 절반이 뒤집힌 거죠. 이걸 눈사태 효과(avalanche effect)라고 하고, 좋은 해시의 기본 조건이에요. 결과만 보고 "입력이 비슷했겠구나"를 짐작할 수 없어야 하니까요. 빈 문자열의 해시 e3b0c442… 는 로그에서 자주 보이니 기억해 두면 좋아요.
암호용 해시 함수는 세 가지 성질을 가져야 해요.
- 역상 저항성: 해시값만 보고 원래 입력을 찾을 수 없어야 해요.
- 제2 역상 저항성: 특정 입력과 같은 해시를 갖는 다른 입력을 찾을 수 없어야 해요.
- 충돌 저항성: 해시가 같은 아무 두 입력 쌍이라도 찾을 수 없어야 해요. MD5와 SHA-1이 무너진 게 바로 이 성질이에요.
해시 vs 암호화 vs 인코딩
세 가지가 자주 섞여 쓰이는데 목적이 완전히 달라요.
| 구분 | 되돌리기 | 키 필요 | 목적 | 예 |
|---|---|---|---|---|
| 인코딩 | 누구나 가능 | 없음 | 형식 변환 | Base64, URL 인코딩 |
| 암호화 | 키 있으면 가능 | 있음 | 비밀 유지 | AES, RSA |
| 해시 | 불가능 | 없음(HMAC은 있음) | 무결성·식별 | SHA-256, MD5 |
"비밀번호를 MD5로 암호화했다"는 말은 엄밀히 틀린 표현이에요. 해시는 복호화가 없어요. 대신 같은 입력이면 같은 값이 나오니까, 사람들이 자주 쓰는 비밀번호를 미리 해시해 둔 표(레인보우 테이블)와 대조하면 원문이 드러나요. 되돌리는 게 아니라 맞혀 보는 거죠. Base64를 암호화로 착각하는 실수는 Base64는 암호화가 아니다 글에서 따로 다뤘어요.
MD5 SHA-1 SHA-256 차이 한눈에 보기
세 알고리즘의 가장 큰 차이는 출력 길이와 지금도 안전한가예요. 출력이 길수록 우연히 겹칠 확률이 낮아지는데, 생일 문제 때문에 n비트 해시의 충돌은 대략 2^(n/2)번 시도하면 찾을 수 있어요. 그래서 설계상 충돌 저항 강도는 출력 길이의 절반이에요.
| 알고리즘 | 발표 | 출력 | 16진수 길이 | 설계상 충돌 강도 | 현재 상태 |
|---|---|---|---|---|---|
| MD5 | 1992 (RFC 1321) | 128비트 | 32자 | 2⁶⁴ | 충돌 생성이 일반 PC로 수 초. 보안 용도 금지 |
| SHA-1 | 1995 (FIPS 180-1) | 160비트 | 40자 | 2⁸⁰ | 2017년 실제 충돌 공개. NIST는 2030년까지 퇴출 예정 |
| SHA-256 | 2001 (SHA-2) | 256비트 | 64자 | 2¹²⁸ | 알려진 실용 공격 없음. 현재 표준 |
| SHA-512 | 2001 (SHA-2) | 512비트 | 128자 | 2²⁵⁶ | 안전. 64비트 CPU에서 SHA-256보다 빠를 때도 있음 |
| SHA3-256 | 2015 (FIPS 202) | 256비트 | 64자 | 2¹²⁸ | 안전. SHA-2와 내부 구조가 완전히 다름 |
길이로 알고리즘을 짐작하는 건 디버깅할 때 꽤 유용해요. 40자는 Git 커밋 해시 길이이기도 하고, SHA3-256도 64자라 길이만으로 확정할 순 없어요. 참고로 SHA-2는 SHA-224·256·384·512를 묶은 가족 이름이고 숫자는 출력 비트 수예요.
파일 체크섬 확인하는 법 (Windows·macOS·리눅스)
리눅스 ISO나 개발 도구를 받을 때 배포 페이지에 함께 적힌 SHA-256 값을 체크섬이라고 해요. 내가 받은 파일을 직접 해시해서 그 값과 똑같으면, 다운로드 도중 깨지지 않았고 중간에 바뀌지 않았다는 뜻이에요.
# Windows (명령 프롬프트)
certutil -hashfile ubuntu.iso SHA256
# Windows (PowerShell) — 기본 알고리즘이 SHA256
Get-FileHash .\ubuntu.iso
# macOS
shasum -a 256 ubuntu.iso
# 리눅스
sha256sum ubuntu.iso
# 배포처가 준 SHA256SUMS 파일로 한 번에 대조 (리눅스)
sha256sum -c SHA256SUMS --ignore-missing
확인할 때 몇 가지를 조심하세요.
- 알고리즘을 맞추세요. 배포처가 SHA-256을 줬는데 MD5로 계산하면 당연히 달라요. 길이부터 비교해 보면 바로 알 수 있어요.
- 대소문자는 상관없어요.
certutil은 소문자,Get-FileHash는 대문자로 출력하지만 같은 값이에요. - 눈으로 앞 몇 자만 보지 마세요. 비교는 전체 64자를 해야 해요. PowerShell에서는
(Get-FileHash .\파일).Hash -eq "배포처값"처럼 비교식을 쓰면 True/False로 바로 나와요. - 같은 서버의 체크섬은 한계가 있어요. 서버가 해킹당해 파일과 체크섬을 함께 바꿔 놓으면 체크섬은 일치해요. 그래서 우분투 같은 배포처는
SHA256SUMS파일에 GPG 서명(SHA256SUMS.gpg)을 따로 붙여요. 체크섬은 "전송 중 손상"을, 서명은 "배포자가 맞는지"를 확인하는 거예요.
왜 MD5와 SHA-1은 깨졌다고 할까
"깨졌다"는 말은 해시값에서 원래 비밀번호를 복원할 수 있게 됐다는 뜻이 아니에요. 같은 해시값을 갖는 서로 다른 두 파일을 일부러 만들 수 있게 됐다는 뜻이에요. 그게 왜 문제냐면, 전자 서명과 인증서는 문서 전체가 아니라 문서의 해시에 서명하거든요. 해시가 같은 두 문서를 만들 수 있으면, 무해한 문서에 받은 서명을 악성 문서에 그대로 옮겨 붙일 수 있어요.
MD5: 2004년 충돌, 2008년 가짜 인증서
- 2004년 중국 왕샤오윈(Wang Xiaoyun) 연구팀이 MD5의 실제 충돌 쌍을 발표했어요. 이후 기법이 발전해 지금은 일반 노트북으로 몇 초 만에 충돌 쌍을 만들 수 있어요.
- 2008년 연구팀이 PlayStation 3 약 200대로 MD5 충돌을 계산해, 실제 인증기관이 서명한 것처럼 보이는 가짜 CA 인증서를 만드는 데 성공했어요. 이 발표 이후 인증서에서 MD5가 빠르게 퇴출됐어요.
- 2012년 플레임(Flame) 악성코드는 MD5 충돌을 이용해 마이크로소프트 코드 서명 인증서를 위조했어요. 학계의 이론이 실제 공격에 쓰인 대표 사례예요.
SHA-1: 2017년 SHAttered
2017년 2월 구글과 네덜란드 CWI 연구소가 SHAttered 공격을 발표했어요. 내용은 다르지만 SHA-1 해시가 완전히 같은 PDF 파일 두 개를 실제로 만들어 공개한 거예요. 계산량은 SHA-1 연산 약 9.2×10¹⁸번(약 2⁶³)으로, 연구팀 설명으로는 CPU 6,500년치와 GPU 110년치에 해당했어요. 설계상 2⁸⁰번이 필요하다던 것보다 10만 배 이상 적은 양이었죠.
2020년에는 "SHA-1 is a Shambles" 연구가 선택 접두 충돌로 PGP 키 위조까지 시연했어요. 브라우저들은 2017년 무렵 SHA-1 인증서 신뢰를 끊었고, NIST는 2022년에 SHA-1을 2030년 말까지 퇴출하겠다고 발표했어요.
그럼 Git은 왜 아직 SHA-1을 쓸까
Git 커밋 ID는 40자 SHA-1이에요. Git은 SHAttered 직후 충돌 공격 패턴을 감지하는 "hardened SHA-1"으로 바꿨고, 저장소를 SHA-256으로 만드는 옵션(git init --object-format=sha256)도 실험 기능으로 들어가 있어요. 다만 기존 생태계와의 호환 문제로 기본값은 아직 SHA-1이에요.
비밀번호 저장에 SHA-256을 쓰면 안 되는 이유
여기서 많이들 헷갈려요. SHA-256은 안전한 해시인데 왜 비밀번호에는 쓰면 안 될까요? 이유는 너무 빨라서예요. 해시는 원래 빠를수록 좋은 함수인데, 비밀번호에서는 그게 공격자에게 유리해요. DB가 유출되면 공격자는 후보 비밀번호를 하나씩 해시해서 대조하는데, 1초에 몇 번을 시도할 수 있느냐가 곧 뚫리는 속도거든요.
비밀번호 전용 해시는 일부러 느리고, 메모리를 많이 쓰게 만들어서 이 속도를 떨어뜨려요.
| 방식 | 특징 | 권장 설정 예 (OWASP 기준) |
|---|---|---|
| Argon2id | 2015년 비밀번호 해싱 대회 우승. 메모리를 많이 써서 GPU 병렬 공격에 강함 | 메모리 19MiB, 반복 2, 병렬 1 |
| bcrypt | 1999년 발표. 가장 널리 지원됨. 입력 72바이트까지만 사용 | cost 10 이상 |
| scrypt | 메모리 비용을 조절 가능 | N=2¹⁷, r=8, p=1 |
| PBKDF2 | FIPS 인증이 필요한 환경용 | HMAC-SHA256으로 60만 회 반복 |
이 함수들은 솔트(salt)도 자동으로 붙여 줘요. 솔트는 사용자마다 다른 랜덤 값이라, 같은 비밀번호 1234 를 쓴 두 사람도 저장되는 해시가 달라져요. 그래서 레인보우 테이블이 통하지 않고, 공격자는 사용자 한 명씩 따로 계산해야 해요. bcrypt 결과 $2b$12$… 에는 알고리즘, cost(12), 솔트, 해시가 한 문자열에 다 들어 있어서 따로 솔트 칸을 만들 필요도 없어요.
비밀번호 길이가 왜 중요한지, 몇 자리면 무차별 대입에 몇 년이 걸리는지는 비밀번호 엔트로피와 무차별 대입 시간 글에서 계산해 두었어요.
HMAC, 키가 들어간 해시로 위변조 막기
웹훅이나 API 요청이 정말 상대방이 보낸 건지 확인하려면 해시만으로는 부족해요. 요청을 가로챈 사람도 본문을 고친 뒤 SHA-256을 다시 계산해서 붙이면 그만이니까요. 그래서 서로만 아는 비밀 키를 섞어 해시하는 HMAC을 써요.
import hmac, hashlib
secret = b"secret"
body = b"hello"
sig = hmac.new(secret, body, hashlib.sha256).hexdigest()
# 88aab3ede8d3adf94d26ab90d3bafd4a2083070c3bcce9c014ee04a443847c0b
# 검증할 땐 == 대신 상수 시간 비교
hmac.compare_digest(sig, received_sig)
GitHub 웹훅의 X-Hub-Signature-256 헤더, 결제 대행사의 콜백 서명, JWT의 HS256 서명이 모두 HMAC-SHA256이에요. JWT 서명이 어떻게 만들어지는지는 JWT 구조와 서명 원리 글에 자세히 있어요.
HMAC을 직접 흉내 내서 SHA256(키 + 메시지) 로 만들면 안 돼요. SHA-256 같은 구조의 해시는 길이 확장 공격에 약해서, 키를 몰라도 원래 해시값과 메시지 길이만 알면 뒤에 내용을 덧붙인 메시지의 올바른 해시를 계산할 수 있거든요. HMAC은 키를 안쪽·바깥쪽으로 두 번 섞는 구조(RFC 2104)라서 이 문제가 없어요. 그리고 서명을 비교할 땐 일반 == 대신 hmac.compare_digest 같은 상수 시간 비교를 써야 앞 글자부터 맞혀 나가는 타이밍 공격을 피할 수 있어요.
상황별로 어떤 해시를 쓸까
| 상황 | 추천 | 피할 것 |
|---|---|---|
| 다운로드 파일 무결성 | SHA-256 (+ GPG 서명) | MD5, SHA-1 |
| 비밀번호 저장 | Argon2id, bcrypt | SHA-256, MD5 (솔트를 붙여도) |
| API·웹훅 서명 | HMAC-SHA256 | SHA256(키+메시지) |
| 캐시 키·중복 제거 (공격자 없음) | MD5, SHA-1, xxHash도 가능 | — |
| 전자 서명·인증서 | SHA-256 이상 | SHA-1 |
| 랜덤 토큰 생성 | 암호학적 난수(crypto) | 시각이나 ID를 해시한 값 |
마지막 줄도 자주 하는 실수예요. md5(user_id + 현재시각) 으로 만든 재설정 토큰은 입력을 추측해 똑같이 계산할 수 있어요. 토큰은 secrets.token_urlsafe() 같은 난수로 만드세요.
자주 묻는 질문
해시값으로 원래 문자열을 복호화할 수 있나요?
수학적으로 되돌리는 방법은 없어요. 다만 "1234", "password"처럼 흔한 입력은 미리 계산해 둔 표와 대조하면 금방 찾아져요. 온라인 "MD5 복호화" 사이트가 하는 일이 바로 이 대조예요. 그래서 짧거나 흔한 값을 해시한 결과는 사실상 공개된 것과 같아요.
MD5와 SHA-256 중 무엇이 더 빠른가요?
일반적으로 MD5가 더 빨라요. GPU 벤치마크에서도 MD5가 SHA-256보다 7배쯤 빠르게 나와요. 하지만 파일 체크섬 정도면 둘 다 디스크 읽기 속도가 병목이라 체감 차이가 거의 없으니, 보안이 걸린 곳에선 SHA-256을 쓰세요.
같은 파일인데 체크섬이 다르게 나와요.
먼저 알고리즘이 같은지(길이로 확인), 다운로드가 끝까지 됐는지(파일 크기 비교) 보세요. 텍스트 파일이라면 줄바꿈(CRLF와 LF)이나 BOM 차이로 바이트가 달라졌을 수 있어요. 그래도 다르면 다른 미러에서 다시 받고, 계속 다르면 그 파일은 쓰지 않는 게 안전해요.
비밀번호를 SHA-256으로 두 번, 천 번 해시하면 안전해지나요?
조금 느려지긴 하지만 직접 만든 방식은 솔트·반복 횟수·비교에서 실수가 나기 쉬워요. 이미 검증된 Argon2id나 bcrypt 라이브러리를 쓰는 게 정답이에요. 반복 방식이 꼭 필요하면 표준인 PBKDF2를 쓰세요.
한글 문자열을 해시하면 왜 사이트마다 결과가 다른가요?
해시는 글자가 아니라 바이트를 계산하기 때문이에요. 같은 "안녕"도 UTF-8과 EUC-KR은 바이트가 달라서 해시가 달라져요. UTF-8 기준 "안녕"의 SHA-256은 e8f817f3…으로 시작해요. 앞뒤 공백이나 줄바꿈이 섞여도 결과가 바뀌니 입력부터 확인하세요.
정리
해시는 되돌릴 수 없는 고정 길이 지문이고, 암호화는 키로 되돌리는 잠금, 인코딩은 누구나 되돌리는 형식 변환이에요. MD5(32자)와 SHA-1(40자)은 각각 2004년과 2017년 SHAttered로 실제 충돌이 만들어져 서명·인증서·위변조 검사에는 쓰면 안 되고, 지금 기본은 SHA-256(64자)이에요. 다운로드 파일은 certutil·shasum -a 256·sha256sum 으로 전체 값을 대조하세요.
비밀번호는 빠른 해시 대신 솔트가 자동으로 붙는 Argon2id·bcrypt로, API 서명은 HMAC-SHA256으로 만들면 돼요. 문자열의 MD5·SHA-1·SHA-256 값을 바로 비교해 보려면 해시 생성기를, 강한 비밀번호가 필요하면 비밀번호 생성기를 써 보세요. 해시값을 2진수·16진수로 뜯어보고 싶다면 2진수 16진수 변환 원리 글도 같이 보면 좋아요.