본문 바로가기
API 개발/API (Back-end)

[sql] 암호화 방법, passlib.hash 라이브러리 pbkdf2_sha256 함수 사용법, 로그인

by 코끼리똥11 2024. 5. 22.

암호화

암호화는 보안을 강화하고 데이터를 안전하게 보호하기 위해 사용되는 기술이다.

  1. 개념: 암호화는 텍스트나 데이터를 읽기 어려운 형식으로 변환하는 과정이다. 이 변환된 데이터를 이해할 수 있는 사람이나 시스템이 없는 한, 데이터는 안전하게 유지된다. 암호화는 대부분의 경우 특정 알고리즘과 키를 사용하여 수행된다.
  2. 해싱: 해싱은 단방향 암호화 기술로, 원본 데이터를 고정된 길이의 고유한 값으로 변환한다. 해시 함수를 사용하여 데이터를 해싱한다. 이 때, 같은 입력에 대해서는 항상 동일한 해시 값이 생성되지만, 해시 값을 기반으로 원본 데이터를 복원할 수는 없습니다. 대표적인 해시 함수로는 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 리소스이다. 여러 단계를 거쳐 사용자가 제공한 데이터를 검증하고 데이터베이스에 저장한다.

  1. 클라이언트가 보낸 데이터를 받아온다. request.get_json()을 사용하여 JSON 형식의 데이터를 가져온다.
  2. 받아온 데이터가 모두 있는지 확인한다. 이때, 이메일, 사용자명, 비밀번호가 필수 필드입니다. 누락된 경우에는 적절한 응답을 반환한다.
  3. 이메일 주소의 유효성을 확인한다. validate_email() 함수를 사용하여 이메일 형식이 올바른지 검증한다. 올바르지 않은 경우에는 에러를 반환한다.
  4. 비밀번호의 길이가 유효한지 검증한다. 비밀번호는 4자 이상 12자 이하로 제한된다.
  5. 비밀번호를 암호화하여 보안을 강화한다. hash_password() 함수를 사용하여 비밀번호를 해싱한다.
  6. 데이터베이스에 사용자 정보를 저장한다. 이때, 입력된 사용자 정보와 함께 암호화된 비밀번호가 저장된다. 데이터베이스 연결 후 INSERT 쿼리를 실행하여 사용자 정보를 저장된다. 만약 에러가 발생한 경우 적절한 응답을 반환한다.
  7. 회원가입이 성공한 경우에는 적절한 응답을 반환한다.

 

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() 함수는 다음과 같은 과정을 거쳐 비밀번호를 검증한다:

  1. original_password에는 클라이언트가 제공한 비밀번호가 저장된다.
  2. hashed_password에는 데이터베이스에서 가져온 암호화된 비밀번호가 저장된다.
  3. original_password를 암호화하는 과정을 거쳐 얻은 해시값과 hashed_password를 비교하여 두 값이 일치하는지 확인한다.

이렇게 하면 사용자가 제공한 비밀번호와 데이터베이스에 저장된 비밀번호를 비교하여 로그인 인증을 수행할 수 있다.

 

완료 후 postman send 를 누르면 로그인이 성공적으로 된다.