[Cryptography] 단방향 암호화 - 해시 함수

4 분 소요


서종호(가시다)님의 On-Premise K8s Hands-on Study 자료를 기반으로 합니다. 학습 정리 목적으로 작성된 글로, 계속해서 수정될 수 있습니다.

암호화 기초


개요

단방향 암호화는 복호화가 불가능한 암호화 방식이다. 대표적으로 해시 함수(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는 변조를 감지할 수 없다

  1. 메시지 가로채서 변조
  2. 변조된 메시지의 해시 재계산
  3. [변조된 메시지 + 새 해시] 전송


A → B: 메시지 + HMAC(메시지, 공유_비밀키)

공격자가 변조하더라도, B가 변조를 감지할 수 있다

  1. 메시지 가로채서 변조
  2. 비밀키를 모르므로 올바른 HMAC 생성 불가
  3. 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. 해시 함수는 임의 길이의 입력을 고정 길이로 변환하는 단방향 함수이다
  2. 고정 출력 특성으로 인해 입력 크기와 무관하게 항상 같은 길이의 해시값을 출력한다
  3. 결정론적 특성은 같은 입력이 항상 같은 출력을 생성함을 의미한다
  4. 눈사태 효과는 1비트의 변화만으로도 해시값이 완전히 변화하는 특성이다
  5. 단방향성은 해시값으로부터 원본을 복원할 수 없음을 뜻한다
  6. 충돌 내성은 같은 해시를 갖는 두 입력을 찾기 어렵다는 특성이다
  7. 레인보우 테이블은 미리 계산된 해시 테이블이며 솔트로 방어할 수 있다
  8. 솔트는 랜덤 값을 추가하여 레인보우 테이블 공격을 방어하는 기법이다
  9. HMAC은 해시와 키를 결합하여 무결성과 인증을 모두 제공하는 메커니즘이다




hit count

댓글남기기