BCryptPasswordEncoder 뜯어보기

무지성개발자·2023년 10월 6일
0

BCryptPasswordEncoder

Spring Security를 사용해서 Password를 저장하고 인증 할 때 사용 되는 BCryptPasswordEncoder는 Password를 hash값으로 만들 때 salt와 key strech 방법을 사용한다.

  • salt는 password에 랜덤한 문자열을 추가 하는 것을 말하며
  • key strech는 hash를 여러번 돌리는 것을 말한다.

BCryptPasswordEncoder 순서

  • 비밀번호 저장 할 때
    • 비밀번호 받음.
    • getSalt()메서드로 salt값을 얻음.
    • hashpw(비번, salt, false) 메소드를 통해 비밀번호 hashing
    • key strech 값은 salt값안에 정해져 있어, bit연산으로 횟 수를 최종 결정함.
  • 비밀번호 인증
    • 비밀번호 받음.
    • 해시된 유저 비밀번호 가져옴.
    • 해시된 유저 비밀번호에 salt값이 있음. 때문에 같은 salt로 hashing가능
    • hashpw(비번, salt, true) 메소드를 통해 비밀번호 hashing.
    • hashing값을 비교해서 비밀번호 인증 처리.

hashpw() 메소드

비밀번호를 저장하던 인증을 하던 결국 hashpw()메서드를 통해 hashing를 한다.

	private static String hashpw(byte passwordb[], String salt, boolean for_check) {
		BCrypt B;
		String real_salt;
		byte saltb[], hashed[];
		char minor = (char) 0;
		int rounds, off;
		StringBuilder rs = new StringBuilder();
		
        // getSalt() or hash값에 있는 salt는 버전+key strech 값 + salt가 합쳐진 문자열이다.
		if (salt == null) {
			throw new IllegalArgumentException("salt cannot be null");
		}

		int saltLength = salt.length();

		if (saltLength < 28) {
			throw new IllegalArgumentException("Invalid salt");
		}

		if (salt.charAt(0) != '$' || salt.charAt(1) != '2') {
			throw new IllegalArgumentException("Invalid salt version");
		}
		if (salt.charAt(2) == '$') {
			off = 3;
		}
		else {
			minor = salt.charAt(2);
			if ((minor != 'a' && minor != 'x' && minor != 'y' && minor != 'b') || salt.charAt(3) != '$') {
				throw new IllegalArgumentException("Invalid salt revision");
			}
			off = 4;
		}

		// Extract number of rounds
		if (salt.charAt(off + 2) > '$') {
			throw new IllegalArgumentException("Missing salt rounds");
		}

		if (off == 4 && saltLength < 29) {
			throw new IllegalArgumentException("Invalid salt");
		}
        
        // salt에 있는 key strech 값을 가져와 몇번 hashing할지 정한다.
		rounds = Integer.parseInt(salt.substring(off, off + 2));
        
		// salt에 있는 버전, key strech 값을 제외한 진짜 salt값을 뽑는다.
		real_salt = salt.substring(off + 3, off + 25);
		saltb = decode_base64(real_salt, BCRYPT_SALT_LEN);

		if (minor >= 'a') {
			passwordb = Arrays.copyOf(passwordb, passwordb.length + 1);
		}

		B = new BCrypt();
        
        // 진짜 salt값과 key streach를 이용해서 hash값을 구함.
		hashed = B.crypt_raw(passwordb, saltb, rounds, minor == 'x', minor == 'a' ? 0x10000 : 0, for_check);

		rs.append("$2");
		if (minor >= 'a') {
			rs.append(minor);
		}
		rs.append("$");
		if (rounds < 10) {
			rs.append("0");
		}
		rs.append(rounds);
		rs.append("$");
        
        // salt 값 + 해쉬 값을 base64로 인코딩해서 합친다.
		encode_base64(saltb, saltb.length, rs);
		encode_base64(hashed, bf_crypt_ciphertext.length * 4 - 1, rs);
		return rs.toString();
	}

위 코드에서 중요한건 주석을 단 부분으로

  • getSalt() 또는 해시된 비밀번호에서 salt값을 가져온다.
  • salt값에서 key strech 값을 가져온다.
  • 비밀번호에 순수 salt값과 key strech 값을 사용해서 비밀번호의 해시값을 구한다.
  • salt값과 해시값을 붙여서 해시된 비밀번호로 사용한다.

한 줄평 : hash된 값은 복호화를 할 수 없어서 encoding이든 match든 같은 해시 함수를 사용 할 수 밖에 없다.

profile
no-intelli 개발자 입니다. 그래도 intellij는 씁니다.

0개의 댓글