bcrypt는 Niels Provos와 David Mazières가 설계한 비밀번호 해싱(password-hashing) 함수이다. 1999년 USENIX 학술 대회에서 처음 발표되었으며, Blowfish 암호를 기반으로 한다. 솔트(salt)를 포함하여 레인보우 테이블 공격을 방어하며, 반복 횟수를 조정할 수 있는 적응형(adaptive) 함수로서 시간이 지남에 따라 연산 능력이 증가하더라도 무차별 대입 공격에 저항할 수 있도록 설계되었다.
배경
Blowfish는 블록 암호 중에서도 키 설정(key setup) 단계가 매우 비용이 많이 드는 특징이 있다. Provos와 Mazières는 이 특성을 활용하여 Eksblowfish("expensive key schedule Blowfish")라는 새로운 키 설정 알고리즘을 개발하였다. 이 알고리즘은 솔트와 비밀번호를 모두 사용하여 모든 서브키를 설정하며, 설정 가능한 라운드 수를 통해 연산 속도를 임의로 느리게 만들 수 있다.
알고리즘 개요
bcrypt 함수의 입력값은 다음과 같다:
- 비밀번호 문자열 (최대 72바이트)
- 비용(cost) 값 (4~31 사이의 숫자, 2^cost 회의 반복을 의미)
- 16바이트(128비트) 솔트 값
출력은 24바이트(192비트) 해시이며, 최종 출력 문자열은 다음과 같은 형식을 가진다:
$2a$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW
$2a$: 해시 알고리즘 식별자 (bcrypt)12: 비용 값 (2^12 = 4,096 라운드)- 그 뒤 22자는 솔트, 31자는 해시 값의 base-64 인코딩 결과
bcrypt는 내부적으로 "OrpheanBeholderScryDoubt"라는 24바이트 텍스트를 Blowfish로 64회 반복 암호화한다.
버전 역사
- $2$ (1999): 최초 사양. Modular Crypt Format 사용.
- $2a$: 비ASCII 문자와 널 종결자(null terminator) 처리를 명확히 한 개정판.
- $2x$, $2y$ (2011년 6월): PHP 구현체(crypt_blowfish)에서 8번째 비트가 설정된 문자 처리 버그 발견. $2x$는 기존(결함 있는) 해시, $2y$는 수정된 알고리즘으로 생성된 해시를 표시. 단, 이 표기법은 crypt_blowfish에 한정되었으며 OpenBSD 등 다른 구현체에서는 채택되지 않음.
- $2b$ (2014년 2월): OpenBSD 구현체에서 255바이트를 초과하는 비밀번호가 올바르게 잘리지 않는 버그 발견 및 수정.
비판 및 한계
- 최대 비밀번호 길이: bcrypt는 비밀번호를 최대 72바이트로 제한한다. UTF-8 인코딩에서 문자당 최대 4바이트가 필요한 경우, 최악의 시나리오에서는 18자로 제한된다.
- 해시 잘림(truncation): 정식 OpenBSD 구현체는 24바이트 해시 중 23바이트만 사용하며, 그 이유는 명확히 알려져 있지 않다.
- Base64 인코딩: OpenBSD 구현체가 사용하는 Base64 알파벳은 RFC 4648 Base64와 호환되지 않는 독자적인 방식을 사용한다.
다른 비밀번호 해싱 알고리즘과의 비교
bcrypt는 키 유도 함수(KDF)가 아니라 비밀번호 해싱 함수이다. 일반적으로 1초 미만의 실행 시간이 요구되는 비밀번호 인증 시나리오에서 bcrypt는 PBKDF2, scrypt, Argon2보다 강력한 것으로 평가된다. 다만 메모리 사용량이 4KB로 고정되어 있어 메모리 하드(memory-hard) 특성은 scrypt나 Argon2에 비해 낮다.
구현 현황
bcrypt는 OpenBSD의 기본 비밀번호 해시 알고리즘이며, 과거 SUSE Linux 등 일부 리눅스 배포판에서도 기본값으로 사용되었다. C, C++, C#, Go, Java, JavaScript, PHP, Python, Ruby, Rust 등 다양한 언어로 구현체가 존재한다.