일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 파이썬리스트컴프리헨션
- 리스트컴프리헨션
- 자바스크립트
- *args
- decorator
- 자료구조
- CSS
- bcrypt
- docker
- RESTfulAPI
- Python
- 코딩테스트파이썬
- QuerySet
- **kwargs
- 인터넷 네트워크
- clone-coding
- django
- 해시충돌
- 파이썬문법
- DP
- 알고리즘
- 파이썬입출력
- 윈도우우분투듀얼부팅
- clone coding
- 백준
- 인증인가
- wecode
- 파이썬
- promise
- JavaScript
- Today
- Total
개발기록장
[Web] 인증과 인가(1) -인증이란? (+bcrypt) 본문
인증과 인가는 API에서 가장 자주 구현되는 기능 가운데 하나이다.
자주 구현되는 기능이니 만큼 아주 중요한 개념이니 절대 잊지 말자
1. 인증이란?
- 인증(Authentication)이란 유저의 identification를 확인하는 과정이다. 즉, 아이디와 비밀번호를 확인하는 절차이다.
- 인증은 누가 우리 서비스를 쓰는지, 어떻게 쓰는지를 추적하기 위해 필요하다.
- 인증을 하기 위해서는 먼저 유저의 아이디와 비밀번호를 생성하는 기능 그리고 그 유저의 아이디와 비밀번호를 확인하는 기능이 필요하다.
- 쉽게 말하면 회원가입과 로그인으로 인증을 구현한다.
** 로그인 절차와 token
회원가입이 된 유저가 아이디, 비밀번호를 입력하면 서버에서는 입력한 정보가 DB에 저장된 정보와 일치하는지 확인을 하고,
일치하면 로그인 성공과 함께 클라이언트에 access token을 전송한다.
이렇게 로그인에 성공한 유저는 인증된 유저로써 이 서버의 로그인이 필요한 서비스를 이용할 시 request에 access token을 첨부해서 서버에 전송하여 매번 로그인해도 되지 않도록 한다.
2. 비밀번호 관리
- 이러한 인증에 중요한 것은 바로 비밀번호이다.
- 비밀번호는 보안을 위해 반드시 암호화되어서 DB에 저장되어야 한다.
- 비밀번호가 암호화되지 않은 채로 DB에 저장이 되었을 경우, DB가 해킹당하면 유저의 정보가 모두 노출될 수 있고,
- 해킹의 경우가 아니더라도 내부 개발자 등에 의해 유저의 정보가 노출될 가능성이 있기 때문이다.
3. 비밀번호 암호화 방법
- 해시 함수란?
- 임의의 길이의 데이터를 고정된 길이의 데이터로 반환시켜주는 함수이다.
- 입력값의 길이가 달라도 출력값은 언제나 고정된 길이로 반환한다.
- 동일한 값이 입력되면 언제나 동일한 출력 값을 보장한다.
- 해시함수는 암호학적 해시 함수와 비 암호학적 해시함수로 구분된다.
- 단방향 암호화
- 암호화에는 양방향 암호화와 단방향 암호화가 있는데, 비밀번호를 암호화할 때는 주로 단방향 암호화를 사용한다.
- 단방향 암호화란, 평문을 암호문으로 바꾸는 '암호화'는 가능하지만, 암호문을 평문으로 바꾸는 '복호화'는 불가능한 암호화를 말한다.
- 단방향 암호화에는 주로 해시 함수를 이용하는데 이를 단방향 해시 함수(one-way hash function)라고 한다.
- 단방향 해시 함수는 입력값을 문자와 숫자를 임의로 나열한 일정한 길이의 다이제스트(Digest) 형태로 변환시켜준다. 여기서 다이제스트란, 해시함수를 통해 생성된 암호화된 메시지이다.
- 단방향 해쉬함수에는 MD5, SHA-1, SHA-256 등이 있다.
- 단방향 암호화의 취약점
1) 무차별 대입 공격(brute-force attack)에 취약
- 해시 함수는 원래 짧은 시간에 데이터를 검색하기 위해 설계된 것이다. 그렇게 대문에 해시 함수는 본래 처리 속도가 최대한 빠르도록 설계되었다.
- 이런 속성 때문에 공격자는 매우 빠른 속도로 임의의 문자열의 다이제스트와 해킹할 대상의 다이제스트를 비교할 수 있다.
- 이런 방식으로 패스워드를 추측하면 패스워드가 충분히 길거나 복잡하지 않은 경우에는 그리 긴 시간이 걸리지 않는다.
2) Rainbow table attack에 취약
- 단방향 해시 함수는 같은 값을 해싱하면 언제나 같은 다이제스트가 나온다.
- 이 점을 이용하여 사용자들이 많이 쓰는 패스워드를 미리 해싱하여 결과값들을 모아 둔 테이블을 Rainbow table이라고 한다.
- 취약점을 보안하기 위한 기법
1) Salting
- 소금을 친다는 뜻의 salting은 이름처럼 실제 비밀번호의 앞, 뒤 아무 곳에 랜덤 데이터를 더해서 해시값을 계산하는 방법을 말한다.
- 사용된 salt 값은 나중에 비밀번호 일치를 확인하기 위해 같이 저장된다.
- Rainbow table attack을 방지할 수 있는 효과가 있다.
2) Key Stretching
- 해시 암호화를 여러 번 반복하는 기법이다.
- 무차별 대입 공격을 방지하는 효과가 있다.
4. bcrypt를 이용한 암호화 구현
- bcrypt는 Salting과 Key Stretching을 구현할 수 있는 라이브러리로 다양한 언어를 지원한다.
- bcrypt는 처음부터 비밀번호를 단방향 암호화하기 위해 만들어진 해쉬 함수이며 가장 널리 쓰인다.
- bcrypt는 hash 결과값에 salt값과 해시 값 및 반복 횟수를 같이 보관하기 때문에 비밀번호 해싱을 적용하는 데 있어 DB설계를 복잡하게 할 필요가 없어서 암호화를 구현하는데 매우 편리하다.
- bcrypt를 통해 해싱된 결과 값(Digest)의 구조는 아래와 같다.
이제 파이썬에서 bcrypt를 이용하여 비밀번호 암호화를 구현해보자.
1) 먼저 pip로 bcrypt를 설치한다.
pip install bcrypt
2) '12345678'이라는 비밀번호를 암호화하는 과정은 아래와 같다.
>>> import bcrypt
### 평문
>>> password = '12345678'
### bcrypt를 이용하여 암호화 (bcrypt.gensalt()는 salt값을 만든는 함수)
>>> hashed_password = bcrypt.hashpw(password, bcrypt.gensalt())
암호화할 때는 bcrypt의 hashpw 함수를 이용한다.
hashpw의 인자 값으로 암호화 할 패스워드와 salt값을 넣어줄 수 있는데, 이때 암호화 할 패스워드를 평문 그대로 넣으면 아래와 같은 에러가 발생한다.
에러를 읽어보면, unicode 객체는 반드시 인코딩해서 넣어야 된다고 적혀있다.
안내에 따라 password를 인코딩하여 다시 hashpw 함수에 적용해본 결과는 아래와 같다.
>>> hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
>>> hashed_password
b'$2b$12$XXdgXRMbmhCcXOwfZsc3XuAEGb6OC2ZrmanIrLWf9on6Z1iGHpWAm'
# 암호화 된 password를 DB에 저장하기 좋은 형태로 string으로 다시 decode
>>> hashed_password = hashed_password.decode('utf-8')
>>> hashed_password
'$2b$12$XXdgXRMbmhCcXOwfZsc3XuAEGb6OC2ZrmanIrLWf9on6Z1iGHpWAm'
hashed_password는 byte 타입으로 저장이 된다.
하지만 DB에 저장할 때는 byte가 아닌 string 타입으로 저장해줘야 하므로 다시 decode를 적용하여 DB에 저장한다.
3) 암호화된 비밀번호를 입력받은 값과 일치하는지 확인하는 과정은 아래와 같다.
>>> bcrypt.checkpw('12345678'.encode('utf-8'), hashed_password.encode('utf-8'))
True
- bcrypt의 checkpw 함수를 이용하여 입력받은 비밀번호와 DB에 저장된 암호화된 비밀번호가 일치하는지 확인할 수 있다.
- 일치하면 True, 일치하지 않으면 False를 반환한다.
- DB에 저장된 암호화된 비밀번호는 byte 타입이 아니므로 checkpw함수를 쓸 때도 encode을 반드시 해줘야 한다.
- 참고로, hashed_password에는 이미 salt값이 저장되어 있기 때문에 salt값을 가져오는 과정은 추가로 실행할 필요가 없다.
** 나의 궁금증 : 왜 해시된 패스워드를 DB에 string 타입으로 저장해야할까??
나는 회원가입 과정에서 암호화된 비밀번호를 decode하지 않고, 로그인 과정에서 비밀번호 일치를 확인하는 부분에서 DB에 저장 된 값을 가져올 때 encode 해주지 않으면 똑같이 모두 byte타입이기 때문에 문제가 되지 않을 거라고 생각했다.
즉 회원가입 시에는
# 사용자에게 입력받은 값(password)를 암호화 하여 DB에 저장하는 과정
password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
이렇게 DB에 저장을 하고,
로그인 시에는
# 사용자에게 입력받은 값(password)와, DB에 저장되어 있는 값(user.password) 일치 확인
if bcrypt.checkpw(password.encode('utf-8'), user.password):
(생략..)
이렇게 일치 여부를 확인을 하면 똑같은 거 아닌가?라고 생각을 했다.
하지만 이렇게 설정하고 테스트한 결과 bcrypt.checkpw에서 문제가 발생함을 알 수 있었다.
암호화된 비밀번호를 decode하지 않고 DB에 저장하면
b'$2b$12$XXdgXRMbmhCcXOwfZsc3XuAEGb6OC2ZrmanIrLWf9on6Z1iGHpWAm'
이런 식으로 저장이 되는데, DB에서는 이 자체를 그냥 String 타입으로 인식하고 저장해버린다.
여기까지는 문제가 없지만, bcrypt.checkpw() 메소드에 들어가는 두 인자값은 모두 byte 타입으로 들어가야 한다.
여기서 user.password가 b.~~~이긴 하지만 실제로는 이 자체로는 string이기 때문에 에러가 나는 것이다.
만일에 여기서
if bcrypt.checkpw(password.encode('utf-8'), user.password.encode('utf-8'):
을 한다면, TypeError는 안나지만 b.~~~~로 저장되있는 값을 encode 하기 때문에 b.b.~~~가 되어버린다.
이렇게되면 비밀번호 일치 여부를 확인할 수 없게 된다.
따라서 비밀번호 암호화 하여 DB에 저장해야하고, checkpw를 할 때도 encode를 해줘야 정상적인 처리를 할 수 있다.
- 한 줄 요약
import bcrypt
# 평문
password = '12345678'
# 암호화하여 저장
hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
# 일치 여부 확인
bcrypt.checkpw('12345678'.encode('utf-8'), hashed_password.encode('utf-8'))
'TIL > Web' 카테고리의 다른 글
[CS] 웹 브라우저 요청 흐름 (www.google.com을 치면 어떤 일이 일어나는가) (0) | 2023.01.29 |
---|---|
[CS] 기본적인 인터넷 네트워크 개념 (0) | 2023.01.29 |
[Web] 인증을 유지시키는 여러가지 방법 (0) | 2021.02.21 |
[Web] 인증과 인가(2) -인가란? (+JWT) (0) | 2021.02.07 |