비밀번호 저장

AI
gemma-4-31b
작성자
익명
작성일
2026.08.07
조회수
49
버전
v2

📋 문서 버전

이 문서는 2개의 버전이 있습니다. 현재 최신 버전을 보고 있습니다.

비밀번호 저장 (Password Storage)

1. 개요

비밀번호 저장은 사용자가 인증을 위해 설정한 비밀번호를 서버의 데이터베이스에 안전하게 보관하여, 관리자나 외부 공격자가 원본 비밀번호를 알 수 없도록 처리하는 보안 프로세스이다.

현대적인 시스템에서 비밀번호를 평문(Plaintext), 즉 암호화되지 않은 텍스트 그대로 저장하는 것은 매우 위험하다. 데이터베이스가 유출될 경우 모든 사용자의 계정 정보가 즉시 노출되며, 많은 사용자가 여러 서비스에서 동일한 비밀번호를 사용한다는 점(Credential Stuffing 위험) 때문에 2차, 3차 피해로 확산될 가능성이 매우 높기 때문이다.

2. 해시 함수 (Hash Function)

비밀번호 저장의 핵심은 해시 함수를 이용한 단방향 암호화이다. 해시 함수란 임의의 길이를 가진 데이터를 입력받아 고정된 길이의 고유한 문자열(해시값)로 변환하는 함수를 말한다.

2.1 단방향성의 원리

해시 함수의 가장 큰 특징은 단방향성(One-way)이다. 즉, 입력값에서 해시값을 계산하는 것은 매우 빠르고 쉽지만, 결과값(해시값)만 가지고 원래의 입력값을 찾아내는 것은 수학적으로 거의 불가능하도록 설계되어 있다. 따라서 서버는 사용자의 비밀번호 원본을 저장하는 대신, 비밀번호의 해시값만을 저장하고 로그인 시 입력받은 값의 해시 결과가 저장된 값과 일치하는지만 확인한다.

2.2 일반 암호화 vs 해시 함수의 차이점

구분 일반 암호화 (Encryption) 해시 함수 (Hashing)
방향성 양방향 (암호화 $\leftrightarrow$ 복호화) 단방향 (해시 생성 $\rightarrow$ 복구 불가)
키(Key) 필요성 복호화를 위한 키가 반드시 필요함 기본적으로 키가 필요 없으나, HMAC 등 특수 목적 시 키 사용 가능
목적 데이터의 기밀 전송 및 복구 데이터의 무결성 확인 및 원본 은닉
결과 길이 입력 길이에 따라 가변적 입력 길이에 상관없이 항상 고정됨

2.3 해시 함수 선택 가이드

비밀번호 저장 목적의 해시 함수를 선택할 때는 다음 기준을 고려해야 한다. - 충돌 저항성: 서로 다른 입력값이 동일한 해시값을 생성할 확률이 극히 낮아야 한다. - 연산 비용: 공격자의 무차별 대입 공격을 늦추기 위해 의도적으로 연산 속도가 느려야 한다. (범용 해시 함수인 SHA-256 등은 너무 빨라 부적합함) - 적응성: 하드웨어 성능 향상에 따라 연산 비용(Cost)을 설정으로 조절할 수 있어야 한다.

3. 키 스트레칭 (Key Stretching)

키 스트레칭이란 해시 함수를 한 번만 적용하는 것이 아니라, 수천 수만 번 반복적으로 적용하여 해시값을 생성하는 기법이다.

3.1 원리와 목적

단순 해싱은 연산 속도가 매우 빨라 공격자가 초당 수억 개의 해시값을 계산해 원문을 찾아낼 수 있다. 키 스트레칭은 hash(hash(hash(...hash(password)...)))와 같이 반복 횟수를 늘림으로써, 정당한 사용자가 로그인할 때는 체감하지 못할 정도의 짧은 지연(예: 0.1초)을 주지만, 수억 번의 시도를 해야 하는 공격자에게는 연산 시간을 기하급수적으로 늘려 공격 비용을 현실적으로 불가능하게 만드는 것이 목적이다.

4. 솔트 (Salt)

단순한 해시 함수만 사용할 경우, 동일한 비밀번호는 항상 동일한 해시값을 생성한다. 이는 공격자가 미리 계산된 해시 테이블을 이용해 비밀번호를 빠르게 찾아낼 수 있게 한다. 이를 방지하기 위해 도입된 개념이 솔트(Salt)이다.

4.1 개념 및 작동 원리

솔트는 비밀번호를 해싱하기 전에 추가하는 무작위 문자열이다. 사용자마다 서로 다른 고유한 솔트를 생성하여 비밀번호에 결합한 뒤 해싱하면, 설령 두 사용자가 동일한 비밀번호(password123)를 사용하더라도 저장되는 최종 해시값은 완전히 달라진다.

4.2 솔트 적용 전후 비교 예시

# 가상의 해시 함수: hash(input)
# 사용자 A와 B가 모두 "password123"를 사용한다고 가정

# [솔트 미적용]
User_A: hash("password123") -> "a5f1...8b2"
User_B: hash("password123") -> "a5f1...8b2" (동일한 결과값 노출)

# [솔트 적용]
# User_A 솔트: "sAlt_789", User_B 솔트: "xYz_123"
User_A: hash("password123" + "sAlt_789") -> "k9p2...m1z"
User_B: hash("password123" + "xYz_123") -> "q7r4...w5v" (서로 다른 결과값)

# ※ 실제 구현 시 솔트는 해시값과 함께 DB에 저장되어야 함

5. 페퍼 (Pepper)

페퍼(Pepper)는 솔트와 유사하지만, 저장 위치와 관리 방식에서 차이가 있는 추가 보안 계층이다.

솔트는 일반적으로 데이터베이스의 사용자 레코드와 함께 저장되지만, 페퍼는 데이터베이스 외부(예: 애플리케이션 설정 파일, HSM, 환경 변수)에 별도로 저장되는 비밀 키이다.

  • 작동 방식: hash(password + salt + pepper)
  • 효과: 공격자가 데이터베이스를 탈취하더라도 페퍼 값을 알지 못하면 해시값을 무력화하기 위해 수행하는 연산의 난이도가 기하급수적으로 상승한다. 즉, DB 유출과 서버 설정 파일 유출이라는 두 가지 사고가 동시에 일어나야만 공격이 가능해진다.

6. 주요 공격 기법

저장된 해시값을 무력화하기 위해 공격자들은 다음과 같은 기법을 사용한다.

  1. 레인보우 테이블(Rainbow Table) 공격: 자주 사용되는 비밀번호와 그에 대응하는 해시값을 미리 계산하여 거대한 테이블로 만들어 둔 뒤, 유출된 해시값과 대조하여 원문을 찾는 공격이다. (솔트 적용 시 무력화됨)
  2. 무차별 대입 공격(Brute-force): 가능한 모든 문자 조합을 하나씩 대입하여 해시값이 일치할 때까지 반복하는 공격이다.
  3. 사전 공격(Dictionary Attack): 실제 사람들이 자주 사용하는 단어 목록(사전)을 기반으로 해시값을 대조하는 효율적인 대입 공격이다.

7. 권장 저장 알고리즘

단순한 SHA-256과 같은 범용 해시 함수는 연산 속도가 너무 빨라 무차별 대입 공격에 취약하다. 따라서 비밀번호 저장 전용으로 설계된 적응형 해시 함수(Adaptive Hash Function) 사용이 권장된다.

7.1 알고리즘별 특징 및 비교

알고리즘 특징 보안 수준 권장 용도
SHA-256 매우 빠른 연산 속도, 범용 해시 낮음 (단순 해싱 시) 파일 무결성 검사
bcrypt CPU 연산 비용 조절 가능, 솔트 내장 높음 일반적인 웹 서비스
scrypt 메모리 하드웨어 비용 추가 (FPGA/ASIC 방어) 매우 높음 고보안 요구 시스템
Argon2 메모리/시간/병렬성 모두 조절 가능 (PHC 우승작) 최상 최신 표준 보안 시스템

7.2 알고리즘별 권장 파라미터 설정값

적응형 알고리즘은 하드웨어 성능 발전에 맞춰 연산 비용을 높일 수 있는 파라미터를 제공한다.

알고리즘 파라미터 권장 설정값 (기준) 설명
bcrypt Cost Factor $10 \sim 12$ $2^{cost}$ 만큼 반복 연산 수행
scrypt $N, r, p$ $N=16384, r=8, p=1$ CPU/메모리 사용량 결정
Argon2id $m, t, p$ $m=64\text{MB}, t=3, p=4$ 메모리 크기, 반복 횟수, 병렬성

7.3 구현 예제 (Python - Argon2)

최신 표준인 Argon2id를 사용하는 예시이다. (argon2-cffi 라이브러리 기준)

from argon2 import PasswordHasher

# 1. 해셔 초기화 (파라미터는 라이브러리 기본값 또는 설정값 사용)
ph = PasswordHasher()

# 2. 비밀번호 해싱 및 저장
# 내부적으로 무작위 솔트 생성 및 키 스트레칭이 자동으로 수행됨
password = "user_secure_password123"
hashed_password = ph.hash(password) 
# 결과값 예: "$argon2id$v=19$m=65536,t=3,p=4$S... (솔트와 파라미터가 포함됨)"

# 3. 비밀번호 검증
user_input = "user_secure_password123"
try:
    ph.verify(hashed_password, user_input)
    print("인증 성공")
except Exception:
    print("인증 실패")

8. 비밀번호 검증 프로세스

사용자가 로그인을 시도할 때 서버에서 수행하는 검증 순서는 다음과 같다.

  1. 입력: 사용자가 IDPassword를 입력함.
  2. 조회: DB에서 해당 ID에 매칭되는 SaltStored_Hash를 가져옴.
  3. 결합: 입력받은 Password에 조회한 Salt와 서버의 Pepper를 결합함.
  4. 해싱: 설정된 알고리즘(예: Argon2)과 파라미터를 사용하여 결합된 값을 해싱함.
  5. 비교: 생성된 New_Hash와 DB의 Stored_Hash가 일치하는지 확인함.
  6. 결과: 일치하면 인증 성공, 불일치하면 인증 실패 처리.

9. 보안 모범 사례 (Best Practices)

  • 고유한 솔트 사용: 모든 사용자에게 동일한 솔트를 사용하지 말고, 사용자별로 무작위로 생성된 고유 솔트를 부여해야 한다.
  • 충분한 연산 비용 설정: 로그인 시 약 0.1초 ~ 0.5초 정도의 지연 시간이 발생하도록 Cost 파라미터를 설정하여 공격자의 대입 속도를 늦춰야 한다.
  • 안전한 비교 함수 사용: 해시값을 비교할 때 == 연산자 대신 상수 시간 비교(Constant-time comparison) 함수를 사용하여 타이밍 공격(Timing Attack)을 방지해야 한다. (문자열을 앞에서부터 비교하여 일치하지 않는 지점에서 즉시 반환할 경우, 응답 시간 차이로 비밀번호 일부를 유추할 수 있는 공격)
  • 주기적인 알고리즘 업데이트: 하드웨어 성능이 향상됨에 따라 기존 알고리즘의 보안성이 낮아질 수 있으므로, 사용자가 로그인할 때 최신 알고리즘으로 해시를 갱신하는 전략을 도입한다.

하드코딩된 비밀 정보의 위험성

소스 코드 내에 비밀번호, API 키, 페퍼(Pepper), DB 접속 정보 등을 직접 텍스트로 작성하는 하드코딩(Hard-coding)은 심각한 보안 취약점을 야기한다.

  • 버전 관리 시스템을 통한 유출: Git과 같은 VCS를 사용할 경우, 한 번 커밋된 비밀 정보는 커밋 히스토리에 영구적으로 남는다. 공개 저장소(Public Repository)에 푸시될 경우 전 세계에 즉시 노출되며, 프라이빗 저장소라 하더라도 권한이 있는 모든 개발자와 협력사가 해당 정보에 접근할 수 있게 된다.
  • 바이너리 역공학(Reverse Engineering): 컴파일된 바이너리 파일이나 자바스크립트 번들 파일 내에도 하드코딩된 문자열은 그대로 남는다. 공격자는 strings 명령어 나 디컴파일러를 통해 실행 파일 내의 비밀 키를 손쉽게 추출할 수 있다.
  • 환경별 관리 어려움: 개발(Dev), 테스트(Staging), 운영(Prod) 환경마다 서로 다른 키를 사용해야 함에도 불구하고, 코드에 고정되어 있으면 환경 변경 시마다 코드를 수정하고 다시 배포해야 하는 운영 효율성 저하가 발생한다.

비밀 정보의 안전한 관리 방안

하드코딩의 위험을 제거하기 위해 비밀 정보는 코드와 완전히 분리하여 관리해야 한다.

2.1 환경 변수(Environment Variables) 활용

비밀 정보를 운영체제 수준의 환경 변수에 저장하고, 애플리케이션이 실행 시점에 이를 읽어오도록 설정하는 방식이다.

  • .env 파일 관리: 로컬 개발 환경에서는 .env 파일에 KEY=VALUE 형태로 저장하여 사용한다. 이때, .env 파일은 절대 버전 관리 시스템에 포함되어서는 안 된다.
    • .gitignore 설정: .gitignore 파일에 .env를 추가하여 Git 추적 대상에서 제외한다.
              # .gitignore 예시
              .env
              .env.local
              .env.*.local
              
    • 템플릿 제공: 대신 .env.example 파일을 만들어 어떤 설정값이 필요한지만 명시하고, 실제 값은 비워둔 채 공유한다.

2.2 전문 비밀 관리 도구 (Secret Management System)

규모가 큰 시스템이나 높은 보안 수준이 요구되는 경우, 환경 변수보다 강력한 전용 관리 도구를 도입한다.

  • 주요 도구:
  • 특징: 중앙 집중식 관리, 접근 제어(IAM), 비밀번호 자동 로테이션(Rotation), 감사 로그(Audit Log) 제공.

2.3 환경 변수 vs Vault 비교

구분 환경 변수 (.env) 비밀 관리 도구 (Vault 등)
저장 위치 서버 로컬 파일 또는 OS 메모리 암호화된 중앙 저장소
접근 제어 서버 접근 권한자 모두 확인 가능 세밀한 권한 제어 (Role-based)
생명 주기 수동 변경 및 재배포 필요 자동 로테이션 및 동적 생성 가능
보안성 중간 (파일 유출 시 위험) 높음 (암호화 저장 및 접근 기록 남음)
복잡도 매우 낮음 (설정이 간편함) 높음 (별도 인프라 구축/운영 필요)

페퍼의 안전한 저장 및 관리

기존에 언급된 '애플리케이션 설정 파일'에 페퍼를 저장하는 방식은 앞서 설명한 하드코딩의 위험성을 그대로 내포하고 있다. 설정 파일이 Git에 포함되거나 서버가 탈취될 경우 페퍼가 함께 유출되어 보안 계층이 무력화될 수 있다.

따라서 페퍼는 다음과 같이 관리하는 것이 권장된다. 1. 환경 변수: 서버 OS의 환경 변수로 설정하여 코드와 분리한다. 2. Secret Manager: AWS Secrets Manager나 HashiCorp Vault와 같은 전문 도구에 저장하고, 애플리케이션 구동 시 API를 통해 동적으로 주입받는다. 3. HSM (Hardware Security Module): 최상위 보안이 필요한 경우 하드웨어 보안 모듈 내에 키를 저장하여 외부 유출을 원천 차단한다.

소스 코드 비밀 정보 유출 방지 (Secret Scanning)

"소스 코드 내 비밀 정보 저장 금지" 원칙을 강제하기 위해, 사람이 수행하는 코드 리뷰 외에 자동화된 Secret Scanning 도구를 도입해야 한다.

  • 작동 원리: 정적 분석(Static Analysis)을 통해 코드 내의 특정 패턴(예: AKIA...로 시작하는 AWS 키, sk_live...로 시작하는 Stripe 키 등)이나 고엔트로피 문자열을 탐지한다.
  • 구체적인 도구:
    • gitleaks: Git 커밋 히스토리를 전수 조사하여 유출된 비밀 정보를 찾는 오픈소스 도구.
    • truffleHog: 저장소의 전체 이력을 스캔하여 비밀 정보를 탐지하는 도구.
    • GitHub Secret Scanning: GitHub 저장소에 푸시되는 코드에서 알려진 서비스의 키 패턴을 자동으로 감지하여 경고를 보내는 내장 기능.
  • 적용 시점:
    • Pre-commit Hook: 커밋 직전에 로컬에서 스캔하여 유출을 사전에 차단.
    • CI/CD Pipeline: 빌드/배포 단계에서 스캔을 수행하여 취약한 코드가 운영 환경에 반영되는 것을 방지.
AI 생성 콘텐츠 안내

이 문서는 AI 모델(gemma-4-31b)에 의해 생성된 콘텐츠입니다.

주의사항: AI가 생성한 내용은 부정확하거나 편향된 정보를 포함할 수 있습니다. 중요한 결정을 내리기 전에 반드시 신뢰할 수 있는 출처를 통해 정보를 확인하시기 바랍니다.

이 AI 생성 콘텐츠가 도움이 되었나요?