해쉬 함수의 원리와 보안 활용: 무결성 검증부터 충돌 대응까지
해쉬 함수의 단방향성·충돌 저항성과 MD, SHA 계열의 차이, 무결성 검증과 패스워드 보안 활용을 정리한다.
2026-08-14 · 최초 발행 2025-06-08
입력이 바뀌면 전혀 다른 값이 되는 변환
해쉬 함수(Hash Function)는 길이에 제한이 없는 데이터를 일정한 길이의 데이터로 매핑한다. 이 출력은 해쉬값(Hash Value), 다이제스트(digest), 해쉬 코드라고 부른다.
같은 입력에는 항상 같은 결과가 나오고 계산은 빠르지만, 해쉬값만으로 원래 데이터를 복원할 수는 없다. 입력을 아주 조금만 바꿔도 결과가 크게 달라지는 눈사태 효과도 핵심 특성이다.
보안성과 자료구조 관점에서 보는 성질
보안 용도의 해쉬 함수는 해쉬값에서 원본을 되찾기 어려운 일방향성(One-way property)을 가져야 한다. 특정 해쉬값에 맞는 입력을 찾기 어려운 약한 충돌 저항성, 그리고 같은 해쉬값을 만드는 서로 다른 두 입력을 찾기 어려운 강한 충돌 저항성도 요구된다.
출력값은 가능한 한 출력 공간에 고르게 분포해야 하며, 실제 사용을 위해 빠르게 계산할 수 있어야 한다. 다만 입력 공간이 출력 공간보다 크므로 비둘기집 원리에 따라 충돌 자체는 피할 수 없다.
해쉬 테이블에서 충돌이 생기면 같은 해쉬값을 가진 항목을 연결 리스트로 관리하는 체이닝(Chaining)을 쓸 수 있다. 다른 버킷을 찾아 저장하는 개방 주소법(Open Addressing), 두 번째 해쉬 함수로 위치를 정하는 더블 해싱(Double Hashing)도 대응 방식이다.
알고리즘 계열별 차이
MD5는 128비트 해쉬값을 생성한다. 계산이 빠르고 다양한 플랫폼에서 지원됐지만 충돌 공격 취약성이 발견됐으며, 2004년 이후 보안 용도로 사용하지 않는다.
SHA-1은 160비트 해쉬값을 만들지만 충돌 취약성 때문에 사용을 권장하지 않는다. SHA-2에는 SHA-224, SHA-256, SHA-384, SHA-512가 있으며, SHA-3는 SHA-2와 다른 내부 구조로 설계된 최신 표준이다. SHA-256 이상은 현재까지 안전하다고 간주되며 디지털 서명, 인증서, 블록체인 등에 사용된다.
RIPEMD는 유럽에서 개발된 해쉬 함수이며 RIPEMD-160이 가장 많이 사용된다. Whirlpool은 512비트 해쉬값을 생성한다. Blake2는 성능과 보안성을 함께 갖춘 현대적 해쉬 함수다. CRC(Cyclic Redundancy Check)는 오류 검출에 사용하는 간단한 해쉬 함수다.
무결성 확인부터 분산 처리까지
파일 배포에서는 원본 파일과 내려받은 파일의 해쉬값을 비교해 무결성을 확인한다. 소프트웨어 배포 사이트가 MD5 또는 SHA 체크섬을 제공하는 방식이 여기에 해당한다.
패스워드는 원문 대신 해쉬값을 저장해 보안을 강화할 수 있다. 메시지의 해쉬값을 개인키로 암호화해 디지털 서명을 만들고, SSL/TLS 인증서의 무결성을 검증하는 데도 쓰인다. 해싱 전에 임의 값을 더하는 솔팅(Salting)은 레인보우 테이블 공격을 방어하며, PBKDF2와 bcrypt는 반복 해싱으로 보안을 강화하는 키 유도 함수다.
해쉬 테이블은 O(1) 시간 복잡도로 데이터에 접근할 수 있게 한다. 블룸 필터(Bloom Filter)는 집합에 원소가 존재하는지를 확률적으로 판단하며, 분산 시스템은 일관된 해싱(Consistent Hashing)으로 부하를 분산한다. 같은 데이터를 식별해 스토리지를 최적화하는 중복 제거도 해쉬값을 활용한다.
블록체인에서는 특정 조건을 만족하는 해쉬값을 찾는 작업 증명(Proof of Work)이 채굴 과정에 해당한다. 머클 트리(Merkle Tree)는 데이터 무결성을 효율적으로 검증하고, 트랜잭션 ID는 각 거래의 고유 식별자로 해쉬값을 사용한다.
패스워드 해싱에서 다뤄야 할 공격
브루트 포스 공격(Brute Force Attack)은 가능한 모든 입력값을 시도한다. 사전 공격(Dictionary Attack)은 자주 쓰는 패스워드 목록을 이용하며, 레인보우 테이블 공격(Rainbow Table Attack)은 미리 계산한 해쉬값 테이블을 사용한다. 길이 확장 공격(Length Extension Attack)은 일부 해쉬 함수의 구조적 약점을 이용한다.
솔트는 해싱 전에 랜덤 값을 추가해 같은 입력에서도 다른 해쉬값이 나오게 한다. 키 스트레칭(Key Stretching)은 해쉬 함수를 반복 적용해 계산 비용을 높이고, 메모리 하드 함수(Memory-Hard Functions)는 많은 메모리를 쓰도록 설계된다. SHA-3, Argon2 등 검증된 최신 알고리즘을 쓰는 선택도 함께 고려할 수 있다.
구현 시에는 용도와 충돌 비용을 함께 본다
해쉬 함수 선택에서는 성능과 보안 사이의 균형이 필요하다. 해쉬 테이블을 구현할 때는 애플리케이션 특성에 맞는 충돌 처리 전략을 정하고, 해쉬 버킷 크기에서는 메모리 사용량과 충돌 가능성의 트레이드오프를 고려한다. 로드 팩터(load factor)에 따라 동적으로 확장하는 재해싱(Rehashing) 전략도 필요하다.
Java에서는 MessageDigest로 SHA-256 해쉬값을 계산할 수 있다.
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class HashExample {
public static String getSHA256Hash(String input) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hashBytes = md.digest(input.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte b : hashBytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
Python에서도 문자열을 바이트로 변환한 뒤 SHA-256을 계산할 수 있다.
import hashlib
def get_sha256_hash(input_string):
# 문자열을 바이트로 변환
input_bytes = input_string.encode('utf-8')
# SHA-256 해시 계산
hash_object = hashlib.sha256(input_bytes)
# 16진수 문자열로 반환
return hash_object.hexdigest()
양자 환경과 제한된 장치까지 고려하는 변화
양자 내성 해쉬 함수(post-quantum cryptography), IoT 장치처럼 제한된 환경을 위한 경량 해쉬 함수, 다양한 보안 요구사항을 수용하는 다기능 해쉬 함수가 개발되고 있다. NIST의 표준화도 새로운 해쉬 함수의 개발과 검증을 이어가는 축이다.
영지식 증명과 동형 암호화 같은 차세대 응용 분야와의 결합도 진행 중이다. 해쉬 함수의 특성과 한계를 이해한 뒤, 보안 요구사항에 맞는 알고리즘과 추가 보안 기법을 선택하는 일이 보안과 데이터 처리 모두에서 기준이 된다.