We are Architect

37. 파이썬을 이용한 비밀번호 적합도 프로그램 만들기 본문

Programing/Python

37. 파이썬을 이용한 비밀번호 적합도 프로그램 만들기

the best infra 2024. 12. 27. 18:50

 

* 취지 

  • 비밀번호를 만드는데 먼저 사전에 이게 노출된 적이 있는지 확인하고 싶은 찝찝함이 있었다. 그래서 미리 확인하고 안전한 지를 파악한 후에 해당 비밀번호를 사용할 수 없을까 해서 공부하면서 만들어 봤다.

 

* 코드 설계

  1. 사용하고 싶은 비밀번호 입력받기
  2. 기준에 맞게 비밀번호 적합도 평가하기
  3. 해당 비밀번호를 비밀번호 노출확인 사이트에서 확인하기
  4. 결과 출력 

 

 

* 사용 도구

  • 기본 라이브러리 
    • re : 주어진 텍스트에서 조건에 맞는 문자열을 필터링해서 가져올수 있게 하는 모듈
    • hashlib : 암호화를 도와주는 모듈.
  • request 패키지 : 
    • api 요청을 할 수 있게 해주는 모듈.
  • 비밀번호 노출이력 검증 사이트
 

Have I Been Pwned: Check if your email has been compromised in a data breach

Have I Been Pwned allows you to search across multiple data breaches to see if your email address or phone number has been compromised.

haveibeenpwned.com

 

 

 

* 사용된 패키지 및 모듈

# 조건을 설정해 텍스트에서 원하는 부분만 처리하는 모듈.(정규 표현식 모듈)
import re
# SHA-1 해쉬 알고리즘을 사용할 수 있게 하는 모듈.
import hashlib
# API를 가져올 수 있게 하는 모듈
import requests

 

 

* 사용된 함수들 

  • 비밀번호 적합도 평가 함수
    • 비밀번호 입력 값을 받음 
    • 다음 기준에 맞게 평가 값을 매김
      • 길이(12자리 or 8자리 or 그이상)
      • 대문자 포함 여부
      • 소문자 포함 여부
      • 숫자 포함 여부
      • 숫자 포함 여부
    • 평가 값 합산해서 결과 반환
      def check_password_strength(password):
          strength = 0
      
          # 길이 체크
          # 12자리 이상이면 +2점
          if len(password) >= 12:
              strength += 2
          # 8자리 이상이면 +1점
          elif len(password) >= 8:
              strength += 1
      
          # 대문자 포함 여부
          if re.search(r"[A-Z]", password):
              strength += 1
      
          # 소문자 포함 여부
          if re.search(r"[a-z]", password):
              strength += 1
      
          # 숫자 포함 여부
          if re.search(r"[0-9]", password):
              strength += 1
      
          # 특수문자 포함 여부
          if re.search(r"[!@#$%^&*(),.?\":{}|<>]", password):
              strength += 1
      
          # 적합 결과 반환
          # 3점 이하 = 부적합 / 3점 = 적합하나 재설정 요망 / 3점 이상 = 적합
          if strength < 3:
              return "부적합."
          elif strength == 3:
              return "적합하나 재설정 요망"
          else:
              return "적합"
  • 비밀번호 노출확인 사이트에서 평가하는 함수
    • 해당 사이트로 보내기 위한 데이터 가공
      • UTF-8로 변환 > sha-1 알고리즘을 적용하기 위해서 바이트 데이터로 변환해야 함. 
      • sha-1으로 암호화(사실 sha-1은 옛날에 사용되던 알고리즘이지만 해당 사이트에 요청하기 위해 사용) > 해당 사이트의 API는 해당 해시 값을 요구
      • hexdigest() 사용해 16진수 문자열로 변환 > 해당 사이트의 요구사항 때문에 변환
      • upper()를 사용해 대문자로 변환 > 해당 사이트의 API는 대문자 형식의 해시 값을 요구
    • 가공된 데이터를 슬라이싱 해서 필요 부분만 사이트로 요청 보냄
    • 사이트로부터 가져온 응답값을 다시 데이터 가공
    • 가공된 값에 대한 정보값을 반환
      def check_pwned_password(password):
          # sha1 알고리즘으로 암호화 + UTF-8로 변환 + 16진수로 변환 + 대문자로 변환
          sha1_password = hashlib.sha1(password.encode("utf-8")).hexdigest().upper()
      
          # 해쉬값 앞에 5글자
          prefix = sha1_password[:5]
      
          # 해쉬값 5글자 뒤에 글자들
          suffix = sha1_password[5:]
      
          # pwnedpasswords에서 검증(API 요청)
          url = f"https://api.pwnedpasswords.com/range/{prefix}"
          respones = requests.get(url)
      
          if respones.status_code != 200:
              raise RuntimeError(f"API로 부터 정상적 코드 200번을 받아오지 못했습니다. 인터넷을 확인해주세요.")
          
          # 반환된 해시 데이터 확인
          # 가져온 데이터를 splitlines, split으로 가공.
          hashes = (line.split(":") for line in respones.text.splitlines())
          for hash_suffix, count in hashes:
              # 만약 리턴된 5글자 뒤에 해쉬값이 suffix과 같다면 count 리턴.
              if hash_suffix == suffix:
                  return int(count)
          return 0
  • 모든 함수를 작동하게 만드는 main 함수
def main():
  while 1 :
    password = input("검증 받을 패스워드를 입력하세요. : ")

    # 강도 평가
    PWStrength = check_password_strength(password)
    print(f"나의 비밀번호 적합도는? : {PWStrength}")

    # 유출 여부 확인
    try:
        pwned_count = check_pwned_password(password)
        if pwned_count > 0:
            print(f"당신의 비밀번호는 유출된적이 있습니다! 노출 횟수는 {pwned_count}번 이에요.")
        else:
            print("당신의 비밀번호는 노출된적이 없습니다. 사용해도 좋아요!\n")
    
    except Exception as e:
        print(f"오류 발생 : {e}")

 

 

* 전체 코드

# 조건을 설정해 텍스트에서 원하는 부분만 처리하는 모듈.(정규 표현식 모듈)
import re
# SHA-1 해쉬 알고리즘을 사용할 수 있게 하는 모듈.
import hashlib
# API를 가져올 수 있게 하는 모듈
import requests

def check_password_strength(password):
    strength = 0

    # 길이 체크
    # 12자리 이상이면 +2점
    if len(password) >= 12:
        strength += 2
    # 8자리 이상이면 +1점
    elif len(password) >= 8:
        strength += 1

    # 대문자 포함 여부
    if re.search(r"[A-Z]", password):
        strength += 1

    # 소문자 포함 여부
    if re.search(r"[a-z]", password):
        strength += 1

    # 숫자 포함 여부
    if re.search(r"[0-9]", password):
        strength += 1

    # 특수문자 포함 여부
    if re.search(r"[!@#$%^&*(),.?\":{}|<>]", password):
        strength += 1

    # 강도 결과 반환
    # 3점 이하 = 약함 / 3점 = 보통 3점 / 이상 = 강함
    if strength < 3:
        return "부적합."
    elif strength == 3:
        return "적합하나 재설정 요망"
    else:
        return "적합"


def check_pwned_password(password):
    # sha1 알고리즘으로 암호화 + UTF-8로 변환 + 16진수로 변환 + 대문자로 변환
    sha1_password = hashlib.sha1(password.encode("utf-8")).hexdigest().upper()

    # 해쉬값 앞에 5글자
    prefix = sha1_password[:5]

    # 해쉬값 5글자 뒤에 글자들
    suffix = sha1_password[5:]

    # pwnedpasswords에서 검증(API 요청)
    url = f"https://api.pwnedpasswords.com/range/{prefix}"
    respones = requests.get(url)

    if respones.status_code != 200:
        raise RuntimeError(f"API로 부터 정상적 코드 200번을 받아오지 못했습니다. 인터넷을 확인해주세요.")
    
    # 반환된 해시 데이터 확인
    # 가져온 데이터를 splitlines, split으로 가공.
    hashes = (line.split(":") for line in respones.text.splitlines())
    for hash_suffix, count in hashes:
        # 만약 리턴된 5글자 뒤에 해쉬값이 suffix과 같다면 count 리턴.
        if hash_suffix == suffix:
            return int(count)
    return 0 

def main():
  while 1 :
    password = input("검증 받을 패스워드를 입력하세요. : ")

    # 강도 평가
    PWStrength = check_password_strength(password)
    print(f"나의 비밀번호 적합도는? : {PWStrength}")

    # 유출 여부 확인
    try:
        pwned_count = check_pwned_password(password)
        if pwned_count > 0:
            print(f"당신의 비밀번호는 유출된적이 있습니다! 노출 횟수는 {pwned_count}번 이에요.")
        else:
            print("당신의 비밀번호는 노출된적이 없습니다. 사용해도 좋아요!\n")
    
    except Exception as e:
        print(f"오류 발생 : {e}")

# 해당 코드가 직접 실행되어야 함수가 실행됨.
if __name__ == "__main__" :
    main()

 

 

* 실행 결과

  • 해당 프로그램을 실행파일로 만들어서 작동시켜보았다. 
  • 혹시 실행 프로그램으로 만들고 싶으면 pyinstaller를 설치하면 된다. 
    • 설치 후 cmd 창에서 해당 파일 디렉터리로 들어가서 다음과 같이 명령
pyinstaller --onefile 파이썬_파일.py

 

 

GPT로 공부하니까 아이디어 고갈도 안 나고 계속 공부할 수 있어서 너무 행복하다. :)