티스토리 뷰

728x90

BCrypt 심층 분석: 비밀번호 보안의 강력한 수호자

안녕하세요, 보안 개발에 관심 있는 여러분! 오늘은 비밀번호 해싱의 강자, BCrypt에 대해 깊이 있게 알아보겠습니다. BCrypt가 어떻게 작동하는지, 왜 다른 해싱 알고리즘보다 뛰어난지, 그리고 실제로 어떻게 구현하는지 상세히 살펴봅시다.

BCrypt의 탄생과 진화

BCrypt의 역사

  • 1999년 Niels Provos와 David Mazières에 의해 개발
  • OpenBSD 운영 체제를 위해 처음 설계됨
  • 시간이 지남에 따라 다양한 프로그래밍 언어와 프레임워크에서 채택

왜 BCrypt인가?

적응형 해시 함수: BCrypt는 시간이 지나도 보안 강도를 유지할 수 있는 '적응형' 알고리즘입니다.

작동 방식:

  • BCrypt는 '작업 계수(work factor)'라는 매개변수를 사용합니다.
  • 이 작업 계수는 해시를 생성하는 데 필요한 계산 횟수를 결정합니다.
  • 계수가 1 증가할 때마다 필요한 계산 시간은 2배로 증가합니다.

장점:

  • 컴퓨터의 성능이 향상되어도 작업 계수를 증가시켜 보안 수준을 유지할 수 있습니다.
  • 현재의 하드웨어로는 빠르게 처리할 수 있지만, 공격자의 무차별 대입 공격은 여전히 어렵게 만듭니다.

예시:

# 작업 계수 10 (2^10 = 1,024회 반복)
bcrypt.hashpw(password, bcrypt.gensalt(10))

# 작업 계수 12 (2^12 = 4,096회 반복)
bcrypt.hashpw(password, bcrypt.gensalt(12))

솔트 내장: 레인보우 테이블 공격 방지

의미: BCrypt는 각 비밀번호마다 고유한 '솔트(salt)'를 자동으로 생성하고 해시와 함께 저장합니다.

작동 방식:

  • 솔트는 랜덤하게 생성된 문자열입니다 (보통 16바이트 길이).
  • 이 솔트는 원래 비밀번호에 추가된 후 해싱됩니다.
  • 생성된 해시에는 솔트가 포함되어 있어 별도로 저장할 필요가 없습니다.

예시:

$2a$10$N9qo8uLOickgx2ZMRZoMye...
 \__/\/ \____________________/
   |  |          |
  버전 |         솔트
    작업 계수

장점:

  • 같은 비밀번호라도 매번 다른 해시가 생성됩니다.
  • 레인보우 테이블 공격을 무력화합니다. (미리 계산된 해시 테이블을 사용할 수 없게 됩니다.)
  • 솔트 관리를 개발자가 직접 할 필요가 없어 구현 오류 가능성이 줄어듭니다.

느린 해싱 속도: 무차별 대입 공격에 강함

의미: BCrypt는 의도적으로 느리게 설계되어 있어, 비밀번호 검증에 시간이 더 오래 걸립니다.

작동 방식:

  • Blowfish 암호화 알고리즘을 기반으로 하며, 키 설정 단계를 여러 번 반복합니다.
  • 작업 계수에 따라 반복 횟수가 기하급수적으로 증가합니다.

예시:

import time
import bcrypt

password = b"mypassword"

start = time.time()
bcrypt.hashpw(password, bcrypt.gensalt(10))
end = time.time()
print(f"작업 계수 10: {end - start:.4f}초")

start = time.time()
bcrypt.hashpw(password, bcrypt.gensalt(12))
end = time.time()
print(f"작업 계수 12: {end - start:.4f}초")

장점:

  • 정상적인 로그인 과정에는 큰 영향을 주지 않습니다. (사용자는 0.1초의 지연을 거의 느끼지 못합니다.)
  • 공격자가 많은 비밀번호를 빠르게 시도하는 것을 어렵게 만듭니다.
  • GPU나 전문 하드웨어를 사용한 공격에 대해서도 효과적인 방어가 됩니다.

 

"BCrypt는 단순한 해시 함수가 아닙니다. 그것은 시간의 검증을 거친 비밀번호 보안의 철학입니다."

 

BCrypt의 기술적 측면

BCrypt 해시의 구조

BCrypt 해시는 다음과 같은 구조를 가집니다:

$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
\__/\/ \____________________/\_____________________________/
 Alg Cost      Salt                        Hash
  • Alg: 알고리즘 버전 ($2a$, $2b$, $2y$)
  • Cost: 작업 계수 (예: 10)
  • Salt: 16바이트(128비트) 솔트, Base64로 인코딩
  • Hash: 실제 해시 값, Base64로 인코딩

작업 계수(Cost Factor)의 중요성

  • 2의 지수로 표현 (예: 10은 2^10 = 1,024 반복)
  • 높을수록 더 안전하지만, 해싱 시간도 증가
  • 일반적으로 8-12 사이의 값 사용 (상황에 따라 조정 필요)

Blowfish 암호화 알고리즘 활용

Blowfish란?

Blowfish는 1993년 Bruce Schneier가 개발한 대칭키 블록 암호화 알고리즘입니다. '대칭키'란 암호화와 복호화에 같은 키를 사용한다는 의미이고, '블록 암호화'는 데이터를 일정 크기의 블록으로 나누어 암호화한다는 뜻입니다.

Blowfish의 주요 특징

  1. 가변 키 길이: 32비트에서 448비트까지의 다양한 키 길이를 지원합니다.
  2. 빠른 성능: 대부분의 하드웨어에서 매우 빠르게 동작합니다.
  3. 간단한 구조: 이해하기 쉽고 구현하기 쉬운 구조를 가지고 있습니다.
  4. 보안성: 지금까지 알려진 공격 방법에 대해 안전한 것으로 평가받고 있습니다.

BCrypt는 Blowfish 암호화 알고리즘의 변형을 사용합니다:

  1. Blowfish의 키 스케줄링 알고리즘을 수정
  2. 비밀번호, 솔트, 비용을 입력으로 사용
  3. 반복적인 키 확장 과정을 통해 강력한 해시 생성
간단한 비유로 이해하기

Blowfish와 BCrypt의 관계를 이해하기 위해, 다음과 같은 비유를 생각해볼 수 있습니다:

Blowfish는 튼튼한 금고라고 생각해 봅시다. 이 금고는 빠르게 열고 닫을 수 있지만, 매우 안전합니다. BCrypt는 이 금고를 가져와서 더 강화했습니다. 금고를 여는 데 필요한 시간을 늘리고, 매번 자물쇠의 구조를 약간씩 변경하여 (솔트 사용), 누군가가 금고를 열려고 시도할 때마다 완전히 새로운 도전에 직면하게 만들었습니다.

BCrypt vs 다른 해싱 알고리즘

알고리즘 장점 단점
MD5 빠른 연산 매우 취약함, 충돌 가능성 높음
SHA-256 널리 사용됨 빠른 연산으로 무차별 대입에 취약
BCrypt 적응형, 내장 솔트 상대적으로 느린 연산
Argon2 메모리 하드, 최신 구현 복잡성

실제 구현: Node.js에서 BCrypt 사용하기

비밀번호 해싱

const bcrypt = require('bcrypt');

async function hashPassword(plainTextPassword) {
  const saltRounds = 10;
  try {
    const hash = await bcrypt.hash(plainTextPassword, saltRounds);
    console.log('해싱된 비밀번호:', hash);
    return hash;
  } catch (error) {
    console.error('해싱 오류:', error);
  }
}

hashPassword('mySecurePassword123');

비밀번호 검증

async function verifyPassword(plainTextPassword, hashedPassword) {
  try {
    const match = await bcrypt.compare(plainTextPassword, hashedPassword);
    if (match) {
      console.log('비밀번호 일치!');
    } else {
      console.log('비밀번호 불일치.');
    }
    return match;
  } catch (error) {
    console.error('검증 오류:', error);
  }
}

verifyPassword('mySecurePassword123', '$2b$10$...');

작업 계수 조정하기

const bcrypt = require('bcrypt');

async function findOptimalCost() {
  let cost = 8;
  const start = Date.now();

  do {
    cost++;
    const salt = await bcrypt.genSalt(cost);
    await bcrypt.hash('test', salt);
  } while (Date.now() - start < 50);  // 목표: 약 50ms

  console.log(`최적의 작업 계수: ${cost}`);
  return cost;
}

findOptimalCost();

BCrypt 사용 시 주의사항

  1. 작업 계수 선택: 보안과 성능의 균형을 고려해야 합니다.
  2. 비동기 함수 사용: 동기 함수는 메인 스레드를 블록할 수 있습니다.
  3. 입력 길이 제한: BCrypt는 72바이트로 입력을 제한합니다. 더 긴 비밀번호는 잘립니다.
  4. 버전 차이 주의: $2a$, $2b$, $2y$ 등 버전에 따른 차이를 이해해야 합니다.

결론

BCrypt는 견고하고 시간 검증된 비밀번호 해싱 솔루션입니다. 적응형 특성과 내장된 보안 기능으로 현재와 미래의 공격에 대비할 수 있습니다. 개발자로서 BCrypt를 적절히 구현하면 사용자의 비밀번호를 효과적으로 보호할 수 있습니다.

보안은 끊임없이 진화하는 분야입니다. BCrypt를 시작점으로 삼되, 새로운 보안 동향과 기술을 계속 주시하며 시스템을 발전시켜 나가는 것이 중요합니다.

728x90
댓글