해시 함수란 무엇인가: SHA-256, 체크섬, 비밀번호 저장까지
SHA-256이 항상 64자리인 이유, 한 글자만 바꿔도 결과가 전부 바뀌는 성질, 해시·암호화·인코딩의 차이, 다운로드 파일 검증, 그리고 비밀번호에 빠른 해시를 쓰면 안 되는 이유를 정리했습니다.
리눅스 배포판이나 개발 도구를 내려받는 페이지 구석에는 SHA256: 3f7a… 같은 긴 문자열이 붙어 있습니다. 대부분은 그냥 지나치지만, 이 문자열 하나로 내려받은 수 GB짜리 파일이 한 비트도 틀리지 않았는지 확인할 수 있습니다. 이 문자열을 만드는 것이 해시 함수입니다.
어떤 입력이든 같은 길이로
해시 함수는 임의 길이의 데이터를 받아 고정된 길이의 값을 돌려줍니다. SHA-256은 이름 그대로 256비트, 16진수로 쓰면 64자리를 냅니다. 입력이 hello 다섯 글자든 10GB 영상 파일이든 결과 길이는 같습니다.
SHA-256("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
SHA-256("") = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
빈 문자열에도 해시가 있습니다. 한글도 마찬가지인데, 해시는 글자가 아니라 바이트에 대해 계산하므로 같은 “안녕하세요”라도 UTF-8로 저장했는지 EUC-KR로 저장했는지에 따라 결과가 달라집니다. 줄바꿈이 LF인지 CRLF인지도 결과를 바꿉니다.
hello의 h를 대문자로 바꾸기만 해도 결과는 완전히 달라집니다. 실제로 두 결과를 비트로 비교하면 256비트 중 125비트가 다릅니다. 입력이 조금만 바뀌어도 출력의 절반 정도가 뒤집히는 이 성질을 **눈사태 효과(avalanche effect)**라고 합니다. 결과를 보고 입력이 “비슷한지”조차 짐작할 수 없게 만드는 성질입니다.
좋은 암호학적 해시가 지켜야 하는 것
SHA-256 같은 암호학적 해시 함수에는 세 가지가 요구됩니다.
- 역상 저항성: 해시값만 보고 원래 입력을 찾아낼 수 없어야 합니다.
- 제2 역상 저항성: 주어진 입력과 같은 해시를 내는 다른 입력을 찾을 수 없어야 합니다.
- 충돌 저항성: 같은 해시를 내는 서로 다른 두 입력을 아무거나 한 쌍이라도 찾기 어려워야 합니다.
출력이 256비트로 정해져 있으니 서로 다른 입력이 같은 해시를 갖는 경우는 수학적으로 반드시 존재합니다. 중요한 것은 그런 쌍을 현실적인 계산으로 찾을 수 없느냐입니다.
MD5와 SHA-1이 밀려난 이유
MD5는 2004년 왕샤오윈(王小云) 연구팀이 충돌 쌍을 만드는 방법을 발표한 뒤로 보안 용도에서 퇴출되었습니다. 2012년 발견된 악성코드 Flame은 MD5 충돌을 이용해 마이크로소프트의 코드 서명 인증서를 위조하기도 했습니다.
SHA-1은 2017년 2월 Google과 네덜란드 CWI 연구소가 내용이 다른 두 PDF 파일이 같은 SHA-1 값을 갖도록 만들어 공개하면서 사실상 끝났습니다(SHAttered). 이때 들어간 계산량은 SHA-1 약 2⁶³번이었습니다. 개인이 할 수 있는 규모는 아니지만, 대형 조직이라면 충분히 가능한 규모라는 것이 드러났습니다.
그렇다고 MD5와 SHA-1이 쓸모없어진 것은 아닙니다. 전송 중 우연히 깨진 파일을 찾는 용도라면 지금도 충분히 제 역할을 합니다. 문제는 누군가 일부러 조작할 수 있는 상황입니다. 서명, 인증서, 비밀번호처럼 공격자가 끼어들 수 있는 곳에는 SHA-256 이상을 씁니다.
해시, 암호화, 인코딩은 다르다
셋 다 원래 데이터를 알아볼 수 없는 문자열로 바꾸기 때문에 자주 섞어 부릅니다. 하지만 성격이 전혀 다릅니다.
| 되돌릴 수 있나 | 키가 필요한가 | 쓰임 | |
|---|---|---|---|
| 인코딩 (Base64 등) | 누구나 되돌림 | 아니요 | 데이터를 글자로 옮겨 적기 |
| 암호화 (AES 등) | 키가 있으면 되돌림 | 예 | 비밀 유지 |
| 해시 (SHA-256 등) | 되돌릴 수 없음 | 아니요 | 무결성 확인, 비밀번호 검증 |
“비밀번호를 암호화해서 저장한다”는 말을 흔히 하지만, 제대로 만든 서비스라면 비밀번호를 암호화하지 않고 해시해서 저장합니다. 암호화는 키만 있으면 되돌릴 수 있으니, 키가 유출되는 순간 모든 비밀번호가 드러납니다.
내려받은 파일 확인하기
체크섬 비교는 운영체제 기본 명령으로 할 수 있습니다.
# Windows (PowerShell)
Get-FileHash .\ubuntu.iso -Algorithm SHA256
# macOS
shasum -a 256 ubuntu.iso
# Linux
sha256sum ubuntu.iso
출력된 값이 배포 페이지의 값과 한 글자라도 다르면 파일이 깨졌거나 바뀐 것입니다. 한 가지 한계는 알아 두세요. 파일과 체크섬을 같은 서버에서 받았다면, 서버를 장악한 공격자는 둘 다 바꿀 수 있습니다. 그래서 주요 배포판은 체크섬 파일에 GPG 서명을 따로 붙입니다.
비밀번호에 SHA-256을 쓰면 안 되는 이유
해시는 되돌릴 수 없으니 비밀번호를 SHA-256으로 저장하면 안전할 것 같지만, 그렇지 않습니다. 문제는 SHA-256이 너무 빠르다는 점입니다. 파일 검증에는 장점이지만, 데이터베이스를 통째로 빼낸 공격자에게도 장점이 됩니다. GPU 한 장으로 1초에 수십억 개의 후보를 해시해 볼 수 있으니, 흔한 비밀번호는 금방 찾힙니다.
그래서 비밀번호 저장에는 두 가지를 더합니다.
첫째는 **솔트(salt)**입니다. 사용자마다 다른 무작위 값을 비밀번호에 붙여 해시합니다. 같은 비밀번호를 쓰는 두 사람도 저장값이 달라지고, 미리 계산해 둔 해시 목록(레인보 테이블)이 쓸모없어집니다.
둘째는 일부러 느린 해시입니다. OWASP의 비밀번호 저장 가이드는 다음을 권합니다.
| 알고리즘 | OWASP 권장 최소 설정 |
|---|---|
| Argon2id (1순위) | 메모리 19 MiB, 반복 2회, 병렬도 1 |
| scrypt | N=2¹⁷, r=8, p=1 |
| bcrypt | 작업 계수 10 이상 (입력은 72바이트까지만 쓰임) |
| PBKDF2-HMAC-SHA256 | 반복 600,000회 이상 (FIPS 준수가 필요할 때) |
한 번 확인하는 데 0.1초가 걸려도 로그인하는 사용자는 느끼지 못합니다. 하지만 수십억 개의 후보를 시험하려는 공격자에게는 치명적인 차이가 됩니다. 이 알고리즘들은 대부분 솔트를 자동으로 만들어 결과 문자열 안에 함께 저장하므로, 직접 솔트를 관리할 필요도 없습니다. 언어마다 검증된 라이브러리가 있으니 직접 구현하지 않는 것이 원칙입니다.
해시 생성기로 확인해 보기
해시 생성기에 글을 입력하거나 파일을 고르면 MD5, SHA-1, SHA-256, SHA-384, SHA-512를 한 번에 보여 줍니다. 마지막 칸에 배포 페이지의 체크섬을 붙여 넣으면 어느 해시와 일치하는지 표시해 줍니다. 파일은 브라우저 안에서만 계산하고 업로드하지 않습니다. hello와 Hello를 차례로 넣어 보면 위 그림의 결과가 그대로 나옵니다.
참고
- NIST FIPS 180-4, Secure Hash Standard (SHS)
- OWASP, Password Storage Cheat Sheet
- Stevens et al., “The first collision for full SHA-1” (SHAttered, 2017)
- Wang, Yu, “How to Break MD5 and Other Hash Functions” (EUROCRYPT 2005)