파이썬 실습/암호화프로그램만들기

파이썬 GUI/윈도우 암호화 프로그램 만들기 - 4.복호화 처리

파기차차 2022. 11. 27. 07:23
728x90
반응형
SMALL
반응형
ㅁ 개요

 

O 프로그램 소개
 

 - 이번 프로그램은 이전글(2022.11.20 - [분류 전체보기] - 파이썬 GUI/윈도우 암호화 프로그램 만들기 - 3.암호화 처리 부분)에 이은 4번째 글로 암호화된 파일을 원래상태로 돌리기 위한 복호화 처리 방식에 대하여 설명합니다.

(본 블로그의 내용은 유튜브 동영상(파이썬 GUI/윈도우 암호화 프로그램 만들기-4.복호화 처리(making GUI/Windows Encryption program byPython 4.decryption))에서 더욱 자세히 보실 수 있습니다.)

 

 

 

O 완성된 프로그램 실행 화면

 

 - 최종 완성된 프로그램의 결과화면은 아래와 같습니다.

1. 암호화된 파일(aa.txt)을 확인결과 파일내용이 'nPfmYWqt0trgwbLc2uVcXw=='와 같이 암호화된 것을 확인 합니다.

 

 

 

 

 

 

2. 아래와 같이 프로그램 실행 후 (1) 복호화할 파일 선택, (2) 암호화 시 사용한 패스워드 입력, (3) 복호화 버튼 클릭 하면 파일('aa.txt')이 정상적으로 복호화 됩니다.

 

 

3. 실제 폴더에 가서 파일 확인 결과 이전 파일 내용('nPfmYWqt0trgwbLc2uVcXw==')과는 다르게 원래 내용('aa')으로 복호화된 것을 확인합니다.

 

 

 

 

 


 

O 시작전 준비 사항

 

 

 - 프로그램을 실행하기 위해서는 아래 모듈들이 필요합니다. 아래와 같이 임포트 해줍니다.

 

import base64

import hashlib
from Crypto.Cipher import AES # 대칭키를 사용하기 위한 모듈 임포트

 

 

***에러 발생시 조치 사항(에러가 없는 경우 안보셔도 됩니다.)

--------------------------------------------------------------------------------------

만일 아래와 같이 에러 발생 시

ModuleNotFoundError: No module named 'Crypto'

 

pip install pycrypto <-- 현재 이 모듈은 더 이상 안전하지 않아 설치 되지 않음
pip install pycryptodome <-- 이 모듈을 설치

 

 

위의 모듈 설치 후에도 계속 동일한 에러 발생 시

ModuleNotFoundError: No module named 'Crypto'


아래 부분을 에서 와 같이 수정
from Crypto.cipher import PKCS1_OAEP # 소문자를

 

이렇게 수정

from Crypto.Cipher import PKCS1_OAEP # 대문자로 수정

--------------------------------------------------------------------------------------

 

 

여기까지 이상없이 되셨다면 이제 우리는 다음 단계로 넘어갈 준비가 되었습니다.


 

ㅁ 세부 내용
 
O 완성된 소스

 

# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import *
from PyQt5 import uic
from PyQt5.QtGui import *
import base64
import hashlib
from Crypto.Cipher import AES # 대칭키를 사용하기 위한 모듈 임포트



form_class = uic.loadUiType("gui_encryption_program4.ui")[0]

class MyWindow(QMainWindow, form_class):
    def __init__(self):
        global pad, unpad
        super().__init__()
        BS = 16 # blocksize를 16바이트로 고정시켜야 함(AES의 특징)

        # AES에서는 블럭사이즈가 128bit 즉 16byte로 고정되어 있어야 하므로 문자열을 encrypt()함수 인자로 전달시
        # 입력 받은 데이터의 길이가 블럭사이즈의 배수가 아닐때 아래와 같이 패딩을 해주어야 한다.
        # 패딩: 데이터의 길이가 블럭사이즈의 배수가 아닐때 마지막 블록값을 추가해 블록사이즈의 배수로 맞추어 주는 행위
        pad = (lambda s: s+ (BS - len(s) % BS) * chr(BS - len(s) % BS).encode())
        unpad = (lambda s: s[:-ord(s[len(s)-1:])])

        self.setFixedSize(700,700)
        self.setWindowIcon(QIcon("pagichacha.png"))
        self.setupUi(self)

        self.toolButton_4.clicked.connect(self.select_toEncryptFile)
        self.pushButton_3.clicked.connect(self.trigger_encrypt)
        # self.pushButton_3.clicked.connect(self.test)

        self.toolButton_5.clicked.connect(self.select_toDecryptFile)
        self.pushButton_4.clicked.connect(self.trigger_decrypt)


    ##################################################################################
    # 1. 키(패스워드) 생성 + 해시 처리
    ##################################################################################
    def make_key(self):
        global key
        self.textEdit.clear()

        key_org = self.lineEdit_4.text()
        self.textEdit.append("1. 원본 키(key) : "+key_org)

        self.key = hashlib.sha256(key_org.encode()).digest()
        print("AES Key(Key 문장 암호화) : ", self.key)
        self.textEdit.append("2. Key에 해시 적용 후 결과 : "+str(self.key)+"\n")

        self.lineEdit_4.clear()

        return self.key

    ##################################################################################
    # 2.암호화 
    ##################################################################################
    def select_toEncryptFile(self):
        global toEncryptFile
        toEncryptFile = QFileDialog.getOpenFileName(self, "Open file", './')
        print(type(toEncryptFile))
        self.textEdit.append('{}'.format(toEncryptFile[0]))
        # return toEncryptFile

    def encrypt(self, message):
        message = message.encode()
        raw = pad(message)
        key = self.make_key()
        cipher = AES.new(key, AES.MODE_CBC, self.__iv().encode('utf8'))
        enc = cipher.encrypt(raw)
        return base64.b64encode(enc).decode('utf-8')

    def trigger_encrypt(self):
        global encrypt_data
        self.textEdit.clear()
        # print(toEncryptFile[0])
        f = open(toEncryptFile[0], "r", encoding="euckr")
        data = f.read()
        f.close()
        print("원문 내용", data)

        encrypt_data = self.encrypt(data)

        # f = open("aa_enc.txt", "w")
        f = open(toEncryptFile[0], "w")
        f.write(str(encrypt_data))
        f.close()

        print("-"*100, "\n")
        print("원문을 키로 암호화 결과: ", encrypt_data)
        print("-"*100, "\n")
        self.textEdit.append("-"*70)
        self.textEdit.append("3. 원문 내용 : "+data)
        self.textEdit.append("4. 원문을 키로 암호화 결과:"+ encrypt_data)
        self.textEdit.append("-"*70)

        QMessageBox.about(self, "암호화!!", "파일이 암호화 되었습니다.")

    def __iv(self):
        return chr(0) * 16

    ##################################################################################
    # 3.복호화 
    ##################################################################################
    def select_toDecryptFile(self):
        global toDecryptFile
        toDecryptFile = QFileDialog.getOpenFileName(self, "Open file", './')
        print(type(toDecryptFile))
        self.textEdit.append('{}'.format(toDecryptFile[0]))
        # return toDecryptFile

    def decrypt(self, enc):
        # key = self.make_key()
        key_org = self.lineEdit_5.text()
        # self.key = hashlib.sha256(key_org.encode()).digest()
        key = hashlib.sha256(key_org.encode()).digest()
        print(key)

        enc = base64.b64decode(enc)
        print(enc)
        cipher = AES.new(key, AES.MODE_CBC, self.__iv().encode('utf8'))
        dec = cipher.decrypt(enc)
        try:
            return unpad(dec).decode('utf-8')
        except:
            return "Error"
        

    def trigger_decrypt(self):
        global decrypt_data
        self.textEdit.clear()

        f = open(toDecryptFile[0], "r", encoding="euckr")
        
        encrypt_data = f.read()
        f.close()

        print("암호화 적용된 파일 속 데이터 내용", encrypt_data)

        decrypt_data = self.decrypt(encrypt_data)
        print("암호화된 문장 복호화 결과:", decrypt_data)
        print("-"*100, "\n")

        self.textEdit.append("-"*70)
        self.textEdit.append("3. 암호화 적용된 파일 속 데이터 내용 : "+encrypt_data)
        self.textEdit.append("4. 암호문을 키로 복호화한 결과:"+ decrypt_data)
        self.textEdit.append("-"*70)
        self.lineEdit_5.clear()


        if decrypt_data != "" and decrypt_data != "Error":
            QMessageBox.about(self, "복호화!!", "파일이 복호화 되었습니다.")
            
            f = open(toDecryptFile[0], "w")
            f.write(str(decrypt_data))
            f.close()
        else:
            QMessageBox.about(self, "복호화!!", "파일이 복호화 되지 않았습니다.\n(패스워드가 올바르지 않습니다.)")


app=QApplication(sys.argv)
window = MyWindow()
window.show()
print("Before event loop")
app.exec_()
print("After event loop")

 

 

 

 

O 소스 다운로드 및 실행
 
 - 소스파일 다운로드 후 cmd 또는 파워쉘 등에서 아래와 같이 실행하시기 바랍니다.
('gui_encryption_program4.ui' 파일은 'total3.py' 실행파일과 동일한 위치에 놓으시기 바랍니다.)
 
 
 > python total3.py

 

 


 
 
O 소스 분석

 

아래 21번까지는 이전글(2022.11.20 - [분류 전체보기] - 파이썬 GUI/윈도우 암호화 프로그램 만들기 - 3.암호화 처리 부분)과 동일하며, 22번부터 새롭게 추가 되었습니다.

 

1.from Crypto.Cipher import AES <-- 대칭키를 사용하기 위한 모듈 임포트
2.BS = 16 <-- blocksize를 16바이트로 고정시켜야 함(AES의 특징)
3.pad = (lambda s: s+ (BS - len(s) % BS) * chr(BS - len(s) % BS).encode()) <-- 패딩 처리
# AES에서는 블럭사이즈가 128bit 즉 16byte로 고정되어 있어야 하므로 문자열을 encrypt()함수 인자로 전달시
# 입력 받은 데이터의 길이가 블럭사이즈의 배수가 아닐때 아래와 같이 패딩을 해주어야 한다.
# 패딩: 데이터의 길이가 블럭사이즈의 배수가 아닐때 마지막 블록값을 추가해 블록사이즈의 배수로 맞추어 주는 행위
4.unpad = (lambda s: s[:-ord(s[len(s)-1:])]) <-- 패딩처리된 부분은 원래대로 처리
5.self.setupUi(self) <-- 위에서 만든 UI를 클래스에 반영
6.self.pushButton_3.clicked.connect(self.test) <-- 'pushButton_3'(암호화) 버튼이 클릭되면 test함수 호출
7.key = self.make_key() <--make_key()함수의 리턴값을 key변수에 저장
8.self.textEdit.clear() <-- 텍스트에디트('실행결과' 화면)를 클리어(빈 화면으로)
9.key_org = self.lineEdit_4.text() <-- lineEdit_4의 입력값(패스워드(키)입력 값)을 key_org변수에 할당
10.self.textEdit.append("1. 원본 키(key) : "+key_org) <-- 텍스트에디트('실행결과' 화면)에 입력한 패스워드를 보여줌
11.self.key = hashlib.sha256(key_org.encode()).digest() <-- 키가 쉽게 노출되는 것을 막기 위해 키를 어렵게 처리하는 과정으로 보통 해시를 적용
12.self.textEdit.append("2. Key에 해시 적용 후 결과 : "+str(self.key)+"\n") <-- 텍스트에디트('실행결과' 화면)에 입력한 패스워드의 해시처리된 값을 보여줌

13.global toEncryptFile <-- toEncryptFile변수를 다른 함수에서 사용할 수 있도록 global 처리
14.toEncryptFile = QFileDialog.getOpenFileName(self, "Open file", './') <-- 파일선택
15.self.textEdit.append('{}'.format(toEncryptFile[0])) <-- 실행결과 화면에 선택된 파일의 전체 경로를 보여줌
16.key = self.make_key() <-- 입력한 패스워드를 해시처리한 값을 리턴
17.cipher = AES.new(key, AES.MODE_CBC, self.__iv().encode('utf8')) <-- AES 알고리즘 적용
18.enc = cipher.encrypt(raw) <-- AES알고리즘으로 암호화 처리
19.f = open(toEncryptFile[0], "r", encoding="euckr") <-- 파일을 읽기모드로 연다.
20.encrypt_data = self.encrypt(data) <-- data(원문)를 암호화 처리 
21.
def __iv(self): <--패딩처리를 위한 함수
        return chr(0) * 16

22.decrypt_data = self.decrypt(encrypt_data) <-- 암호화된 데이터를 복호화 처리
23.if decrypt_data != "" and decrypt_data != "Error": <-- 복호화 처리 후 리턴 받은 데이터가 비어 있지 않거나, "Error"가 아니면
24.QMessageBox.about(self, "복호화!!", "파일이 복호화 되었습니다.") <-- 정상적인 메시지 출력
25.key_org = self.lineEdit_5.text() <-- 사용자가 입력한 복호화 패스워드를 읽어서 key_org변수에 할당
26.key = hashlib.sha256(key_org.encode()).digest() <-- 사용자가 입력한 복호화 패스워드를 해시처리(그래야 암호화 할때 사용한 패스워드와 동일하게 됨)
27.cipher = AES.new(key, AES.MODE_CBC, self.__iv().encode('utf8')) <-- 해시처리한 패스워드를 키로 하여 AES암호화 알고리즘 적용
28.dec = cipher.decrypt(enc) <--적용된 AES암호화 알고리즘으로 aa.txt 파일의 내용을 복호화
29.
try:
    return unpad(dec).decode('utf-8')
except: <-- 복호화 시 입력한 패스워드가 잘못된 경우
    return "Error" <-- 일부러 "Error"을 리턴함


 

O 주요 내용

 

 

 

1. 복호화처리를 위해 복호화할 파일 선택(self.toolButton_5.clicked.connect(self.select_toDecryptFile)) 및 복호화 버튼(self.pushButton_4.clicked.connect(self.trigger_decrypt))을 만듭니다.

 

 

 

 

 

 

2.복호화 버튼 클릭시 trigger_decrypt()함수가 호출되며, 이 함수는 암호화된 파일을 읽어와서(이미 def select_toDecryptFile(self): 함수에서 toDecryptFile를 global처리 해주었기에 가능) 복호화 처리(decrypt()) 결과(decrypt_data)를 보여줍니다.

 

결과처리가 제대로 되지 않은 경우를 대비하여, 결과가 비어있지 않고, 리턴값이 "Error"이 아닌 경우 정상적인 메시지를 출력하고, 그렇지 않은 경우 에러 메시지를 출력합니다. 

 

 

 

 3. 실제 복호화를 처리하는 decrypt()함수에서는 사용자가 입력한 복호화 패스워드를 해시하여 이전 암호화했던 로직과 동일하게 키를 맞춘 후 이 키(해시된 패스워드)로 암호화된 파일(정확히는 암호화된 파일의 내용)을 복호화 합니다.

 

여기서 사용자가 패스워드가 틀린 경우가 발생할 수 있으므로, 패스워드가 틀린 경우는 리턴 값을 일부러 'Error'를 던져 주도록하였고, 'Error'를 리턴 받은 trigger_decrypt()함수에서는 메시지박스로 에러 메시지('패스워드가 올바르지 않습니다.')를 출력하게 하였습니다.

 

 

 

 


 

ㅁ 정리
 
O 우리가 배운 내용
 
 

 - 오늘 우리가 배운 내용을 간단히 정리해 보면 아래와 같습니다.
 
 > 1. 복호화할 파일 선택 : 다른 함수에서도 사용가능하도록 global 처리
 > 2. 복호화 패스워드 입력 : 암호화 과정과 동일하게 입력한 패스워드를 해시 처리
 > 3. 입력한 패스워드가 올바른지 체크 후 복호화 처리 : 처리결과가 비어있거나, 'Error'이면 에러 메시지 출력
 
 

 

오늘은 여기까지이며, 위의 내용이 유익하셨다면, 좋아요와 구독 부탁드립니다.

 

 

감사합니다.

728x90
반응형
LIST