파이썬 AES 구현 - paisseon AES guhyeon

python으로 password 만들기

적어도 하나의 소문자, 적어도 하나의 대문자 및 적어도 3개의 숫자가 있는 10자의 영숫자 암호를 생성한다.

import string import secrets alphabet = string.ascii_letters + string.digits while True: password = ''.join(secrets.choice(alphabet) for i in range(20)) if (any(c.islower() for c in password) and any(c.isupper() for c in password) and sum(c.isdigit() for c in password) >= 3): break

python tokenbytes, tokenhex

secrets.token_bytes([nbytes=None])

: nbytes 바이트를 포함하는 임의의 바이트열을 반환합니다. nbytes가 None이거나 제공되지 않으면, 적절한 기본값이 사용됩니다.

secrets.token_hex([nbytes=None])

: 무작위 16진수 텍스트 문자열을 돌려줍니다. 이 문자열에는 nbytes 무작위 바이트가 있으며, 각 바이트는 두 자리 16진수로 변환됩니다. nbytes가 None이거나 제공되지 않으면, 적절한 기본값이 사용됩니다.

  • 출처: //docs.python.org/ko/3.8/library/secrets.html
  • secrets 라이브러리는 python 3.6 버젼 이상부터 지원한다.

bit, byte, 16진수 관계 정리

  • bit 는 2 진수 한 자리를 말하며 표현가능한 가짓수는 2 개이다. 숫자로 치면 0 ~ 1.
  • byte 는 8 bit 를 말하며 2 진수 8 자리에 해당하고 표현가능한 가짓수는 2^8 개인 256 개이며 숫자로 치면 0 ~ 255.

bit 는 전기의 on / off 이며 이것을 2진수의 1 / 0 에 대응시킨 것이다. 2진수와 16진수의 변환이 헷갈릴 수 있다. 수학에서 하듯 2진수를 10진수로 바꾸고 이것을 다시 16진수로 바꾸는 식으로 해도 되지만 2진수 네 자리가 16진수 한 자리와 같다는 것만 기억하면 훨씬 쉽게 변환이 가능하다.

설명하자면, 16진수 한 자리는 16가지의 수의 표현이 가능하고, 이는 2^4(4 * 4 = 2^2 * 2^2 = 2^4) 가지이고, 이것은 2진수 4자리가 표현가능한 수의 가짓수와 같다. 즉, “2진수 4자리 = 16진수 1 자리” 인 셈이다. 참고로 16진수 한 자리에 해당하는 4 bit 를 니블(nibble)이라고 말한다.

방금 말한 4 bit 단위로 읽으면 2진수와 16진수의 변환을 쉽게 할 수 있다. 2 byte 에 해당하는 16 bit 의 수를 예로 들어보자.

0110 1001 1101 0010

위와 같은 2진수를 4 bit 씩 끊어서 변환하면, 6 9 13 2 가 되고, 컴퓨터의 16진수로 표현하면 0x69D2 가 되는 것이다.

기억하기에는 1 byte = 8 bit = 16진수 2자리 정도만 기억하면 단위가 헷갈리는 경우는 없을 것 같다.

출처: //m.blog.naver.com/PostView.nhn?blogId=herbbread&logNo=220692723391&proxyReferer=https%3A%2F%2Fwww.google.com%2F

암호화(encryption)

암호화(暗號化) 또는 엔크립션(encryption)은 특별한 지식을 소유한 사람들을 제외하고는 누구든지 읽어볼 수 없도록 알고리즘을 이용하여 정보(평문을 가리킴)를 전달하는 과정이다. 이러한 과정을 통해 암호화된 정보(암호문)를 낳는다. 이에 역행하는 과정을 해독 또는 디크립션(decryption)이라고 하며 이로써 암호화된 정보를 다시 읽을 수 있다.

출처 : //ko.wikipedia.org/wiki/%EC%95%94%ED%98%B8%ED%99%94

대칭키 알고리즘

출처 : //lesstif.gitbooks.io/web-service-hardening/content/encryption.html

대칭키(Symmetric key) 방식은 암호문을 생성(암호화)할 때 사용하는 키와 암호문으로부터 평문을 복원(복호화)할 때 사용하는 키가 동일한 암호 시스템으로 일반적으로 알고 있는 암호 시스템입니다.

하나의 키로 암호화와 복호화를 수행하므로 대칭키(Symmetric key) 또는 비밀키(Secret Key) 방식의 암호화라고 합니다. 대칭키 방식의 암호화는 데이타를 블록(Block) 단위로 처리하는 Block cipher 와 연속된 스트림으로 처리하는 Stream cipher 가 있으며 일반적으로 대칭키 암호화라고 하면 블록 방식의 암호화를 의미합니다.

블록 방식의 주요 알고리즘으로는 예전에 유닉스의 패스워드 파일을 암호화하는데 사용했지만 오래전에 뚫려서 이제는 절대 사용하면 안 되는 DES(Data Encryption Standard)와 현재 미국의 표준 암호 알고리즘인 AES가 있으며 국내에서 개발한 SEED와 ARIA , 그리고 유럽에서 개발한 IDEA 알고리즘 등이 있습니다.

블록 암호화는 정해진 길이, 또는 가변 길이의 암/복호화 키를 사용하며 알고리즘마다 다릅니다. 예로 AES 는 128, 192, 256 비트의 키를 사용할 수 있으며 SEED 는 128를 OpenBSD 에서 많이 사용하는 Blowfish 는 32 ~ 448 비트의 키 길이를 사용합니다. 대칭키 암호는 공개키 방식에 대비하여 구현이 용이하고 CPU/메모리를 적게 사용하고 매우 빠른 암/복호화 속도를 장점으로 갖고 있으며 Windows의 BitLocker 와 OS X 의 FileVault 같이 디스크 전체를 암호화할 때도 대칭키 방식(AES) 을 사용합니다. 하지만 대칭키 방식은 하나의 키만 사용하므로 상대방과 대칭키 기반으로 암호화 통신을 할 경우 상대방도 사전에 같은 키를 갖고 있어야 합니다.

AES(Advanced Encryption Standard)

AES는 2001년 미국 표준 기술 연구소(NIST)에 의해 제정된 암호화 방식이다. AES는 미국 정부가 채택한 이후 전 세계적으로 널리 사용되고 있다. AES는 Block으로 나눠어서 암호화를 하는데 128, 192, 256비트로 나눌 수가 있다. Block으로 암호화를 할때는 아래와 같이 4가지 모드가 있다.

  1. ECB ( Electric Code Book )
  2. CBC ( Cipher Block Chaining )
  3. OFB ( Output Feed Back )
  4. CFB ( Cipher Feed Back )

출처 : //jo.centis1504.net/?p=137

이처럼 대칭키 방식의 최대 단점은 암호화 통신을 위한 키 전달과 관리가 어려운 점입니다. 가령 대칭키를 보내는 중간에 유출될 우려가 있고 이로 인해 암호화 통신을 도감청 당할 수 있으며 여러 상대방과 통신할 경우 각각의 대칭키를 관리하기(생성, 전달, 유출시 폐기)가 어렵고 여러 상대방과 같은 키를 사용할 경우 키가 노출되면 모든 채널과 새로 키를 전달해야 하는 치명적인 단점이 있습니다.

블록 암호 패딩(Padding)

블록 알고리즘은 입력 데이타를 정해진 블록으로 잘라서 암/복호화를 수행하며 블록의 크기는 알고리즘마다 다릅니다. 예로 AES 는 128 bit(16 byte)를 하나의 블록으로 처리합니다. 하지만 입력 데이타가 항상 블록의 배수일리는 없으므로 입력데이타를 블록 크기에 맞춰주는 작업이 필요하며 이를 패딩이라고 합니다. 즉 AES 를 사용할 경우 암호화할 데이타가 19 바이트일 경우 최소 공배수인 32 byte 로 맞춰주어야 하며 이 작업을 패딩이라고 지칭하며 여러 가지 패딩 방법이 있습니다.

CBC(cipher-block chaining)

CBC 는 ECB 의 단점을 해결하기 위한 방법으로 각 블록은 암호화되기 전에 이전 블록과 XOR 연산을 거치며 유추가 힘들도록 최초의 입력은 IV(initial vector) 라는 데이타와 연산을 하게 됩니다. 이때문에 CBC 암호화를 사용할 경우 대칭키와 IV 가 필요합니다.

데이타 암호화 프로세스

데이타 암호화와 복호화 작업을 누가 수행하는지에 따라 암호화 프로세스가 달라져야 합니다.

사용자가 복호화

예로 사이트 로그인 정보를 관리해 주는 lastpass.com 서비스를 생각해 봅시다. 로그인 정보는 lastpass 서버에 암호화해서 보관되며 lastpass가 복호화를 하면 안 됩니다. 이때 필요한 방법은 PBKDF2같이 비밀번호 기반으로 암호화하는 방법으로 사용자로부터 마스터 암호를 입력받고 PBKDF2 함수를 수행해서 안전한 대칭키와 Initial Vector 를 생성해서 복호화합니다. 암호화와 복호화는 사용자의 비밀번호를 기반으로 이루어지며 사용자만 수행해야 합니다.. 이 방법은 암호화된 사용자 데이타마다 비밀번호가 다르므로 서버는 키 관리가 힘들지 않고 실제 데이타/암복호화는 사용자 PC에서 이루어지는 경우가 많으므로 서버의 부담이 적습니다.

서버에서 복호화

사용자의 신용 카드 정보를 보관하고 매 달마다 결제한다고 생각해 봅시다. 서버는 정보를 암호화하여 안전하게 보관해야 하며 매달 결제일마다 복호화하여 카드 정보를 알아내야 합니다. 데이타의 암호화/복호화가 서버에서 이루어지므로 보안에 매우 신경써야 하며 특히 암호화 키를 잘 관리해야 합니다. 이 모델을 사용할 경우 HSM을 도입하여 안전하게 키 관리하는것이 좋습니다.

출처 : //lesstif.gitbooks.io/web-service-hardening/content/encryption.html

AES-128-CBC 대칭키 test

class AESCryptoCBC(): ''' * iv or key generator * from Crypto import Random iv = Random.new().read(AES.block_size) # binary code import secrets iv = secrets.token_hex(8) ''' def __init__(self): from Crypto.Cipher import AES key = b'1234567891234567' iv = b'1234567891234567' self.crypto = AES.new(key, AES.MODE_CBC, iv) def encrypt(self, input): import base64 print('input :: ', input) encrypted = self.crypto.encrypt(input) print('encrypted :: ', encrypted) encoded_encrypted = base64.b64encode(encrypted) return encoded_encrypted def decrypt(self, encrypted): import base64 decoded_data = base64.b64decode(encrypted) print('decoded_data :: ', decoded_data) decrypted = self.crypto.decrypt(decoded_data) return decrypted.decode('utf-8') def aes_test(request): data = '1234567891234567' enc = AESCryptoCBC().encrypt(data) print("The encoded_encrypted value is", type(enc), enc) dec = AESCryptoCBC().decrypt(enc) print("The decrypted value is", type(dec), dec) # 결과물 # input :: 1234567891234567 # encrypted :: b'\x1aa\x03\x97\x14|\xc0DKT\xf7e\x06\xca@\xa6' # The encoded_encrypted value is <class 'bytes'> b'GmEDlxR8wERLVPdlBspApg==' # decoded_data :: b'\x1aa\x03\x97\x14|\xc0DKT\xf7e\x06\xca@\xa6' # The decrypted value is <class 'str'> 1234567891234567

블록 암호 패딩(Padding) 작업을 해보자

python의 AES.MODE_CBC 모듈에서 key는 16, 24, or 32 bytes로, IV는 16 bytes 값으로 선언하지 않을 경우 발생하는 에러는 다음과 같다.

  1. AES key must be either 16, 24, or 32 bytes long
  2. IV must be 16 bytes long

또한 암호화할 데이터가 16의 배수가 아니여도 아래와 같은 에러가 발생한다.

Input strings must be a multiple of 16 in length

이 에러들을 해결하기 위해 사용한 파이썬의 struct 모듈을 사용했다.

This module performs conversions between Python values and C structs represented as Python bytes objects. This can be used in handling binary data stored in files or from network connections, among other sources

import struct class AESCryptoCBC(): def __init__(self): from Crypto.Cipher import AES key = 'samplekey' iv = 'sampleIV' encoded_key = struct.pack('16s', key.encode('utf-8')) encoded_iv = struct.pack('16s', iv.encode('utf-8')) self.crypto = AES.new(encoded_key, AES.MODE_CBC, encoded_iv) def encrypt(self, input): encoded_input = struct.pack('16s', input.encode('utf-8')) encrypted = self.crypto.encrypt(encoded_input) return base64.b64encode(encrypted) def decrypt(self, encrypted): decoded_data = base64.b64decode(encrypted) decrypted = self.crypto.decrypt(decoded_data) return decrypted.decode('utf-8')

struct.pack(fmt, v1, v2, ...) : Return a bytes object containing the values v1, v2, … packed according to the format string fmt. The arguments must match the values required by the format exactly.

pack() 함수를 사용하면 16 bytes에 모자란 부분을 b'samplekey\x00\x00\x00\x00\x00\x00\x00' 와 같이 \x00로 채워준다. 만약 value가 16bytes 이상일 경우, 17자리부터 절삭한다.

>>> key = 'samplekey01234567890' >>> encoded_key = struct.pack('16s', key.encode('utf-8')) >>> encoded_key b'samplekey0123456'

<참고>

  • //blog.naver.com/PostView.nhn?blogId=s2kiess&logNo=220243476924
  • //docs.python.org/ko/3.6/library/struct.html

Toplist

최신 우편물

태그