BCrypt

2021-02-24

BCrypt에 대해 정리를 한 내용 입니다.


단방향 해시 함수

단방향 해시 함수는 수학적인 연산을 통해 원본 메시지를 변환하여

암호화된 메시지(Digest)를 생성합니다.

원본 메시지를 알면 암호화된 메시지를 구하기 쉽지만

암호화된 메시지로는 원본 메시지를 알 방법이 없어야 하므로 "단방향성"이라고 합니다.

이미지 출처

단방향 해시 함수의 문제점

1. 인식 가능성

동일한 패스워드 문구동일한 Digest를 얻게 된다면

Digest를 다량 확보한 다음,

비교하면서 원본 메시지를 찾아내거나 동일한 효과의 메시지를 얻을 수 있습니다.

이 말은 같은 결과값을 얻어낼 수 있으니 

비교할 대상이 많아져 정보를 탈취당하기 좋다는 뜻으로 이해했습니다.

2. 속도

해시 함수는 짧은 시간에 데이터를 검색하기 위해 설계되어있습니다.

처리속도가 빠름으로 인해 임의의 문자열의 Digest

해킹할 대상의 Digest를 비교할 수 있습니다.

해결책

솔팅

Salt는 단방향 해시 함수에서 Digest를 생성할 때 추가되는

"바이트 단위의 임의의 문자열" 입니다.

이미지 출처

Salt인 "8zff4fgflgfd93fgdl4fgdgf4mlf45p1"를 추가해 Digest를 생성한 모습입니다.

이렇게 하면 Digest를 탈취 당하더라도,

다른 Salt를 사용한다면 Digest가 다르게 생성됩니다.

키 스트레칭(key stretching)

입력한 패스워드의 Digest를 생성하고,

생성된 Digest를 입력 값으로 하여 Digest를 생성하고,

이를 반복하는 방법으로 Digest를 생성할 수도 있습니다.

이렇게 하면 입력한 패스워드를 동일한 횟수만큼 해시해야만

입력한 패스워드의 일치 여부를 확인할 수 있습니다.

이것이 기본적인 키 스트레칭 과정입니다.

이미지 출처

양식

bcrypt 해시 문자열은 다음과 같은 양식입니다.

$2b$[cost]$[22 character salt][31 character hash]

간단한 예시

$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy

  • $2a$ : 해시 알고리즘의 식별자이자 version의 값입니다.
  • 10 : Cost, 키 스트래칭을 몇 번 했는가 알게 해줍니다.(10 = 2의 10승 라운드)
  • N9qo8uLOickgx2ZMRZoMye : salt 값, 22자
  • IjZAgcfl7p92ldGxad68LJZdL17lhWy : 해시 값, 31자

궁금증

img.png

Salt 값은 랜덤으로 넣어주기 때문에 위 사진처럼 매번 다른 Digest가 나옵니다.

그럼 어떻게 패스워드가 같다는걸 확인하는지 궁금했습니다.

Salt 값을 어딘가에 저장해 두는건가?

BCrypt의 경우 Hash 값 내부에 Salt 값이 포함되기 때문에

Salt 값을 따로 저장하지 않아도 Hashing 된 값과 평문을 비교할 수 있었습니다.

BCryptPasswordEncoder 내부 코드를 살펴봅니다.

	@Override
	public boolean matches(CharSequence rawPassword, String encodedPassword) {
		if (rawPassword == null) {
			throw new IllegalArgumentException("rawPassword cannot be null");
		}
		if (encodedPassword == null || encodedPassword.length() == 0) {
			this.logger.warn("Empty encoded password");
			return false;
		}
		if (!this.BCRYPT_PATTERN.matcher(encodedPassword).matches()) {
			this.logger.warn("Encoded password does not look like BCrypt");
			return false;
		}
		return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
	}

matches()로 같은가 다른가를 확인 할 수 있어서 해당 함수를 찾아봤습니다.

그런데 여기서 BCrypt.checkpw에서 Salt 값 없이 평문과 Hash 값만으로 값을 검증했습니다.

즉, 검증에는 Salt 값이 필요가 없다는 것이기에 Salt 값은 따로 저장할 필요가 없다고 판별했습니다.

백기선 선생님의 말씀

누군가 저와 같은 생각을 해서 질문을 남겼습니다.

matches로 plain 패스워드와 인코딩 된 패스워드를 비교할 때는 salt를 사용하지 않습니다. 

오로지 plain 패스워드와 인코딩된 패스워드만 가지고 해시를 하고 

그 결과는 인코딩 된 패스워드와 일치하게 되어있습니다. 

따라서 salt가 바뀌는건 신경쓰지 않아도 됩니다. 인코딩 할 때만 쓰이니까요.

그러므로 match를 사용하면

plain 패스워드Digest를 비교하면 가능하다 라고 생각하게 되었습니다.

인프런 질문.

참조

nameunzz.log

네이버 D2

위키피디아 : bcrypt

인프런