본문 바로가기

Network

[인증] 로그인 / 인증 / 암호화 / bcrypt

 

인증(Authentication)

▶  인증이란?

사용자의 신원을 확인하는 절차

로그인을 하여 아이디와 비번을 확인하는 절차

 

▶ 인증 절차

1) 사용자 가입 절차를 진행해서 사용자의 아이디와 비밀번호를 생성

2) 아이디와 암호화한 비밀번호를 데이터베이스에 저장

3) 로그인할 때 본인의 아이디와 비밀번호를 입력

4) 입력한 비밀번호를  암호화한 후, 암호화되어 DB에 저장된 값과 비교하여 일치하면 로그인 성공

5) 백엔드 API 서버는 access token을 프론트엔드 혹은 클라이언트에게 전송

6) 프론트엔드는 access token을 첨부해서 request를 서버에 전송

 


 

엑세스 토큰 알아보기(JWT 토큰)


▶ 왜 사용자의 비밀번호를 암호화해서 저장해야 하는가?

1) 외부의 해킹 공격에 의해 데이터베이스가 노출되었을 경우를 방지하기 위해

2) 내부 인력에 의해 데이터베이스가 노출되었을 경우를 방지하기 위해

 

▶ 비밀번호를 어떻게 관리해야 하는가?

1) Database에 저장 시 개인 정보를 하여 복원할 수 없도록 관리

암호화할 때는 단방향 해시 함수가 일반적으로 사용하여 복호화할 수 없도록 하는 것, 즉 온전히 본래의 비밀번호 값을 알지 못하도록 방지하는데에 목적이 있는 것.

2) 통신 시 개인 정보를 주고받을 때 SSL을 적용하여 암호화(https)

* http 와 https(or SSL) : http를 통해 통신하고 있다면 누군가 볼 수 있다. https를 통해 통신하고 있다면 누군가 웹을 가로챈다고 하더라도 내용을 볼 수 없다. 즉, http를 통해 로그인을 요청한다면 그 사이트에서는 로그인 해서는 안 된다!

 

 

▶ 비밀번호 암호화 방법

 

1) 해싱

* 단방향 해시 함수는 수학적인 연산을 통해 원본 메시지를 변환하여 암호화된 메시지인 다이제스트를 생성

* 단방향으로,  복호화가 불가능

* 같은 알고리즘으로 같은 인풋을 해싱하면 항상 같은 결과가 도출되는 허점이 있음 (= 동일한 password 값이면 동일한 digest값을 갖음)

rainbow attack이라 하여, 테이블을 먼저 생성한 뒤 함수 값을 역추적해서 본래 값을 찾아내는 해킹방법을 사용하면 보안이 뚫릴 수 있음.

   → 보완하여 2) Salting & 3) Keystretching 이 나옴.

* 단방향 해쉬 함수 종류 : MD5, SHA-1, SHA-256 등

2) Salting

* 입력한 비밀번호와 임의로 생성한 문자열을 합쳐서 해싱해서 이 해시값을 저장하는 방법

* 비교를 위해 해시값과 솔트값을 같이 저장해야 함.

3) Keystretching

* 해커가 무작위 대입을 통해 해시값을 계산하는데 필요한 시간을 대폭 늘리기 위해 salting 및 해싱을 여러번 반복해서 원본 값을 유추하기 어렵게 만드는 것

▶ bcrypt

Salting & Keystretching 대표적인 라이브러리

hash 결과값에 소금값과 해시값 및 반복횟수를 같이 보관하기 때문에 DB 설계를 복잡하게 할 필요가 없음

bcrypt 를 통해 해싱된 결과값 구조

 

▶ bcrypt 암호화 하여 DB에 패스워드 저장하기

class SignupView(View):
    def post(self, request):
        ...

        password = data["password"]

        1)hashed_password = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt())
        2)decoded_password = hashed_password.decode("utf-8")

        User.objects.create(
            ...
            password=decoded_password,
            ...
        )
        return JsonResponse({"MESSAGE": "SUCCESS"}, status=201)

bcrypt.hashpw(password, bcrypt.gensalt()) → 유니코드 객체는 해싱전에 인코딩되어야 한다는 에러 발생

문자열 '1234'를 encode 하면 byte '1234'가 되고, bytes 1234를 decoding 하면 문자열 '1234'가 된다!

>> encoded_password=password.encode('utf-8')   b'1234'  / bytes 타입

>> decoded_password=encoded_password.decode('utf-8')  '1234' / str 타입

 

1) hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())

 b'$2b$12$jU65cFfWiRbkGpyI0o2Q5uUQco/BDyKhWam.1jjB4QViaslguJ.He'

→ bytes 타입으로 출력

 

 여기서, bcrypt.gensalt() 값은 임의의 값이다.

>>> bcrypt.gensalt()
b'$2b$12$m0S8wyAvITBMDHKtDoaV1O'

>>> bcrypt.gensalt()
b'$2b$12$.MClbPzBZIdlXvUeMNLNy.'

 

 

2) decoded_password = hashed_password.decode("utf-8")

→ DB에는 문자열 형태로 저장해야 하므로, decode 과정을 거친다!

 

▶ 로그인 시 입력한 패스워드와 DB에 저장되어 있는 패스워드 일치여부 확인하기

class SigninView(View):
    def post(self, request):
        data = json.loads(request.body)
            ...
            user = User.objects.get(email=data["email"])

            1)if not bcrypt.checkpw(
                data["password"].encode("utf-8"), user.password.encode("utf-8")
            ):
                return JsonResponse({"MESSAGE": "INVALID_USER"}, status=401) 
            ...
        except KeyError:
            return JsonResponse({"MESSAGE": "KEY_ERROR"}, status=400)

 

1) bcrypt.checkpw(data["password"].encode('utf-8'), user.password.encode("utf-8"))

로그인 시 입력한 패스워드(data["password"]) & 데이터베이스의 패스워드(user.password)

모두 encode("utf-8") 하여 bytes 형태로 일치하는지 비교!

 

 

 

 

 

 

 

▣ 출처

NAVER D2

https://st-lab.tistory.com/100