[sql] 암호화 방법, passlib.hash 라이브러리 pbkdf2_sha256 함수 사용법, 로그인
암호화
암호화는 보안을 강화하고 데이터를 안전하게 보호하기 위해 사용되는 기술이다.
- 개념: 암호화는 텍스트나 데이터를 읽기 어려운 형식으로 변환하는 과정이다. 이 변환된 데이터를 이해할 수 있는 사람이나 시스템이 없는 한, 데이터는 안전하게 유지된다. 암호화는 대부분의 경우 특정 알고리즘과 키를 사용하여 수행된다.
- 해싱: 해싱은 단방향 암호화 기술로, 원본 데이터를 고정된 길이의 고유한 값으로 변환한다. 해시 함수를 사용하여 데이터를 해싱한다. 이 때, 같은 입력에 대해서는 항상 동일한 해시 값이 생성되지만, 해시 값을 기반으로 원본 데이터를 복원할 수는 없습니다. 대표적인 해시 함수로는 SHA-256이 있다
SHA-256는 passlib.hash 모듈 에 있는 함수중 하나로 passlib.hash 모듈은 비밀번호 해시를 생성하고 검증하기 위한 다양한 해싱 알고리즘을 제공하는 Passlib 라이브러리의 일부이다. 이 모듈은 다양한 해시 알고리즘을 지원하여 보안 강화에 도움이 된다.
pbkdf2_sha256
함수는 비밀번호를 안전하게 저장하기 위해 사용되는 해시 함수 중 하나이다. 해시 함수는 입력값(여기서는 비밀번호)을 일정한 길이의 고정된 출력값으로 변환하는 알고리즘이다. 이를 통해 원본 입력값을 추론할 수 없게 된다. 그러나 단순한 해시 함수만을 사용하면 해시된 비밀번호를 알아내기 위해 무작위로 생성된 문자열로 대입해보는 공격(무차별 대입 공격 또는 브루트 포스 공격)을 통해 원본 비밀번호를 추론할 수 있다.
이런 공격을 막기 위해, 보안 강화를 위해 비밀번호를 해싱할 때 "솔팅(Salting)"과 "키 스트레칭(Key Stretching)" 기법이 사용된다. pbkdf2_sha256는 이러한 기법을 사용하여 비밀번호를 해싱하는 함수 중 하나이다.
- 솔팅(Salting): 동일한 비밀번호가 해시 함수에 입력될 때마다 항상 동일한 결과를 생성하는 것을 방지하기 위해, 각 비밀번호에 임의의 솔트(salt) 값을 추가한다. 이 솔트는 일반적으로 해시된 비밀번호와 함께 저장되어야 한다. pbkdf2_sha256는 솔팅을 자동으로 처리한다.
- 키 스트레칭(Key Stretching): 비밀번호를 해싱할 때, 단순히 한 번만 해싱하는 것이 아니라 여러 번 반복해서 해싱한다. 이렇게 함으로써 무작위로 생성된 문자열을 대입하는 브루트 포스 공격에 대해 더욱 견고한 보호를 제공힌다. pbkdf2_sha256는 이러한 키 스트레칭을 제공한다.
따라서 pbkdf2_sha256 함수를 사용하면 솔팅과 키 스트레칭을 통해 보다 안전한 방법으로 비밀번호를 해싱할 수 있다. 이는 사용자의 개인정보 보호를 강화하고, 해커의 공격으로부터 시스템을 보호하는 데 도움이 된다.
사용법
from passlib.hash import pbkdf2_sha256
# 원래 비밀번호
password = "my_password"
# pbkdf2_sha256 함수를 사용하여 비밀번호 해싱
hashed_password = pbkdf2_sha256.hash(password)
# 해싱된 비밀번호 출력
print(hashed_password)
postman을 사용하여 mysql 에 입력한 비밀번호를 해싱하는 방법
포스트맨에 회원가입 request를 추가하고 주소를 저장해준 후 딕셔너리 형태로 유저이름, 이메일, 패스워드를 지정해준다.
user 정보를 작성하는 파일에 아래 코드를 작성한다.
class UserRegisterResource(Resource):
def post(self):
# 클라이언트가 보낸 데이터를 받아준다.
data = request.get_json()
if data.get('email') is None or data.get('email').strip() == '' or \
data.get('username') is None or data.get('username').strip() == '' or \
data.get('password') is None or data.get('password').strip() == '':
return {"result": "fail"}, 400
try:
validate_email(data['email'])
except EmailNotValidError as e:
return {'result': 'fail', 'error': str(e)}, 400
if not 4 <= len(data['password']) <= 12:
return {"result": "fail"}, 400
# 비밀번호를 암호화한다.
password = hash_password(data['password'])
try:
connection = get_connection()
query = '''insert into user
(username, email, password)
values
(%s, %s, %s);'''
record = (data['username'], data['email'], password)
cursor = connection.cursor()
cursor.execute(query, record)
connection.commit()
# DB에 회원가입하여, user 테이블에 insert된 후,
# 이 user 테이블의 id 값을 가져와야 한다.
user_id = cursor.lastrowid
cursor.close()
connection.close()
except Error as e:
if cursor is not None:
cursor.close()
if connection is not None:
connection.close()
return {'result': 'fail'}, 500
사용자 회원가입을 처리하는 Flask 리소스이다. 여러 단계를 거쳐 사용자가 제공한 데이터를 검증하고 데이터베이스에 저장한다.
- 클라이언트가 보낸 데이터를 받아온다. request.get_json()을 사용하여 JSON 형식의 데이터를 가져온다.
- 받아온 데이터가 모두 있는지 확인한다. 이때, 이메일, 사용자명, 비밀번호가 필수 필드입니다. 누락된 경우에는 적절한 응답을 반환한다.
- 이메일 주소의 유효성을 확인한다. validate_email() 함수를 사용하여 이메일 형식이 올바른지 검증한다. 올바르지 않은 경우에는 에러를 반환한다.
- 비밀번호의 길이가 유효한지 검증한다. 비밀번호는 4자 이상 12자 이하로 제한된다.
- 비밀번호를 암호화하여 보안을 강화한다. hash_password() 함수를 사용하여 비밀번호를 해싱한다.
- 데이터베이스에 사용자 정보를 저장한다. 이때, 입력된 사용자 정보와 함께 암호화된 비밀번호가 저장된다. 데이터베이스 연결 후 INSERT 쿼리를 실행하여 사용자 정보를 저장된다. 만약 에러가 발생한 경우 적절한 응답을 반환한다.
- 회원가입이 성공한 경우에는 적절한 응답을 반환한다.
postman이랑 연결
postman과 연동하는 파일에 경로를 지정해준다.
from flask import Flask
from flask_restful import Api
from resources.recipe import RecipeListResource, RecipePublishResource, RecipeResource
from resources.user import UserRegisterResource
from flask_jwt_extended import JWTManager
from config import Config
app = Flask(__name__)
# 환경변수 셋팅
app.config.from_object(Config)
#JWT 매니저 초기화
jwt = JWTManager(app)
api = Api(app)
# 경로(path)와 리소스(api 코드)를 연결한다.
api.add_resource(UserRegisterResource , '/users/register')
if __name__=='__main__':
app.run()
포스트맨 send 를 누른 후 mysql에서 확인을 해보면
입력한 유저의 password 가 해시로 변환돼어 암호화 된걸 확인 할 수 있다.
해시로 변환된 암호 로그인 방법
postman에 로그인 api를 생성하고 이메일과 패스워드를 입력하는 Body를 생성해준다.
유저를 관리하는 파일에 아래 코드를 작성한다.
class UserLoginResource(Resource):
def post(self) :
# 1. 클라이언트로부터 데이터를 받는다.
data = request.get_json()
if 'email' not in data or 'password' not in data:
return {'result' : 'fail'}, 400
if data['email'].strip() == '' or data['password'].strip() == '':
return {'result' : 'fail'}, 400
# 2. DB로부터 이메일에 해당하는 유저 정보를 가져온다.
try :
connection = get_connection()
query = '''select *
from user
where email = %s ;'''
record = ( data['email'] , )
cursor = connection.cursor(dictionary=True)
cursor.execute(query, record)
result_list = cursor.fetchall()
print(result_list)
cursor.close()
connection.close()
except Error as e:
if cursor is not None:
cursor.close()
if connection is not None:
connection.close()
return {'result':'fail', 'error':str(e)},500
# 3. 회원인지 확인한다.
if result_list == [] :
return {'result' : 'fail'} , 401
# 4. 비밀번호를 체크한다.
# 유저가 입력한 비번 data['password']
# DB에 암호화된 비번 result_list[0]['password']
isCorrect = check_password(data['password'] , result_list[0]['password'])
if isCorrect == False :
return {'result' : 'fail'} , 401
check_password(data['password'], result_list[0]['password']) 부분은 사용자가 제공한 비밀번호와 데이터베이스에 저장된 암호화된 비밀번호를 비교하는 부분이다. 이 함수는 주어진 두 개의 비밀번호를 비교하여 일치 여부를 확인한다.
- data['password']: 클라이언트가 제공한 비밀번호이다. 이 비밀번호는 평문으로 저장되어 있다.
- result_list[0]['password']: 데이터베이스에 저장된 사용자의 비밀번호이다. 이 비밀번호는 해시 함수를 사용하여 암호화되어 있다.
check_password() 함수는 다음과 같은 과정을 거쳐 비밀번호를 검증한다:
- original_password에는 클라이언트가 제공한 비밀번호가 저장된다.
- hashed_password에는 데이터베이스에서 가져온 암호화된 비밀번호가 저장된다.
- original_password를 암호화하는 과정을 거쳐 얻은 해시값과 hashed_password를 비교하여 두 값이 일치하는지 확인한다.
이렇게 하면 사용자가 제공한 비밀번호와 데이터베이스에 저장된 비밀번호를 비교하여 로그인 인증을 수행할 수 있다.
완료 후 postman send 를 누르면 로그인이 성공적으로 된다.