[Cryptography] 단방향 암호화 - 해시 함수
서종호(가시다)님의 On-Premise K8s Hands-on Study 자료를 기반으로 합니다. 학습 정리 목적으로 작성된 글로, 계속해서 수정될 수 있습니다.
암호화 기초
- (1) 암호학 기초 - 개념과 분류
- (2) 단방향 암호화 - 해시 함수
- (3) 양방향 암호화 - 대칭키
- (4) 양방향 암호화 - 비대칭키
개요
단방향 암호화는 복호화가 불가능한 암호화 방식이다. 대표적으로 해시 함수(Hash Function)가 있으며, 비밀번호 저장, 파일 무결성 검증 등에 널리 사용된다. 이 글에서는 해시 함수의 특징과 활용, 그리고 보안 강화 방법에 대해 살펴본다.
해시 함수란?
해시 함수는 임의 길이의 입력 데이터를 고정된 길이의 출력값(해시값)으로 변환하는 함수이다.
해시값 = Hash(입력 데이터)
- 입력: 임의 길이의 데이터 (1비트 ~ 수 GB)
- 출력: 고정 길이의 해시값 (알고리즘에 따라 128비트, 256비트 등)
해시 함수의 특징
1. 고정된 출력 길이
해시 값의 길이는 입력 메시지 길이와 무관하게 항상 일정하다.
- 메시지가 1비트이든 1기가바이트이든 고정된 길이의 해시값 출력
- SHA-256의 경우 항상 256비트(32바이트) 출력
- 예를 들어, “el”, “er”, “eraser”처럼 입력 길이가 다른 문자열들도 모두 동일한 길이의 해시값을 생성한다
2. 결정론적 (Deterministic)
같은 입력은 항상 같은 출력을 반환한다.
SHA256("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
SHA256("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 # 항상 동일
이 특성 덕분에 무결성 검증이 가능하다.
3. 눈사태 효과 (Avalanche Effect)
메시지가 1비트라도 변화하면 해시값이 완전히 달라진다.
SHA256("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
SHA256("hello!") = ce06092fb948d9ffac7d1a376e404b26b7575bcc11ee05a4615fef4fec3a308b # 완전히 다름
이 특성으로 인해 원본 데이터의 작은 변조도 쉽게 감지할 수 있다.
4. 단방향성 (One-way)
해시값으로부터 원본 메시지를 복원하는 것이 계산적으로 불가능하다.
메시지 → 해시값 (가능, 빠름)
해시값 → 메시지 (불가능)
수학적으로 역함수가 존재하지 않도록 설계되어 있다.
5. 충돌 내성 (Collision Resistance)
서로 다른 두 입력이 같은 해시값을 가지는 경우(충돌)를 찾기가 매우 어렵다.
Hash(A) = Hash(B) → 이런 A, B를 찾기 어려움
충돌 내성의 강도는 알고리즘에 따라 다르다. MD5, SHA-1은 이미 충돌이 발견되어 보안 용도로는 권장되지 않는다.
해시 함수의 활용
파일 무결성 검증
파일이 변조되었는지 확인할 때, 파일 전체를 비교하는 대신 해시값(다이제스트)을 비교한다.
- 파일 전체를 비교하지 않음
- 파일의 지문(fingerprint)을 비교
- 해시 함수 적용 후 결과값(digest) 비교
# 파일의 SHA-256 해시값 확인
sha256sum myfile.zip
# a3f2b5c9d8e7f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6 myfile.zip
다운로드한 파일의 해시값을 배포자가 제공한 해시값과 비교하면, 파일이 변조되지 않았음을 확인할 수 있다.
비밀번호 저장
비밀번호를 평문으로 저장하면 보안 위험이 크다. 대신 해시값만 저장한다.
# 저장 시
stored_hash = SHA256(user_password)
# 로그인 검증 시
if SHA256(input_password) == stored_hash:
print("로그인 성공")
데이터베이스가 유출되더라도 원본 비밀번호는 알 수 없다.
데이터 중복 제거
동일한 파일인지 빠르게 확인할 때 해시값을 활용한다.
file_hash = SHA256(file_content)
if file_hash in existing_hashes:
print("이미 존재하는 파일입니다")
레인보우 테이블 공격과 솔트
레인보우 테이블 (Rainbow Table)
해시 함수는 역함수가 없지만, 가능한 모든 입력에 대한 해시값을 미리 계산해 놓은 테이블을 사용하면 원본을 찾을 수 있다.
- 가능한 모든 경우의 수를 미리 해시해서 저장
- 작은 테이블도 기본 수십~수백 GB에 달함
- 주로 비밀번호 해시 크래킹에 사용됨
"password" → 5e884898da28047d9...
"password1" → 7c6a180b36896a65c...
"123456" → 8d969eef6ecad3c29...
...
유출된 해시값을 테이블에서 찾으면 원본 비밀번호를 알아낼 수 있다.
솔트 (Salt)
레인보우 테이블 공격을 방어하기 위해 솔트(Salt)를 사용한다.
솔트는 비밀번호에 랜덤한 값을 추가하여 해시하는 방식이다.
salt = generate_random_bytes(16) # 랜덤 솔트 생성
stored_hash = SHA256(password + salt)
# 솔트와 해시값을 함께 저장
솔트 값을 이용해 방어하는 것은 다음과 같은 이유에서 효과적이다:
- 같은 비밀번호라도 솔트가 다르면 해시값이 달라짐
- 공격자는 각 솔트마다 별도의 레인보우 테이블을 만들어야 함
- 사실상 레인보우 테이블 공격이 불가능해짐
# 같은 비밀번호, 다른 솔트 → 다른 해시 SHA256("password" + "abc123") = x1y2z3... SHA256("password" + "def456") = a1b2c3... # 완전히 다름
주요 해시 알고리즘
주요 해시 알고리즘은 출력 길이, 보안 상태, 용도에 따라 구분된다.
- MD5: 8비트 출력을 생성하지만 이미 취약점이 발견되어 비보안 체크섬 용도로만 제한적으로 사용되며 보안 용도로는 권장되지 않음
- SHA-1: 160비트 출력을 생성하나 마찬가지로 취약점이 발견되어 Git 커밋 해시 같은 레거시 시스템에서만 사용됨
- SHA-256: 256비트 출력을 생성하며 현재 안전한 것으로 평가되어 일반적인 보안 용도로 널리 사용됨
- SHA-512: 512비트 출력을 생성하며 안전하고, 높은 보안이 필요한 경우에 적합
- BLAKE2: 가변 길이 출력을 생성하며 안전하면서도 고성능이 필요한 경우에 사용된다.
HMAC (Hash-based Message Authentication Code)
HMAC은 해시 함수 + 비밀 키를 결합한 메시지 인증 코드이다.
mac = HMAC(message, secret_key)
해시 vs HMAC
해시와 HMAC은 여러 측면에서 차이를 보인다.
- 키 사용 측면에서 일반 해시는 키를 사용하지 않는 반면, HMAC은 비밀 키를 사용함
- 목적 면에서 해시는 무결성 확인에 사용되고 HMAC은 무결성과 인증을 모두 제공함
- 생성 가능 주체는 해시의 경우 누구나 생성할 수 있지만, HMAC은 키 보유자만 생성할 수 있음
- 변조 방지 측면에서 해시는 부분적인 보호만 제공하는 반면, HMAC은 완전한 변조 방지를 제공함
HMAC의 필요성
일반 해시만 사용하면 중간자가 메시지와 해시값을 모두 변조할 수 있다.
A → B: 메시지 + Hash(메시지)
공격자가 변조하더라도, B는 변조를 감지할 수 없다
- 메시지 가로채서 변조
- 변조된 메시지의 해시 재계산
- [변조된 메시지 + 새 해시] 전송
A → B: 메시지 + HMAC(메시지, 공유_비밀키)
공격자가 변조하더라도, B가 변조를 감지할 수 있다
- 메시지 가로채서 변조
- 비밀키를 모르므로 올바른 HMAC 생성 불가
- B가 HMAC 검증 실패
HMAC 사용 예시
import hmac
import hashlib
# 송신자
secret_key = b"shared_secret"
message = b"Hello, World!"
mac = hmac.new(secret_key, message, hashlib.sha256).hexdigest()
# 수신자
received_mac = hmac.new(secret_key, message, hashlib.sha256).hexdigest()
if mac == received_mac:
print("메시지 검증 성공")
HMAC은 TLS 데이터 전송, API 인증 등에서 널리 사용된다.
정리
해시 함수의 핵심 개념을 정리하면 다음과 같다:
- 해시 함수는 임의 길이의 입력을 고정 길이로 변환하는 단방향 함수이다
- 고정 출력 특성으로 인해 입력 크기와 무관하게 항상 같은 길이의 해시값을 출력한다
- 결정론적 특성은 같은 입력이 항상 같은 출력을 생성함을 의미한다
- 눈사태 효과는 1비트의 변화만으로도 해시값이 완전히 변화하는 특성이다
- 단방향성은 해시값으로부터 원본을 복원할 수 없음을 뜻한다
- 충돌 내성은 같은 해시를 갖는 두 입력을 찾기 어렵다는 특성이다
- 레인보우 테이블은 미리 계산된 해시 테이블이며 솔트로 방어할 수 있다
- 솔트는 랜덤 값을 추가하여 레인보우 테이블 공격을 방어하는 기법이다
- HMAC은 해시와 키를 결합하여 무결성과 인증을 모두 제공하는 메커니즘이다
댓글남기기