* 본 포스트는 개인연구/학습 기록 용도로 작성되고 있습니다.
[Python] 파이썬 모듈을 활용한 암호화/복호화
By MK on August 4, 2019
업무를 하다보면 민감한 문서를 다뤄야할 때가 있다.
이럴때 암호화/복호화 기술을 활용하면 용이하다.
파이썬 모듈을 활용해 쉽게 적용할 수 있다.
우선 암호화 기술에 대해 알아보자.
암호블록체인(Cipher-block chainingm CBC) 방식은 1976년 IBM에 의해 개발되었다. 각 블록은 암호화되기 전에 이전 블록의 암호화 결과와 XOR되며, 첫 블록의 경우에는 초기화 벡터가 사용된다. 초기화 벡터가 같은 경우 출력결과가 항상 같기 때문에 매 암호화마다 다른 초기화 벡터를 사용해야 한다.
코딩시 IV(Initial Vector) 변수가 초기화벡터이다. 즉, IV을 초기 셋팅해 암호화하고 복호화할때도 동일한 IV값이 있어야 한다. IV가 제 2의 키역할을 하게 된다.
CBC방식은 현재 가장 널리 사용되고 있는 방식으로 암호화 입력값이 이전 결과에 의존하기 때문에 암호화시에는 병렬화가 불가능하지만 복호화시에는 병렬화가 가능하다.
코드에서 사용한 모듈은 AES CBC 모드로 암호화/복호화 하는 클래스이다. 그럼 AES란 또 무엇일까?
표준대칭키 알고리즘 중 하나이다. 암호문을 생성(암호화)할 때 사용하는 키와 암호문으로부터 평문을 복원(복호화)할 때 사용하는 키가 동일한 암호 시스템이다.
전체적인 암호화과정은 암호블록체인 알고리즘을 활용하고 체인별 암호화/복호화시에는 대칭키알고리즘 을 활용하는 것이다.
대칭키의 대표적인 알고리즘은 DES(Data Encryption Standard)였다. 하지만 DES는 54bit의 키를 사용하여 현재 컴퓨팅 기술에 보안에 취약하였다. 이에 미국의 표준기술연구소(NIST)는 1998년 DES를 대체할 최고 보안 규격의 알고리즘을 공모하였고 2001년 Rijndael(Submitted by Joan Daeman and Vincent Rijman)이 선정되어 AES알고리즘이 탄생하게 된다.
[ AES의 특징]
- 입력 평문의 길이는 128비트로 고정
- AES의 기본 연산은 Byte 단위로 수행
- 사용하는 암호화 키의 길이는 128, 192, 256비트 중 하나를 선택할 수 있음
참고) Rijndel 알고리즘은 키의 길이와 입력 평문의 길이를 128, 192, 256 비트 중 선택 가능
- 알려진 모든 공격법에 대해 안전하도록 설계됨
- 모든 플랫폼에서 속도와 code compactness 면에서 효율적
Design simplicity
Suited for Smart cards
대칭키 암호 시스템은 알고리즘이 상대적으로 단순한 장점이 있지만 키 관리에 어려움이 많다.
시스템에 가입한 사용자들 사이에 매 두 사용자 마다 하나의 서로 다른 키를 공유해야 하기 때문에 n명이 가입한 시스템에는 ~n~ C ~2~ = n(n-1)/2 개의 키가 필요하다. 또 각 사용자는 n-1개의 키를 관리해야 하는 부담이 있다. 이는 매우 큰 단점으로 키 관리가 상대적으로 용이한 공개키 암호 시스템의 출현 의 계기가 되었다.
대표적인 공개키 암호 시스템이 RSA 이다. 암호화뿐만 아니라 전자서명이 가능한 최초의 알고리즘 으로 알려져 있다. RSA가 갖는 전자서명 기능은 인증을 요구하는 전자 상거래 등에 RSA의 광범위한 활용을 가능하게 하였다. RSA 암호체계의 안정성은 큰 숫자를 소인수 분해하는 것이 어렵다는 것에 기반을 두고 있다. 그러므로 큰 수의 소인수 분해를 획기적으로 빠르게 할 수 있는 알고리즘이 발견된다면 이 암호 체계는 가치가 떨어질 것이다. 1993년 피터 쇼어는 쇼어 알고리즘을 발표하여, 양자 컴퓨터를 이용하여 임의의 정수를 다항 시간 안에 소인수 분해하는 방법을 발표하였다. 따라서 양자 컴퓨터가 본격적으로 실용화되면 RSA 알고리즘은 무용지물이 될 수도 있으나 양자 컴퓨터가 이 정도 수준으로 실용화되려면 아직 여러 해가 더 필요할 것으로 보인다.
RSA는 두 개의 키를 사용 한다. 일반적으로 공개키(public key)는 모두에게 알려져 있으며 메시지를 암호화(encrypt)하는데 쓰이며, 암호화된 메시지는 개인키(private key)를 가진 자만이 복호화(decrypt)하여 열어볼 수 있다. 하지만 RSA 공개키 알고리즘은 이러한 제약조건이 없다. 즉 개인키로 암호화하여 공개키로 복호화할 수 있다.
공개키 알고리즘은 누구나 어떤 메시지를 암호화할 수 있지만, 그것을 해독하여 열람할 수 있는 사람은 개인키를 지닌 단 한 사람만이 존재한다는 점에서 대칭키 알고리즘과 차이를 가진다.
RSA는 소인수 분해의 난해함에 기반하여, 공개키만을 가지고는 개인키를 쉽게 짐작할 수 없도록 디자인되어 있다.
메시지와 공개키 모두를 알 수 있다면 변조된 메시지를 보낼 수 있기 때문에, 실제로는 수신측의 공개키만을 사용하여 암호화하는 경우는 드물다. 송수신 양측의 키쌍을 사용하는 방법으로는 A의 개인키로 암호화 -> B의 공개키로 암호화 한 메시지를 전달 하고 복호화 과정은 B의 개인키로 복호화 -> A의 공개키로 복호화 로 구성된 방식이 일반적이다.
즉, 사용목적에 따라 대칭키 방식 공개키 방식을 사용할 수 있다. 다수의 참여자가 활동하는 환경에서는 공개키 방식을 사용하는 것이 실용적이지만, 개인적인 업무 혹은 소수집단에서의 활용측면에서는 대칭키 방식도 유용하다.
이하 코드는 AES CBC 방식의 암호화/복호화 예제 이다.
1. 모듈 로드
콘솔에서 아래 명령을 통해 라이브러리를 설치한다.
pip install pycryptodomex
임폴트에 문제가 있을 경우 상단에 주석처리된 3개 코드를 함께 실행해보자.
#import crypto
#import sys
#sys.modules['Crypto'] = crypto
import base64
from Crypto import Random
from Crypto.Cipher import AES
2. 블럭사이즈에 대한 패딩로직
스트링 문자열을 encrypt에 인자로 전달시 입력받은 데이터의 길이가 BLOCK_SIZE의 배수가 아닐때 패딩개념이 필요하다.
AES에서는 BLOCK_SIZE가 128bit 즉 16바이트로 고정되는데, 아래 코드를 통해 자동 패딩처리한다.
한글에 대한 처리를 위해 pad
시에 len(s.encode('utf-8'))
처리가 반드시 필요하다.
영문과 기호는 문자당 1바이트지만 한글은 문자당 2바이트
이기 때문이다. len()함수를 활용해 길이를 통해 바이트 계산을 하는 방식이므로 ‘utf-8’ 변환하지 않을 경우 오류가 발생하게 된다.
BS = 16
pad = lambda s: s + (BS - len(s.encode('utf-8')) % BS) * chr(BS - len(s.encode('utf-8')) % BS)
unpad = lambda s : s[:-ord(s[len(s)-1:])]
3. 암호화알고리즘 클래스 생성
class AESCipher:
def __init__( self, key ):
self.key = key
def encrypt( self, raw ):
raw = pad(raw)
iv = Random.new().read( AES.block_size )
cipher = AES.new( self.key, AES.MODE_CBC, iv )
return base64.b64encode( iv + cipher.encrypt( raw.encode('utf-8') ) )
def decrypt( self, enc ):
enc = base64.b64decode(enc)
iv = enc[:16]
cipher = AES.new(self.key, AES.MODE_CBC, iv )
return unpad(cipher.decrypt( enc[16:] ))
encrypt 함수의 내부를 보면 입력받은 raw데이터를 raw.encode(‘utf-8’) 16바이트 바이너리로 변환 후, 다시 base64.b64encode() 인코딩 한다. 즉 문자열을 기준으로 해서 byte화시키는 문자인코딩과는 다르게 bytes를 기준으로 문자열화 시키는 인코딩 방식 이 필요하다.
4. KEY설정
AES256을 구현하기 위해 32바이트의 키를 생성한다. ( 2 ^32^ = 256) KEY값은 아래와 같이 리스트 베열로 바로 지정하거나, time함수와 hashlib를 통해 임의로 생성해볼 수 있다.
1) 리스트에 셋팅하는 방식
key = [0x10, 0x01, 0x15, 0x1B, 0xA1, 0x11, 0x57, 0x72, 0x6C, 0x21, 0x56, 0x57, 0x62, 0x16, 0x05, 0x3D,
0xFF, 0xFE, 0x11, 0x1B, 0x21, 0x31, 0x57, 0x72, 0x6B, 0x21, 0xA6, 0xA7, 0x6E, 0xE6, 0xE5, 0x3F]
2) 임의로 생성하는 방식
import base64
import hashlib
def make_pass():
timekey = int(time.time())
return str(timekey)
password = make_pass().encode('utf-8')
5. 암호화/복호화
데이터 스트링 셋팅시 '
표현에 유의하자 자주 사용되는 '
를 문자열 내에서 표현하기 위해서는 문자열을 감싸는 표현은 쌍따옴표"
를 사용한다.
역으로 문자열 내에서 "
를 표현하기 위해서는 문자열을 '
를 사용해야 한다.
data = "Iran has seized a foreign oil tanker in the Persian Gulf that was smuggling fuel to some Arab states, according to a state television report on Sunday. The report said that the tanker had been detained and the ship's foreign crew held by the country's elite Islamic Revolutionary Guards Corps."
데이터암호화
encrypted_data = AESCipher(bytes(key)).encrypt(data)
encrypted_data
b'643crQRSMHsohr+JNXabtD9yNhdFs9+4UmWrv8amNR1JGZHbqsYSh/+eDSFRmkuTTuEXTL5a6VzblG2f1MGcYWSW89OGmiwBs/1X/7QcX2V+SDyvwDUJ6CZBncSpQ30vSZftWSXlDtnuuhCF8M4CvuH1SgsHyhce0JEpVeisNr9fwV0PYmzYp0QETZ4V2yzwaBToOVrcgJOCzigWGT4JoMSbtX0Z5T5oFnwCzIVULYAvRtsxW8MSh4e5lznEBGnMl1tpVSMpmmdETLPyXDoAp/y5N+6jk0YZ5RcetSA83iO407iR4DpNLLVbd+T+3i2Icw7ZqDZeilyxbBFePGvqJAg6qgNKbLobpTB+/BugzDjiSVUX6JWTT1XVynxagktXWPPRmIWfbBjd/QI3wZSOVXHp6WtlTloWY6Zj+1wuaGk='
데이터복호화
decrypted_data = AESCipher(bytes(key)).decrypt(encrypted_data)
decrypted_data.decode('utf-8')
b"Iran has seized a foreign oil tanker in the Persian Gulf that was smuggling fuel to some Arab states, according to a state television report on Sunday. The report said that the tanker had been detained and the ship's foreign crew held by the country's elite Islamic Revolutionary Guards Corps."
Reference
- https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
- http://blog.naver.com/PostView.nhn?blogId=rack_young&logNo=220844664848
- https://codeday.me/ko/qa/20190315/20318.html
- http://redutan.github.io/2015/11/20/about-crypto