해시 알고리즘이란? SHA-256부터 비밀번호 저장까지
해시 함수의 원리, SHA 패밀리, 실무에서의 활용법과 보안 주의사항을 정리했다.
비밀번호를 데이터베이스에 저장할 때 평문 그대로 넣는 개발자는 없다 (있으면 안 된다). 해시 함수를 통과시켜서 원래 값을 알 수 없는 형태로 바꿔서 저장한다. 블록체인의 핵심 기술도 해시다. Git이 커밋을 식별하는 것도 해시. 생각보다 여기저기 쓰인다.
해시 함수가 하는 일
입력 데이터를 고정 길이의 값으로 변환한다. 이 출력값을 해시, 다이제스트, 체크섬이라고 부른다.
핵심 성질이 몇 가지 있다:
결정성 — 같은 입력은 항상 같은 출력을 만든다. "hello"를 SHA-256에 넣으면 언제나 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824가 나온다.
단방향성 — 해시 값에서 원래 입력을 역산하는 건 (이론적으로) 불가능하다.
눈사태 효과 — 입력이 1비트만 바뀌어도 출력이 완전히 달라진다. "hello"와 "hellp"의 해시는 전혀 다르다.
충돌 저항성 — 서로 다른 두 입력이 같은 해시를 만드는 경우(충돌)를 찾기 어렵다.
SHA 패밀리
SHA(Secure Hash Algorithm)는 미국 국가안보국(NSA)이 설계하고 NIST가 표준화한 해시 함수 모음이다.
SHA-1
160비트(40자리 16진수) 출력. Git 커밋 해시가 SHA-1이다. 하지만 2017년에 구글이 충돌을 시연했다. 두 개의 다른 PDF 파일이 같은 SHA-1 해시를 갖도록 만든 거다. 이후로 보안 용도에서는 사용이 비권장된다.
SHA-256
256비트(64자리 16진수) 출력. 현재 가장 널리 쓰이는 해시 함수. 비트코인 채굴도 SHA-256 기반이다. SSL/TLS 인증서, 소프트웨어 서명 등 대부분의 보안 관련 해싱에 쓰인다.
SHA-384 / SHA-512
더 긴 출력. SHA-512는 64비트 프로세서에서 SHA-256보다 오히려 빠를 수 있다. 64비트 연산을 기반으로 설계되었기 때문.
SHA-3
Keccak 알고리즘 기반. SHA-2와는 완전히 다른 내부 구조를 가진다. SHA-2에 취약점이 발견될 경우를 대비한 백업 표준 같은 위치인데, SHA-2가 아직 안전해서 실사용은 드물다.
해시 vs 암호화
이 둘은 완전히 다른 개념이다.
| 해시 | 암호화 | |
|---|---|---|
| 방향 | 단방향 (복원 불가) | 양방향 (키로 복호화) |
| 목적 | 무결성 검증, 식별 | 기밀성 보호 |
| 키 필요 | 없음 | 필요 |
| 예시 | SHA-256, bcrypt | AES, RSA |
"해시로 암호화했다"는 표현은 기술적으로 틀렸다. 해시는 암호화가 아니다.
실무 활용
비밀번호 저장
비밀번호를 해시해서 저장하면, DB가 유출되어도 평문 비밀번호는 노출되지 않는다. 하지만 단순 SHA-256 해시만으로는 부족하다.
문제 1: 레인보우 테이블. "password123" → SHA-256 해시를 미리 계산해놓은 거대한 테이블이 있다. 흔한 비밀번호는 이 테이블에서 바로 찾을 수 있다.
해결: 솔트(Salt). 비밀번호에 랜덤 문자열을 붙인 뒤 해시한다. 같은 비밀번호라도 솔트가 다르면 해시가 달라진다.
문제 2: 속도. SHA-256은 빠르다. 그게 비밀번호에서는 단점이다. 공격자가 초당 수십억 개의 해시를 시도할 수 있다.
해결: bcrypt, scrypt, Argon2. 의도적으로 느리게 설계된 해시 함수. Argon2가 현재 권장. 비밀번호에는 SHA-256이 아니라 이런 전용 함수를 써야 한다.
파일 무결성 검증
소프트웨어를 다운로드할 때 제공되는 SHA-256 체크섬은 파일이 변조되지 않았음을 확인하기 위한 것이다. 다운로드한 파일의 해시를 계산해서 공식 해시와 비교한다.
# Linux/macOS
sha256sum downloaded-file.iso
# 또는
shasum -a 256 downloaded-file.iso
# Windows PowerShell
Get-FileHash downloaded-file.iso -Algorithm SHA256
패키지 매니저도 같은 원리를 쓴다. npm이 package-lock.json에 integrity 필드로 SHA-512 해시를 저장하는 이유가 이거다. 누가 패키지를 변조하면 해시가 달라지니까 설치 시점에 잡아낸다.
Git
Git의 모든 객체(커밋, 트리, 블롭)는 SHA-1 해시로 식별된다. 커밋 해시 a1b2c3d는 그 커밋의 내용(변경사항, 작성자, 시간, 부모 커밋 등)을 SHA-1에 넣은 결과다. 내용이 1바이트라도 다르면 해시가 달라지니까, 커밋의 무결성이 보장된다.
# Git이 내부적으로 하는 일과 비슷한 방식
echo -n "blob 5\0hello" | sha1sum
# ce013625030ba8dba906f756967f9e9ca394464a
Git은 SHA-256으로 전환하는 작업을 진행 중이다. SHA-1 충돌 공격이 가능해졌기 때문. git init --object-format=sha256으로 이미 SHA-256 저장소를 만들 수 있긴 하지만, 아직 실험적 단계다.
블록체인
비트코인의 작업 증명(Proof of Work)은 SHA-256 해시의 앞부분이 특정 수의 0으로 시작하는 값을 찾는 과정이다. 해시의 특성상 입력을 조금씩 바꿔가며 무작위로 시도하는 수밖에 없다. 이게 채굴의 본질이고, 엄청난 계산 자원이 필요한 이유다.
HMAC
HMAC(Hash-based Message Authentication Code)은 해시와 비밀 키를 결합한 메시지 인증 코드다. API 요청이 변조되지 않았음을 확인할 때 사용한다. AWS API 서명, GitHub 웹훅 검증 등에서 HMAC-SHA256을 쓴다.
MD5는 왜 안 쓰나
MD5는 128비트 해시를 생성하는 오래된 함수다. 2004년에 충돌이 발견됐고, 이후 노트북 한 대로도 충돌을 만들 수 있게 됐다. 보안 용도로는 완전히 퇴출. 하지만 보안이 필요 없는 체크섬(파일 다운로드 검증 등)에는 아직 쓰이기도 한다.
재밌는 건 MD5 충돌을 시각적으로 보여주는 사례도 있다는 거다. 서로 다른 두 이미지 파일인데 MD5 해시가 동일한 경우를 만들어낸 연구가 있었다. 실제로 보면 해시 충돌이 왜 위험한지 확 와닿는다.
코드로 해시 써보기
대부분의 언어에서 해시 계산은 몇 줄이면 된다.
// Node.js
const crypto = require("crypto");
const hash = crypto.createHash("sha256").update("hello").digest("hex");
// 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
# Python
import hashlib
h = hashlib.sha256(b"hello").hexdigest()
# CLI
echo -n "hello" | sha256sum
echo -n에서 -n이 중요하다. 이걸 빼먹으면 줄바꿈 문자가 포함돼서 전혀 다른 해시가 나온다. 은근히 자주 하는 실수.
브라우저에서 간단하게 해시를 확인하고 싶으면 해시 생성기를 쓸 수 있다. 텍스트를 입력하면 MD5, SHA-1, SHA-256, SHA-512를 한번에 볼 수 있다.
어떤 해시를 써야 하나
정리하면 이렇다:
- 비밀번호 저장: Argon2 또는 bcrypt. SHA 계열 쓰면 안 된다
- 파일 무결성 검증: SHA-256이 표준. 속도가 중요하면 xxHash나 BLAKE3도 괜찮다
- 데이터 구조(해시맵 등): 언어 런타임 기본 해시면 충분. 보안과 무관
- 디지털 서명, 인증서: SHA-256 이상
- 빠른 체크섬: CRC32, xxHash. 보안 필요 없는 경우에만
용도를 헷갈리면 사고가 난다. "해시는 다 같은 거 아냐?"라고 생각하는 순간 비밀번호를 SHA-256으로 저장하는 일이 벌어진다. 해시 알고리즘은 도구 상자 안의 도구다. 망치로 나사를 박지 말자.