본문 바로가기
Data Structure & Algorithm/알고리즘

[암호] AES (C++ 구현/멤버 변수, public 함수) - 2

by 별준 2021. 9. 23.

2021.09.23 - [Data Structure & Algorithm/알고리즘] - [암호] AES (Advanced Encryption Standard) - 1

 

[암호] AES (Advanced Encryption Standard) - 1

References 리얼월드 알고리즘 Contents AES란? AES 암호화/복호화 알고리즘 Key Scheduling C++ 구현 AES (Advanced Encryption Standard) ? 현대 암호 기술은 특정한 수학적 방법을 사용하여 암호문을 생성합니..

junstar92.tistory.com

이번 글에서는 이전 글에 이어서 AES를 C++로 구현해보도록 하겠습니다.

AES 구현에 대해서 검색해보면 최적화된 코드들이 많지만, 제가 아직 수학적으로 완벽하게 이해하지 못했고, 쉽게 살펴보기 위해서 최적화에 대해서는 신경쓰지 않고 제 스타일대로 구현하였습니다.

 

코드 라인이 꽤 많아서, 멤버 변수와 public 함수 / private 함수 이렇게 두 부분으로 나누어서 살펴보겠습니다.

 

전체 코드는 github에서 살펴보실 수 있습니다 !

https://github.com/junstar92/DataStructure_Algorithm/tree/master/Algorithm/AES


Block Cipher Mode(BCM)

AES를 직접 구현하기 전에 추가로 알아야할 것이 몇 가지 있는데, 첫 번째가 바로 Block Cipher Mode입니다.

한국어로 블록 암호 운영 방식이라고 불리는데, 하나의 키 아래에서 블록 암호를 생성할 때 반복적으로 안전하게 이용하는 절차를 의미합니다. (위키 참조)

 

여러 가지가 있지만, 이번 글에서는 두 가지의 모드(ECB, CBC)만 살펴볼 예정입니다.

Electronic Codebook (ECB)

ECB는 모드 중에서 가장 간단한 구조를 가지고 있습니다. 이 방법은 AES에서 각 블록을 암호화/복호화할 때 독립적으로 수행합니다. 

출처 : 위키피디아

즉, ECB는 모든 블록이 같은 암호화키를 사용하게 되며, 따라서 보안에 취약합니다. 또한, 암호화하고자하는 메세지를 여러 블록으로 나누었을 때, 두 블록이 같은 값을 가진다면, 암호화한 결과 역시 동일합니다.

출처 : 위키피디아

보안에는 취약하지만, 해당 방법은 병렬로 암호화를 수행할 수 있기 때문에 속도면에서는 빠릅니다.

 

Cipher-Block Chaining(CBC)

CBC 모드는 각 블록이 암호화되기 전에 이전 블록의 암호화 결과와 XOR 연산을 수행하게 됩니다. 첫 블록인 경우에는 이전 블록이 존재하지 않기 때문에 초기화 벡터(Initialization Vector, IV)를 사용하게 됩니다. 다만, 초기화 벡터가 같은 경우에는 동일한 메세지를 암호화할 때 출력 결과가 항상 같으므로, 매 암호화마다 다른 초기화 벡터를 사용해야 합니다.

출처 : 위키피디아

CBC 모드는 ECB와 달리 각 블록이 이전 블록의 결과에 의존하므로 각 블록을 병렬로 암호화를 수행할 수는 없습니다. 다만, 복호화의 경우에는 각 블록에서 이전 블록의 암호문을 사용하므로 병렬로 복호화가 가능합니다.

 

Padding

AES를 구현하기 전에 알아야되는 것 중, 두 번째는 바로 Padding입니다. 

AES에서 암호화할 때, 메세지를 16바이트로 블록화하여서 각 블록에 대해서 암호화를 수행하게 됩니다. 하지만, 암호화하고자하는 메세지가 항상 16바이트로 떨어지지 않기 때문에 마지막 블록은 빈 공간이 생길 수 있습니다. 

즉, 암호화/복호화할 때 input 메세지의 사이즈가 블록 사이즈의 배수가 되지 않으면 그 배수에 맞도록 빈 공간을 채워주는 것이 필요합니다. 

 

여기서 사용되는 것이 바로 PKCS#7 Padding 인데, 이것은 빈 공간의 개수를 값으로 하여 빈 공간을 채워주는 방법입니다. 

만약 마지막 블록이 [0xAB 0xCD 0xEF] 라면, 16바이트 중에 13바이트가 비어있고, 나머지 빈 공간은 13(0x0D)으로 채워지게 됩니다.

-> [0xAB 0xCD 0xEF 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D]

마지막 블록에서 16바이트 중에 11바이트만 채워져있다면, 0x05값으로 빈 공간을 모두 채우게 됩니다.

 


AES Class

이제 AES의 C++ 구현을 살펴보도록 하겠습니다.

먼저 헤더 파일부터 살펴보겠습니다.

/*************************** AES.hpp ***************************/
#ifndef _AES128_HPP_
#define _AES128_HPP_
#include <iostream>
#include <string>
#include <vector>

using byte = uint8_t;

typedef enum BlockCipherMode {
	ECB,
	CBC,
} MODE;

class AES
{
private:
	const uint8_t Bsize_ = 4; // the number of 32 bits(4 bytes) words in block state
	const uint8_t Ncol_ = 4; // the column size of state matrix
	const uint8_t Nstate_ = 16; // the number of state matrix's bytes
	const uint8_t Nkey_;	// the number of 32 bits(4 bytes) words in cipher key
	const uint8_t Nround_; // the number of round
	MODE mode_;
	std::vector<byte> iv_; // initialization vector (it is used if CBC mode)
	std::vector<byte> phiv_; // placeholder for saving prev iv

	const byte sbox_[256] = {
		0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
		0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
		0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
		0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
		0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
		0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
		0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
		0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
		0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
		0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
		0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
		0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
		0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
		0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
		0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
		0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 
	};
	const byte inv_sbox_[256] = {
		0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
		0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
		0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
		0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
		0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
		0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
		0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
		0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
		0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
		0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
		0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
		0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
		0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
		0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
		0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
		0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d
	};
	const byte rcon_[11] = {
		0xFF, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36
	};

	std::vector<byte> _createState(const std::vector<byte>& block, uint32_t start);
	std::vector<byte> _expandKey(const std::vector<byte>& cipherKey, bool verbose);
	void _addRoundKey(std::vector<byte>& state, const std::vector<byte>& roundKey, uint8_t round, bool verbose);

	void _xor_iv(std::vector<byte>& state, std::vector<byte>& iv, bool verbose);

	void _encryption(std::vector<byte>& state, const std::vector<byte>& roundKey, bool verbose);
	byte _mappingSBox(const byte val);
	void _subBytes(std::vector<byte>& state, bool verbose);
	void _shiftRows(std::vector<byte>& statee, bool verbose);
	void _mixColumns(std::vector<byte>& state, bool verbose);

	void _decryption(std::vector<byte>& state, const std::vector<byte>& roundKey, bool verbose);
	byte _mappingInvSBox(const byte val);
	void _invSubBytes(std::vector<byte>& state, bool verbose);
	void _invShiftRows(std::vector<byte>& state, bool verbose);
	void _invMixColumns(std::vector<byte>& state, bool verbose);
	
	std::string _convertTypeByteStateToStr(const std::vector<byte>& state, bool decryption);
	std::vector<byte> _convertTypeStrToByteBlock(std::string str);
	void _printState(const std::vector<byte>& state);
	std::string _convertCharToStrHex(const char& c);
	char _convertHexToChar(const byte hex);

public:
	AES() = delete;
	AES(size_t keyBits, MODE mode = MODE::ECB);

	void setIV(uint32_t seed);
	void setIV(std::vector<byte> iv);
	std::vector<byte> getIV() const;

	std::string encryption(const std::string& input, const std::string& cipherKey, bool verbose = false);
	std::string decryption(const std::string& input, const std::string& cipherKey, bool verbose = false);

	std::string convertStrToHexStr(const std::string& str);
	std::string convertHexStrToStr(const std::string& hex);
};

#endif

 

AES는 byte로 이루어진 4x4 state matrix를 사용하므로, uint8_t의 타입을 byte라는 이름으로 사용합니다.

또한, 클래스 내에서 4x4 matrix는 2차원 배열이 아닌 1차원 배열인 vector로 나타내어서 연산에 사용됩니다 !


생성자

AES() = delete;	// 기본 생성자는 사용하지 못하도록 delete
AES(size_t keyBits, MODE mode = MODE::ECB);

AES 클래스의 생성자는 사용되는 암호화키의 bits 수를 입력으로 받습니다. AES는 128, 192, or 256 의 비트수를 암호화키로 사용하므로 이 3개의 입력만 허용됩니다. 그리고, Block Cipher Mode도 입력으로 받습니다. 입력하지 않는다면 ECB로 설정되고, CBC로 설정할 수도 있습니다. 

생성자는 아래처럼 구현되어 있습니다.

AES::AES(size_t keyBits, MODE mode) : Nkey_(keyBits / 32), Nround_(6 + this->Nkey_), mode_(mode)
{
	assert(keyBits == 128 || keyBits == 192 || keyBits == 256);
	
    if (mode == MODE::CBC) {
		this->iv_.assign(this->Nstate_, 0);
		this->setIV(time(NULL));
	}
}

암호화키의 비트수인 keyBits를 파라미터로 받아서, 사용되는 암호화키의 word(4 bytes=32 bits)의 개수(NKey_)를 설정하고, 각 비트수에 따라 결정되는 round 수(Nround_)도 설정합니다. 128, 192, 256만 비트수로 허용되므로 assert를 통해서 다른 값이 입력되면 에러가 발생하도록 하였습니다. 

 

mode는 ECB나 CBC로 설정될 수 있는데, CBC로 설정되는 경우에는 초기화 벡터(initialization vector)가 필요합니다. 따라서 내부에서 사용되는 초기화 벡터(iv_)의 크기를 16바이트로 설정하고, 그 값을 랜덤으로 세팅해줍니다.


멤버 변수

내부에서 사용되는 변수는 다음과 같습니다.

Bsize_ Block Size. Block에서 word(4 바이트)의 개수
Ncol_ The number of Columns. State Matrix(4x4)의 column 개수
Nstate_ The size of state matrix. State Matrix의 크기(16 바이트)
Nkey_ 암호화 키의 word(4 바이트) 개수
AES128, AES192, AES256 경우에 따라 4, 6, 8로 세팅
Nround_ 암호화 내부 라운드 개수
Key bits(128, 192, 256)에 따라서 10, 12, 14로 세팅
mode_ Block Cipher Mode
ECB 또는 CBC로 선택 가능
iv_ Initialization Vector. CBC 모드인 경우 사용되는 초기화 벡터
phiv_ 이전 블록의 초기화 벡터를 저장하기 위해서 사용되는 place holder
sbox_ SubBytes 연산을 위해 sbox 값들을 미리 저장해둔 배열
inv_sbox_ InvSubBytes 연산을 위해 inverse sbox 값들을 미리 저장해둔 배열
rcon_ 키 확장에서 사용되는 Round Constant 값들을 저장해둔 배열

 

public 멤버 함수

public으로 선언된 멤버 함수입니다. 

// 초기화벡터(IV)를 세팅하는 함수입니다. 
// 랜덤 seed를 가지고 랜덤하게 세팅하거나 특정 IV로 세팅할 수 있습니다.
void setIV(uint32_t seed); 
void setIV(std::vector<byte> iv);
// 초기화벡터를 읽는 함수입니다. 16바이트의 벡터를 반환합니다.
std::vector<byte> getIV() const;

// 암호화 함수
// hex값들로 이루어진 암호화하고자하는 입력과 암호화키를 파라미터로 입력받아 암호화를 수행합니다.
// verbose는 true로 세팅되면 암호화 과정 중간중간의 결과를 출력합니다.(default : false)
std::string encryption(const std::string& input, const std::string& cipherKey, bool verbose = false);

// 복호화 함수
// 암호화와 마찬가지로 hex값의 암호문과 암호화키를 파라미터로 입력받아 복호화를 수행합니다.
std::string decryption(const std::string& input, const std::string& cipherKey, bool verbose = false);

// string(아스키코드) type을 hex값의 string type으로 변환해주는 함수입니다.
std::string convertStrToHexStr(const std::string& str);
// 반대로 hex값의 string type을 string(아스키코드) type으로 변환해줍니다.
std::string convertHexStrToStr(const std::string& hex);

 

암호화/복호화 함수는 string type의 16진수값을 입력으로 받습니다.

따라서, 입력이 만약 문자열이라면 convertStrToHexStr 함수를 통해서 변환해주어야 합니다. 만약 암호화키도 문자열이라면 16진수 값의 string으로 변경해주어야 합니다.

AES aes128(128);
std::string str = "The current fire house installed within the building used by South Korea's agency for managing the industrial zone will move to the newly built three-story building, the official said.";
std::string hexStr = aes128.convertStrToHexStr(str);
cipherKey = "ABCDEFGHIJKLMNOP";
cipherKey = aes128.convertStrToHexStr(cipherKey);

위와 같이 변환 작업이 사전에 필요합니다.

 

다른 함수의 구현은 github을 참조해주시고, encryption과 decryption 함수만 어떻게 구현되는지 살펴보겠습니다.

encryption

std::string AES::encryption(const std::string& input, const std::string& cipherKey, bool verbose)
{
	assert(cipherKey.size() == this->Nkey_ * 4 * 2); // check cipherKey(str) size == 128 / 192 / 256 bits * 2

	uint32_t block_size = input.length() / (this->Nstate_ * 2);
	if (input.length() % (this->Nstate_ * 2) != 0)
		block_size += 1;

	std::string cipherText = "";
	std::vector<byte> plainText = this->_convertTypeStrToByteBlock(input);
	std::vector<byte> _cipherKey = this->_convertTypeStrToByteBlock(cipherKey);
	std::vector<byte> roundKey = this->_expandKey(_cipherKey, verbose);

	if (this->mode_ == MODE::CBC) {
		this->phiv_ = this->iv_;
	}

	for (uint32_t i = 0; i < block_size; i++) {
		std::vector<byte> state = this->_createState(plainText, i * this->Nstate_);

		if (this->mode_ == MODE::CBC) {
			this->_xor_iv(state, this->phiv_, verbose);
		}

		this->_encryption(state, roundKey, verbose);

		if (this->mode_ == MODE::CBC) {
			this->phiv_ = state;
		}

		cipherText += this->_convertTypeByteStateToStr(state, false);
	}

	if (verbose) {
		std::cout << "\n --- Cipher Text --- \n";
		uint32_t i = 0;
		for (auto& c : cipherText) {
			std::cout << c;
			i++;
			if (i % (this->Nstate_ * 2) == 0)
				std::cout << "\n";
		}
		std::cout << "\n";
	}

	return cipherText;
}
  • line 3 : 암호화를 시작하기 전에 우선 입력받은 암호화키(cipher key)가 유효한지 체크해줍니다. 사용되는 암호화키 비트수에 따라서 입력받은 암호화키 문자열의 길이가 다릅니다. 바이트 하나당 문자열로 변환하면 0x[00] 꼴로 변환되므로 암호화키의 바이트 수 x 2가 되어야 유요합니다.
  • line 5 ~ 7 : 입력받은 평문은 16바이트의 block으로 변환되어서 암호화가 진행되므로, 평문이 몇 개의 block으로 나누어지는지 확인합니다. 만약 16의 배수로 떨어지지 않는다면 나머지는 패딩을 추가하여 진행하도록 block_size를 1 증가해줍니다.
  • line 9 ~ 11 : 입력받은 평문과 암호화키를 쉽게 계산하기 위해서 _convertTypeStrToByteBlock 함수를 통해 byte type의 vector로 변환합니다.
  • line 12 : 여기서부터 이제 진짜 AES 알고리즘의 시작입니다. 각 라운드에서 사용되는 라운드키(round key)를 생성합니다. 각 암호화 블럭에서 사용되는 라운드키는 동일하므로 불필요한 연산을 제거하기 위해서 처음 한 번의 연산으로 라운드키를 미리 생성하여 사용하도록 되어있습니다. _expandKey 함수를 통해서 라운드키가 생성됩니다.
  • line 14 ~ 16 : CBC 모드인 경우에는 초기화 벡터가 사용되는데, 각 블록에서 이전 블록의 암호문이 초기화 벡터로 사용됩니다. 첫 블록은 초기에 설정된 초기화 벡터가 사용되므로 phiv에 초기 설정된 값을 설정합니다.
  • line 18 : 각 블록의 암호화를 수행합니다.
  • line 19 : 블록의 state matrix를 생성합니다. _createState 함수를 통해 state matrix를 반환받습니다.
  • line 21 ~ 23 : CBC 모드인 경우 이전 블록의 암호문을 state matrix와 XOR 연산합니다. XOR 연산은 _xor_iv 함수를 통해 수행됩니다.
  • line 25 : 하나의 블록에서 수행되는 암호화 과정을 _encryption 함수 내부에서 수행합니다. 이 함수 내부에서 addRoundKey, SubBytes, ShiftRows, MixColumns 연산이 수행됩니다.
  • line 27 ~ 29 : 현재 블록에서의 암호문을 다음 블록의 초기화 벡터로 사용하기 위해서 저장합니다.
  • line 31 : 변환된 암호문을 cipherText 변수에 저장합니다. 그리고 line 19로 돌아가 다음 라운드를 수행합니다.

encryption 함수는 위와 같이 진행됩니다. 각 연산을 위한 내부 함수는 아래에서 조금 더 자세히 다루도록 하겠습니다.

 

decryption

std::string AES::decryption(const std::string& input, const std::string& cipherKey, bool verbose)
{
	assert(cipherKey.size() == this->Nkey_ * 4 * 2); // check cipherKey(str) size == 128 / 192 / 256 bits * 2

	uint32_t block_size = input.length() / (this->Nstate_ * 2);

	std::string plainText = "";
	std::vector<byte> cipherText = this->_convertTypeStrToByteBlock(input);
	std::vector<byte> _cipherKey = this->_convertTypeStrToByteBlock(cipherKey);
	std::vector<byte> roundKey = this->_expandKey(_cipherKey, verbose);

	if (this->mode_ == MODE::CBC) {
		this->phiv_ = this->iv_;
	}

	for (uint32_t i = 0; i < block_size; i++) {
		std::vector<byte> state = this->_createState(cipherText, i * this->Nstate_);
		std::vector<byte> tmp;

		if (this->mode_ == MODE::CBC) {
			tmp = state;
		}

		this->_decryption(state, roundKey, verbose);

		if (this->mode_ == MODE::CBC) {
			this->_xor_iv(state, this->phiv_, verbose);
			this->phiv_ = tmp;
		}

		plainText += this->_convertTypeByteStateToStr(state, true);
	}

	if (verbose) {
		std::cout << "\n --- Plain Text --- \n";
		uint32_t i = 0;
		for (auto& c : plainText) {
			std::cout << c;
			i++;
			if (i % (this->Nstate_ * 2) == 0)
				std::cout << "\n";
		}
		std::cout << "\n";
	}

	return plainText;
}

복호화도 암호화와 유사합니다. 

  • line 3 encryption 함수와 마찬가지로 암호화키의 유효성을 확인합니다.
  • line 5 : 입력받은 암호문의 몇 개의 block으로 나누어지는지 계산합니다. 암호문은 항상 16바이트의 배수가 되므로 encryption 때와 달리 나머지를 고려할 필요가 없습니다.
  • line 7 ~ 9 : 입력받은 암호문과 암호화키를 _convertTypeStrToByteBlock 함수를 통해 byte type의 vector로 변환합니다.
  • line 10 : 암호화와 마찬가지로 불필요한 연산을 제거하기 위해서 처음 한 번의 연산으로 라운드키를 미리 생성하여 합니다. _expandKey 함수를 통해서 라운드키가 생성됩니다.
  • line 12 ~ 14 : CBC 모드인 경우에는 초기화 벡터가 사용되고, 첫 블록은 초기에 설정된 초기화 벡터가 사용되므로 phiv에 초기 설정된 값을 설정합니다.
  • line 16 : 각 블록의 복호화를 수행합니다.
  • line 17 : 블록의 state matrix를 생성합니다. _createState 함수를 통해 state matrix를 반환받습니다.
  • line 18 ~ 22 : CBC 모드인 경우에 복호화 연산을 모두 수행하고 이전 블록의 암호문을 state matrix와 XOR 연산하게 됩니다. 복호화를 진행하면 블록에서의 암호문이 덮어씌워지므로 tmp 변수에 임시로 저장해둡니다.
  • line 24 : 현재 블록에서 수행되는 복호화 과정을 _decryption 함수 내부에서 수행합니다. 함수 내부에서 InvAddRoundKey, InvSubBytes, InvShiftRows, InvMixColumns 연산이 수행됩니다.
  • line 26 ~ 29 : 이전 블록의 암호문(첫 블록인 경우에는 초기화벡터)값을 state matrix와 XOR 연산을 수행합니다. 그리고 다음 블록에서 사용될 값을 phiv에 저장합니다.
  • line 31 : 복호화된 평문을 plainText 변수에 저장합니다. 그리고 line 16로 돌아가 다음 라운드를 수행합니다.

 

구현된 클래스는 아래 예시처럼 사용가능합니다.

#include <iostream>
#include "AES.hpp"

int main(void) {
	AES aes128(128);
	AES aes192(192);
	AES aes256(256);

	std::string plainText = "3243F6A8885A308D313198A2E0370734";
	std::string cipherKey = "2B7E151628AED2A6ABF7158809CF4F3C";

	std::cout << "Input : " << plainText << std::endl;
	std::string cipherText = aes128.encryption(plainText, cipherKey, true);
	std::cout << "Cipher : " << cipherText << std::endl;
	std::string origin = aes128.decryption(cipherText, cipherKey, true);
	std::cout << "origin : " << origin << std::endl;

	plainText = "014BAF2278A69D331D5180103643E99A";
	cipherKey = "E8E9EAEBEDEEEFF0F2F3F4F5F7F8F9FA";

	std::cout << "Input : " << plainText << std::endl;
	cipherText = aes128.encryption(plainText, cipherKey);
	std::cout << "Cipher : " << cipherText << std::endl;
	origin = aes128.decryption(cipherText, cipherKey);
	std::cout << "origin : " << origin << std::endl;

	plainText = "76777475F1F2F3F4F8F9E6E777707172";
	cipherKey = "04050607090A0B0C0E0F10111314151618191A1B1D1E1F20";

	std::cout << "\nInput : " << plainText << std::endl;
	cipherText = aes192.encryption(plainText, cipherKey);
	std::cout << "Cipher : " << cipherText << std::endl;
	origin = aes192.decryption(cipherText, cipherKey);
	std::cout << "origin : " << origin << std::endl;

	plainText = "069A007FC76A459F98BAF917FEDF9521";
	cipherKey = "08090A0B0D0E0F10121314151718191A1C1D1E1F21222324262728292B2C2D2E";

	std::cout << "\nInput : " << plainText << std::endl;
	cipherText = aes256.encryption(plainText, cipherKey);
	std::cout << "Cipher : " << cipherText << std::endl;
	origin = aes256.decryption(cipherText, cipherKey);
	std::cout << "origin : " << origin << std::endl;

	//aes128.setIV(time(NULL));
	std::cout << "\n AES/128/ECB mode\n";
	std::string str = "The current fire house installed within the building used by South Korea's agency for managing the industrial zone will move to the newly built three-story building, the official said.";
	std::string hexStr = aes128.convertStrToHexStr(str);
	cipherKey = "ABCDEFGHIJKLMNOP";
	cipherKey = aes128.convertStrToHexStr(cipherKey);
	std::cout << "Input(Str) : " << str << std::endl;
	std::cout << "Input(Hex) : " << hexStr << std::endl;
	cipherText = aes128.encryption(hexStr, cipherKey);
	std::cout << "Cipher : " << cipherText << std::endl;
	origin = aes128.decryption(cipherText, cipherKey);
	std::cout << "Output(Hex) : " << origin << std::endl;
	std::cout << "Output(Str) : " << aes128.convertHexStrToStr(origin) << std::endl;

	std::cout << "\n AES/128/CBC mode\n";
	AES aes128_CBC(128, MODE::CBC);
	std::cout << "Input(Str) : " << str << std::endl;
	std::cout << "Input(Hex) : " << hexStr << std::endl;
	cipherText = aes128_CBC.encryption(hexStr, cipherKey);
	std::cout << "Cipher : " << cipherText << std::endl;
	origin = aes128_CBC.decryption(cipherText, cipherKey);

	std::cout << "Output(Hex) : " << origin << std::endl;
	std::cout << "Output(Str) : " << aes128.convertHexStrToStr(origin) << std::endl;

	return 0;
}

댓글