호우동의 개발일지

Today :

article thumbnail

썸네일


DES의 개념

- 64bit 평문을 64bit 암호문으로 암호화하는
대표적인 비밀키 방식의 대칭 암호 알고리즘

- 암호화/복호화할 때 쓰는 비밀키가 동일함

 


DES 암호화 방식

평문을 64비트로 나눠 56비트의 키로
다시 64비트의 암호문을 만들어내는 알고리즘

DESFeistel Cipher 방식으로 암호화를 한다.

Feistel Cipher 방식이란
데이터를 좌 우로 나누어 교대로 비선형 변환을 적용하는 구조

장점으로는 누군가 키를 알아내도 원래의 함수를 알아내지 못해 보안성이 좋다.

 


구현

DES.py

from Crypto.Cipher import DES
from Crypto.Hash import SHA256 as SHA

class myDES():

    # DES 초기화
    def __init__(self, keytext, ivtext):
        hash = SHA.new()
        hash.update(keytext.encode('utf-8'))
        # keytext를 해시화했을 때 첫 8byte를 key로 함
        key = hash.digest()
        self.key = key[:8]

        hash.update(ivtext.encode('utf-8'))
        iv=hash.digest()
        # ivtext를 해시화햇을때 첫 8byte를 iv로 함
        self.iv=iv[:8]

    # ECB 모드로 암호화
    def encrypt_ECB(self, plaintext):

        # 항상 8byte 단위로 끊어서 암호화하기 때문에 평문이 8byte로 끊기지 않는다면 
        # padding값을 추가해 8byte로 만들어줌
        while(len(plaintext) % 8 != 0):
            plaintext += ' '
        des=DES.new(self.key,DES.MODE_ECB)
        encryptMsg = des.encrypt(plaintext.encode())
        return encryptMsg
    # ECB 모드로 암호화된 암호문을 복호화
    def decrypt_ECB(self,ciphertext):
        des = DES.new(self.key, DES.MODE_ECB)
        descryptMsg = des.decrypt(ciphertext)
        return descryptMsg

    # CBC 모드로 암호화
    def encrypt_CBC(self, plaintext):

        while(len(plaintext) % 8 != 0):
            plaintext += ' '

        # CBC 모드에서는 iv(초기화 벡터) 값이 필요
        des=DES.new(self.key,DES.MODE_CBC,self.iv)
        encryptMsg = des.encrypt(plaintext.encode())
        return encryptMsg

    # CBC 모드로 암호화된 암호문을 복호화
    def decrypt_CBC(self,ciphertext):

        des = DES.new(self.key, DES.MODE_CBC,self.iv)
        descryptMsg = des.decrypt(ciphertext)
        return descryptMsg

DES의 초기화와 암호화, 복호화 함수를 담아둔 클래스이다.

중요하게 봐야 할 점은 CBC모드에서는 iv(초기화벡터)가 필요하다는 점

8byte 단위로 암호화를 하기 때문에 평문이 8byte로 끝나지 않는다면
억지로 padding을 추가해 8byte로 맞춰준다는 것이다.

Send.py

import socketserver
from DES import myDES
from os.path import exists
HOST = ''
PORT = 8081
# 임의의 키와 IV를 만듦
DESKEY = 'eightkey'
DESIV = '1234'

# 들어오는 요청을 처리하는 핸들러 클래스를 재정의한 요청처리기
class MyTcpHandler(socketserver.BaseRequestHandler):
    def handle(self):
        data_transferred = 0
        print('[%s]연결됨' % self.client_address[0])
        # 1024 바이트 받기
        filename = self.request.recv(1024)
        filename = filename.decode()

        if not exists(filename):
            return

        print('파일 [%s] 전송 시작..' % filename)

        #키와 IV 값을 클라이언트에게 보냄
        self.request.send(DESKEY.encode('utf-8'))
        self.request.send(DESIV.encode('utf-8')) # ECB 모드면 삭제

        with open(filename,'r') as file:
            try:
                line = file.readline()
                des = myDES(DESKEY,DESIV)
                enc = des.encrypt_CBC(line)
                data_transferred += self.request.send(enc)
            except Exception as e:
                print(e)

        print('암호화된 텍스트 : %s\n',enc)
        print('\n')
        print('\n')

        print('전송 완료[%s],전송량[%d]' % (filename, data_transferred))

def runServer():
     print('파일 서버 시작')
     try:
        server = socketserver.TCPServer((HOST, PORT), MyTcpHandler)
        # Ctrl + C 하기 전까진 계속 서버 실행
        server.serve_forever()
     except KeyboardInterrupt:
         print('파일 서버 종료')            
runServer()

서버이자 파일을 보내주는 send.py이다.

임의의 키와 iv값을 만들어둔 뒤,
클라이언트로부터 요청을 받으면 해당 파일이름이 있는지 확인한다.

이후 있으면 키와 iv를 클라이언트에게 보내준다.

보내줄 때 위에 DES Class로 키와 iv를 이용하여 평문을 암호화하여 보내준다.

Receive.py

import socket
from DES import myDES

HOST = 'localhost'
PORT = 8081

# 서버로부터 파일을 받아오는 함수(파일 이름)
def getFileFromServer(filename):
    data_transferred = 0

    #IP_4 Version으로 TCP방식으로 설정
    with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as sock:
        sock.connect((HOST,PORT)) # 해당 포트와 호스트로 연결
        sock.sendall(filename.encode()) # 파일이름을 인코딩(바이트화)해서 서버로 보냄

        key = sock.recv(8) # Key는 8byte이므로 8byte만 받아옴
        # ECB모드라면 iv는 필요없으므로 해당 코드 삭제-----------------
        iv = sock.recv(8) # iv도 마찬가지
        data = sock.recv(1024) # 데이터는 1024byte씩 받아오면됨(해당 테스트에선 1줄이어서 한번만)

        if not data:
            print('파일[%s]: 서버에 존재하지 않거나 오류 발생'%filename)
            return

        # 복호화가 제대로 됐는지 확인하기 위해 파일로 저장
        with open(filename+'.des-CBC','wb') as f:
            try:
                # 해당 키와 iv로 복호화
                des = myDES(key.decode(),iv.decode()) # EBC 모드라면 iv쪽을 ''로 비워둠

                # 데이터를 다 읽을때까지 읽어들여 데이터를 복호화
                while data:
                    des = des.decrypt_CBC(data)
                    f.write(des)
                    data_transferred +=len(data)
                    data = sock.recv(1024)


            except Exception as e:
                print(e)

            print('파일 [%s] 전송 종료. 전송량 [%d]' %(filename, data_transferred))

getFileFromServer('example.txt')

클라이언트 파일이자 파일을 받는 receive 파일이다. 
파일을 요청하고 파일을 만드는 역할을 한다.

파일을 요청한 뒤 파일이 있으면 서버로부터 키와 iv를 받는다.

이후 서버로부터 암호문 데이터를 받아 키와 데이터를 이용하여 복호화한다.
그리고 이를 파일에 적어 파일화 한다.

example.txt
example.txt

해당 example.txt 파일을 클라이언트가 서버에게 요청했다고 가정해 보자.
이를 두 개의 터미널을 이용해서 실행하면 이런 결과가 나온다.

Send,py 실행 결과
Send.py 실행 결과

연결된 아이피와 전송된 파일, 그리고 암호화된 텍스트를 확인할 수 있다.
그리고 전송량 또한 알 수 있었다.

텍스트

그리고 복호화가 제대로 된 지 확인하기 위해
만들어둔 파일에 제대로 복호화가 된 것을 확인할 수 있다.