Develop

[JAVA] Bcrypt를 이용하여 비밀번호 암호화하기 본문

웹 개발/Java

[JAVA] Bcrypt를 이용하여 비밀번호 암호화하기

개발 기록 2024. 2. 26. 14:42

Bcrypt(비크립트) 를 이용하여 암호화 하기

 

암호화란?

어떤 평문을 암호문으로 바꾸는 것

 

복호화란?

암호문을 평문으로 바꾸는 것

 

평문은 우리가 봤을때 바로 알아볼 수 있지만

암호화된 암호문은 봤을때 어떤 뜻이지 알아보기 힘들다

 

 

암호화는 데이터가 유출 되었을 때 어떤 정보인지 알아보지 못하게 하기 위한 것이다

유출 자체를 막을 수는 없지만 정보를 보호해주는 기능을 한다.

주로 비밀번호를 암호화하여 사용자의 계정을 보호한다.

따라서 DB에는 사용자의 비밀번호가 그대로 저장되지 않고 암호화된 비밀번호가 저장된다.

 


 

암호화 방식

먼저 복호화가 가능한지에 따라 양방향(복호화 가능)과 단방향(복호화 불가능)으로 구분되고

양방향에서 복호화할 때 사용하는 비밀키가 암호화할 때 그대로 사용되면 대칭키, 다른 키를 사용하 면 비대칭키(공개키)가 된다. 

 

Bcrypt란?

단방향 암호화를 위해 만들어진 해쉬함수

단방향이므로 복호화는 불가능하며 현재 사용되는 해쉬 알고리즘 중 가장 강력한 암호화 방식이다.

추가적으로 2^4 ~ 2^31 회의 반복 횟수를 설정할 수 있어서 처리되는 시간을 임의로 늘림으로써

무차별적인 대입을 통한 해킹 방식을 방어할 수 있다.

 

참고로 Bcrypt 알고리즘은 SHA-256 알고리즘과 다르게 동일한 평문도 매번 다른 해쉬값으로 나타나므로 단순하게 비교하는 방식으로는 같은 값인지 알 수 없다.

 

Hash(해쉬) 함수란?

임의의 길이를 갖는 임의의 데이터에 대해 고정된 길이의 데이터로 매핑하는 함수

ex) 비밀번호가 1234 라면 'dsk3l1kkdoz@...' 64자리의 값으로 암호화 해준다.

비밀번호가 몇자리든 고정된 길이 즉 64자리의 값으로 암호화해주며 암호화 된 값을 해쉬값이라고도 한다.

 

추가 내용

암호화된 비밀번호가 DB에 저장되므로 사용자가 비밀번호를 입력할 때마다 암호화가 이루어지며,

암호화된 값과 DB의 값이 일치하는지 비교한다.

 

기존에 SHA-256과 같은 해쉬 함수들이 사용되고 있었으나

SHA-256은 암호화를 위한 구조보단 짧은 시간 내에 데이터를 빠르게 검색하기 위한 구조로 설계 되어있다.

즉 빠른 속도가 장점이지만 보안에는 취약하다.

또한 미리 해시 값들을 계산해 놓은 테이블 Rainbow table(레인보우 테이블) 이 존재하므로

해쉬값을 역추적 할 수 있어 보안 측면의 취약점이 존재한다.

 

Bcrypt는 기존의 단방향 해쉬 함수들이 가지고 있는 단점들을 보완하기 위해

일반적으로 2가지 보완방법이 사용된다.

 

1. Salting (솔팅)

실제 비밀번호 이외에 추가적으로 랜덤한 값을 더해서 해쉬 값을 계산하는 방법이다.

음식에 소금을 뿌린다해서 Salting이라고 부르며 유저의 비밀번호(음식)에 난수(소금)를 추가하여 암호화하는 방법이다.

비밀번호가 길어지고 복잡해지면 레인보우 테이블에 존재할 확률이 낮아지고 비밀번호를 알아내기 위해서는 많은 시간이 걸리기 때문에 해킹이 어려워진다. 

 

2. Key Stretching (키 스트레칭)

단방향 해쉬 값을 계산한 뒤 그 해쉬값을 해쉬하고 또 해쉬하는 방법이다.

키 스트레칭을 적용하여 동일 장비에서 1초에 5번 정도만 비교할 수 있도록 한다.

즉 기존 단방향 해쉬 알고리즘의 빠른 실행속도가 취약점인 것을 보안하기 위한 방법이다.

 


 

코드

Spring boot + Spring security 가 필요하고

maven 이나 gradle을 통해 준비할 수 있다.

나는 maven을 사용중이므로 maven을 이용하겠다.

 

 만약 gradle을 사용하고싶다면

Springboot에서 BCrypt 암호화(해시화) 구현하기 feat. 시큐리티 인증 없이 BCrypt 사용하기 (tistory.com)

 

Springboot에서 BCrypt 암호화(해시화) 구현하기 feat. 시큐리티 인증 없이 BCrypt 사용하기

들어가며 스프링 시큐리티를 공부하다가 만나게 된 BCrypt 알고리즘은 어떤 특징이 있나 살펴보기 위해 포스팅을 작성했다. 또한 예전에 기술면접에서 BCrypt에 대해 잘못된 대답을 했던 부끄러운

bbubbush.tistory.com

 

 

이 글 참고하기!

 

Maven

1. pom.xml에 spring-security 추가

 

maven 사이트 접속

 

=> spring-security 검색

 

=> Spring 버전에 맞는 core, web, config 추가

 

 

메이븐 사이트

https://mvnrepository.com/

 

 

 

Spring 버전과 맞는지 확인하는 방법

라이브러리 하나를 클릭해서 들어간 뒤에

밑으로 내린다음 compile dependencies 에 나와있는 Spring 버전을 확인하면 된다.

 

내 Spring버전은 pom.xml에서 확인할 수 있다

 

pom.xml

 

내 Spring은4.3.8 버전인데 compile Dependencies 에 있는 지원 버전이 4.3.5 와 4.3.9 밖에 없길래

현재 내 스프링 버전보다는 낮은 버전이지만 4.3.5 버전을 지원해주는 4.2.2 버전 라이브러리를 사용했다.

 

 

 

web과 config도 같은 버전으로 추가해주었다.

 

<!-- 암호화 -->
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>4.2.2.RELEASE</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-web -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>4.2.2.RELEASE</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>4.2.2.RELEASE</version>
</dependency>

 

 

추가했다면 저장 후 메이븐 업데이트를 해주자

 

 

 

2. spring-security.xml 생성

 

WEB-INF/spring 에 spring-security.xml을 생성하고 아래코드를 복사 및 붙여넣기를 한다.

 

<?xml version="1.0" encoding="UTF-8"?>
   <beans:beans xmlns="http://www.springframework.org/schema/security"
       	xmlns:beans="http://www.springframework.org/schema/beans"
       	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       	xsi:schemaLocation="http://www.springframework.org/schema/beans
       	http://www.springframework.org/schema/beans/spring-beans.xsd
       	http://www.springframework.org/schema/security
       	http://www.springframework.org/schema/security/spring-security.xsd">
       
	<beans:bean id="bcryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />  
 
</beans:beans>

 

 

3. web.xml 수정

web.xml 파일에서

/WEB-INF/spring/root-context.xml 아래에 

/WEB-INF/spring/spring-security.xml을 추가해준다.

(나는 /WEB-INF/spring/root-context.xml  대신 /WEB-INF/spring/*-context.xml 으로 설정해놨음)

 

 

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        /WEB-INF/spring/*-context.xml
        /WEB-INF/spring/spring-security.xml	
    </param-value>
</context-param>

 

 

 

4. Bcrypt를 사용하고자하는 Controller에 @Autowired 나 @Inject 를 사용해 추가하기

 

	@Inject
	BCryptPasswordEncoder passEncoder;

 

 

이제 사용하면 된다!

 

회원가입시 암호화할 때는

passEncoder.encode(사용자가 입력한 비밀번호); // 암호화된 비밀번호가 나온다

 

로그인시 비교할 때는

passEncoder.matches( 사용자가 입력한 비밀번호, 암호화된 DB 비밀번호)

 

 

이렇게 작성하면 된다.

 


 

Controller에 작성한 회원가입 코드

	// 유효성 검사용 정규식 
	public static final String us_id_reg_check = "^[a-zA-Z0-9][a-zA-Z0-9]{2,7}";
	public static final String us_pw_reg_check = "^[a-zA-Z0-9][a-zA-Z0-9]{2,7}";
	public static final String us_name_reg_check = "^[가-힣][가-힣]{0,9}";
	public static final String us_nickname_reg_check = "^[a-zA-Z0-9ㄱ-힣][a-zA-Z0-9ㄱ-힣]{0,7}";
	public static final String us_tel_reg_check = "^010[0-9]{8}";
	
	// 회원가입
	@ResponseBody
	@RequestMapping(value = "userJoin", method = RequestMethod.POST)
	public int userJoinPOST(UserVO userVO, String us_pw_check) throws Exception {
		logger.debug("userJoinPOST(UserVO userVO)호출");
		logger.debug("회원가입 정보 userVO : " + userVO);
		logger.debug("회원가입 정보 pw_ch : " + us_pw_check);
		
		boolean us_id = false, us_pw = false, us_pw_ck = false, us_name = false, us_nickname = false, us_tel = false;
		
		us_id = Pattern.matches(us_id_reg_check, userVO.getUs_id());
		us_pw = Pattern.matches(us_pw_reg_check, userVO.getUs_pw());
		us_name = Pattern.matches(us_name_reg_check, userVO.getUs_name());
		us_nickname = Pattern.matches(us_nickname_reg_check, userVO.getUs_nickname());
		us_tel = Pattern.matches(us_tel_reg_check, userVO.getUs_tel());

		if (userVO.getUs_pw().equals(us_pw_check)) {
			us_pw_ck = true;
		}

		// 모든 조건 만족시
		if (us_id && us_pw && us_pw_ck && us_name && us_nickname && us_tel) {
			String encoding_pw = passEncoder.encode(userVO.getUs_pw());
			userVO.setUs_pw(encoding_pw);
			return uService.userJoin(userVO);
		}
		
		return 0;
	}

 

 

Cotroller에 작성한 로그인 코드

	// 로그인 시도(login-post)
	@ResponseBody
	@RequestMapping(value = "/userLogin", method = RequestMethod.POST)
	public int userLoginPOST(UserVO loginVO, HttpSession session) throws Exception {
		logger.debug("userLoginPOST()호출");
		
		// 아이디에 관한 회원 정보 들고오기
		UserVO resultVO = uService.userLogin(loginVO);
		
		// 정보가 있으면
		if (resultVO != null) {
			// 비밀번호 일치 여부 확인
			if(passEncoder.matches(loginVO.getUs_pw(), resultVO.getUs_pw())) {
				// 세션에 로그인 정보 저장
				session.setAttribute("us_id", resultVO.getUs_id());
				session.setAttribute("us_nickname", resultVO.getUs_nickname());
				return 1;
			};
		}
		return 0;

	}

 

 

회원가입 및 로그인 모두 잘 동작한다.


 

참고한 글

 

bcrypt를 통해 비밀번호 암호화하기! (velog.io)

 

bcrypt를 통해 비밀번호 암호화하기!

bcrypt를 활용한 비밀번호 암호화 방법에 대해 알아보자 📚

velog.io

 

gradle

 

Springboot에서 BCrypt 암호화(해시화) 구현하기 feat. 시큐리티 인증 없이 BCrypt 사용하기 (tistory.com)

 

Springboot에서 BCrypt 암호화(해시화) 구현하기 feat. 시큐리티 인증 없이 BCrypt 사용하기

들어가며 스프링 시큐리티를 공부하다가 만나게 된 BCrypt 알고리즘은 어떤 특징이 있나 살펴보기 위해 포스팅을 작성했다. 또한 예전에 기술면접에서 BCrypt에 대해 잘못된 대답을 했던 부끄러운

bbubbush.tistory.com

 

maven 

 

bcrypt를 이용해 비밀번호 암호화하기 (Spring / Maven) (velog.io)

 

bcrypt를 이용해 비밀번호 암호화하기 (Spring / Maven)

spring security의 bcrypt를 사용해 암호화를 해보자!

velog.io