암호화와 토큰의 이해

Kepler·2020년 2월 14일
1

Python

목록 보기
9/12

암호화 (encryption)

암호화(encryption)는 정보를 노출시키지 않기 위해 특정 알고리즘을 이용하여 암호화된 형태로 변형하는 것을 말하며 이에 역행하는 과정을 복호화(decryption)라고 하며 이로써 암호화된 정보를 다시 읽을 수 있다...

개발에 있어 암호화란?

보안이 필요한 정보를 특정 알고리즘을 통해 의미없는 문자열(이진수 덩어리)로 바꾸는 것을 의미하며 이로써 악의적인 목적으로 암호를 해독해 공격하는 것을 방지하는 것을 의미한다. 보안을 위해 시스템적인 보호 방법 보다 근본적인 해결방법이라 할 수 있다

(Quotes: http://myblog.opendocs.co.kr/archives/907)

정보통신망법, 개인정보보호법에 의거 암호화는 필수이다.

encryption과 encoding을 헷갈리지 말자.

encoding의 목적은 어떠한 시스템에 의해 제대로 데이터가 쓰일 수 있도록 데이터를 변환하는(transform) 것이다. 즉, 데이터의 사용성에 목적을 둔다.

encryption의 목적은 데이터를 제3자로 부터 보호하기 위하여 변환 하는 것이다. 사용성에 중점을 둔 것이 아니라, 데이터의 보완에 목적을 둔다.

Encryption transforms data into another format in such a way that only specific individual(s) can reverse the transformation. It uses a key, which is kept secret, in conjunction with the plaintext and the algorithm, in order to perform the encryption operation. As such, the ciphertext, algorithm, and key are all required to return to the plaintext.

(Quote, content: https://danielmiessler.com/study/encoding-encryption-hashing-obfuscation/)

암호화의 종류

단방향

원래대로 돌이킬 수 없는 상태가 되는 암호화 방법이다. decoding이 어려기 때문에 보안성이 높아서, 비밀번호 관리에 많이 쓰이는 방식이다. 외부 공격뿐 아니라 노출(개발자, 사이트관리자)에서도 보호가 가능하다.

양방향

알고리즘을 사용하여 decoding이 가능한 암호화이다. 암호화된 정보를 다시 풀어서 봐야할 때 사용한다.

단방향에서 비밀번호를 검증할 때는 암호화된 값들 끼리(사용자가 입력한 값과 서버에 저장된 값) 비교& 검증이 이루어진다.

단방향 암호화는 미리 해쉬값들을 계산해 놓은 테이블을 사용하는 rainbow table attack에 취약할 수 있다. 따라서, bcrypt와 같은 암호화 라이브러리를 추가로 사용해야 한다.

암호화 오픈소스 라이브러리 : bcrypt

해시(hash)와 솔팅(salting) 기법을 사용한 단방향 암호화 프로그램이다.


해시란?

평문을 해시 함수(알고리즘)을 사용하여 고정된 길이의 암호화된 문자열로 바꾸는
기법이다.

예를 들어 'password2020' 이라는 비밀번호는, 다음과 같이 해시를 통하여 아주 긴 문자열로 암호화되어 저장되게 된다.

f52fbd32b2b3b86ff88ef6c490628285f482af15ddcb29541f94bcf526a3f6c7  

솔팅이란?

솔트(salt)는 단방향 해시 함수에서 다이제스트를 생성할 때 추가되는 바이트 단위의 임의의 문자열이다. 그리고 이 원본 메시지에 문자열을 추가하여 다이제스를 생성하는 것을 솔팅(salting)이라 한다.

비밀번호에 랜덤값을 더해주고 암호화 하는 기법이다.

솔트와 패스워드의 다이제스트를 데이터베이스에 저장하고, 사용자가 로그인할 때 입력한 패스워드를 해시하여 일치 여부를 확인할 수 있다. 이 방법을 사용할 때에는 모든 패스워드가 고유의 솔트를 가진다

decoding시에도 동일한 salting값을 사용하여야 하는데, 이는 hash값 자체에 붙여놈으로써 가능하다. 즉, salting값을 알면 해킹 공격을 당할수도 있다는 것인데, sating의 주 목적은 랜덤값만큼 경우의 수를 늘여줘서 공격 속도를 급격히 늦추는데 있다고 볼 수 있다.

(Quote, image : https://d2.naver.com/helloworld/318732)


Key-stretching:

  • 단방향 해쉬값을 계산 한 후 그 해쉬값을 또 또 해쉬 하고, 또 이를 반복하는 것을 말한다.

bcrypt는 평문으로 받은 데이터를 byte화 하고 salting하면서, 해시를 12번 반복하여 암호화를 실시한다.

예제:

파이썬 가상환경에 pip를 이용하여 bcrypt를 설치해주고, 파이썬 파일을 열어 다음의 코드를 입력해 보자.

import bcrypt

password = '1234'

encoded_pw = password.encode('utf-8')

encoded_pw
>>>b'1234'

hashed_pw = bcrypt.hashpw(password, bcrypt.gensalt())

hashed_pw
>>>b'$2b$12$oqfMsk.AiycRHY2266AM7utY8AIRBDSOk7t0qJFzqLpwwP6inqyIW'

bcrypt모듈을 import해주고, 임의의 패스워드를 설정했다. 패스워드는 문자열의 상태이므로, 이를 먼저 byte타입으로 암호화(.encode('utf-8'))해 줄 필요가 있다.

byte으로 변환한 패스워드는 .hashpw 메소드를 사용하여 hash를 해준다. 이 때, 두번째 argument에는 솔팅을 해주려면 bcrypt.gensalt()) 을 입력한다.

hash_pw을 출력하면 솔팅까지 완료된 암호화된 password가 나타난다.

hashed_pw.decode('utf-8')
>>> '$2b$12$oqfMsk.AiycRHY2266AM7utY8AIRBDSOk7t0qJFzqLpwwP6inqyIW'

byte형태로는 db에 저장이 안되므로, 다시 문자열로 바꾸어주는 decoding이 필요하다. 위와같이 decode한 후, b'가 사라지면서 다시 문자열이 된 것을 확인할 수 있다.

user_input = '1234'

bcrypt.checkpw(user_input.encode('utf-8'), hashed_pw)
>>> True

wrong_input = '12'
bcrypt.checkpw(wrong_input.encode('utf-8'), hashed_pw)
>>> True

이제 유저가 입력한 비밀번호와 서버에 저장된 비밀번호를 비교해보자..checkpw메소드를 사용하여 비교할 수 있다. 이는 boolean으로 값을 리턴한다.

이 때 첫번째 argument로써 넘겨주는 값은 꼭 encoding된 유저의 input값이어야 한다. encoding한 상태에서만, 비밀번호 비교가 가능하기 때문이다 (byte to byte comparison). 두번째 argument는 서버에 있는 패스워드를 넘겨준다.

토큰 (token)

정확히는 access 토큰이라 한다. 한 프로세스의 보안맥락을 나타내는 객체이다.

예를 들어, 네이버에서 한 유저가 자신의 로그인 아이디와 비밀번호를 올바르게 입력하여 로그인 하면, 유저에게 토큰이 발행된다. 이 토큰을 가진 유저에게는 해당 유저만 볼 수 있는 정보(블로그, 이메일 등)에 access가 가능해지며, 네이버에게 다시 유저 인증을 하지 않고도 네이버 안의 다른 탭을 인증된 유저로써 자유롭게 서핑할 수 있다.

토큰을 만료화 시킬 수 있으며(일정시간 이후 재 로그인이 필요), 파이썬 개발시에는 decorator를 사용하여 검증절차를 모듈화 시킬 수 있다.

JWT (JSON Web Token)

JWT는 토큰 기반 인증 방식으로, 클라이언트의 세션 상태를 저장하는 게 아니라 필요한 정보를 토큰 body에 저장해 클라이언트가 가지고 있고 그것을 증명서처럼 사용한다.

(Quote: https://yonghyunlee.gitlab.io/node/jwt/)

발행한 토큰은 Http request header에 들어가며, 이 정보를 가지고 로그인 상태를 유지할 수 있다.

토큰에 담는 정보는 사용자를 식별할 수 있는 ID나 Primary Key등 이다. 토큰안의 정보는 해시 되어서 랜덤으로 분산되어 있으며, 암호화도 되어 있다.

구성:


(Image: Nordic APIs)

  • Header : 알고리즘과 토큰 타입
  • Payload : 유저 아이디와 같은 클라이언트에 대한 정보, meta data (encoded)
  • Signature : 알고리즘, secret key, 서명 with payload & header (encrypted (secret key), JWT를 만든 서버에서만 복호화 가능)

전체를 암호화하면 프론트엔드도 유저정보를 복호화 해야하기 때문에, signature만 암호화.

처리의 흐름:


(Image, content : https://yonghyunlee.gitlab.io/node/jwt/)

예제:

먼저 가상환경에 pip을 사용하여 jwt를 설치한다 (pyjwt).
그리고 jwt를 import 하고, 다음 코드를 입력한다.

import jwt

token = jwt.encode({'user_id' : 1}, 'secretkey', algorhithm = 'HS256')

token
>>>b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxfQ.B9XWphE_QU5UZAT8jhKg_jYNGmZOfSL1gcl-3kZeuoI'

jwt에서 제공하는 .encode메소드는 3개의 arguments를 받는다.
1. 유저정보: 유저 아이디 등 검증시 넘길 정보.
2. 시크릿키: 복잡도를 가진 해시값의 시크릿키.
3. 암호화 알고리즘 종류: HS256이 디폴트 값.

$ jwt.decode(token,'secretkey',algorithm = 'HS256')
>>> {'user_id' : 1}

token을 넘겨주고, decode하면 유저정보를 돌려준다.
만약 장고를 이용하여 만든 소스코드를 github에 공유한다면, settings.py에 이 정보가 고스란히 올라가 노출될 위험이 있다. 따라서, secretkey는 따로 로컬 환경에 저장을 하는 것을 추천한다 (예: my_settings.py와 같은 파일을 만들어, 로컬 환경에 저장하고 .gitignore에 추가)

(SECRET_KEY 변경 및 분리: https://wayhome25.github.io/django/2017/07/11/django-settings-secret-key/)

token.decode('utf-8')
>>>'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxfQ.B9XWphE_QU5UZAT8jhKg_jYNGmZOfSL1gcl-3kZeuoI'

토큰은 프론트엔드에 전달하여, 유저에 맞추어서 알맞은 페이지를 렌더링 할 수 있게 하여야 한다. byte타입의 데이터는 넘겨줄 수 없기 때문에, 꼭 decode하여 문자열로 바꾸어서 전달해야 한다.

인가:

Authorization은 유저가 요청하는 request를 실행할 수 있는 권한이 있는 유저인가를 확인하는 절차 이다.
예를 들어, 해당 유저는 고객 정보를 볼 수 있는 있지만 수정 할 수는 없다.

access token을 통해 해당 유저 정보를 얻을 수 있음으로 해당 유저가 가지고 있는 권한(permission)도 확인 할 수 있다.

프론트엔드가 session or cookies에 jwt를 넣어서 보관했다가, 필요에 따라 http request header에 넣어서 전달한다.

Authorization 절차

  1. Authentication 절차를 통해 access token을 생성한다. access token에는 유저 정보를 확인할 수 있는 정보가 들어가 있어야 한다 (예를 들어 user id).
  2. 유저가 request를 보낼때 access token을 첨부해서 보낸다.
    서버에서는 유저가 보낸 access token을 복호화 한다.
    복호화된 데이터를 통해 user id를 얻는다.
    user id를 사용해서 database에서 해당 유저의 권한(permission)을 확인하다.
  3. 유저가 충분한 권한을 가지고 있으면 해당 요청을 처리한다.
    유저가 권한을 가지고 있지 않으면 Unauthorized Response(401) 혹은 다른 에러 코드를 보낸다.

(Content: wecode private stackoverflow)


참고링크 :
https://velog.io/@devmin/bcrypt-jwt-basic
https://d2.naver.com/helloworld/318732
https://yonghyunlee.gitlab.io/node/jwt/
https://tansfil.tistory.com/58

profile
🔰

0개의 댓글