흐르는 시간의 블로그...

AES 관련 코딩을 하다보니 내가 연결하려는 서버측과 내가 가지고 있는 C++ 소스의 상황이 달랐다.


예제로 구한 코드에서 IV(초기화 벡터)값을 요구 했다.


하지만 고객사에서 보내온 Java 파일에는 해당 값들이 전혀 없었다.(링크된 내용과 동일한 코드였다)


알고 봤더니... 다음과 같은 Block Mode들이 존재 했다.

내쪽에서는 CBC를 사용하는 C++ 코드를 사용했고 상대측에서 보내온 자바파일은 디폴트를 사용했는데 자바는 ECB가 기본이었다


자료 출처는 AES-128-CBC 를 이용하는 방법 을 참고했다

아래의 자료는 위 출처의 자료이다.





AES-128-CBC 를 이용하는 방법

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 )
원래 위의 4개 모드는 DES 이용을 위해 고안되었는데 DES에 한정하지 않고 모든 Block암호에 적용이 가능하다.
이외에도 PCBC와 Counter Method등의 새로운 모드도 고안되고 있다고 한다.
먼저 ECB 모드를 살펴보자.
ECB모드는 1Block씩 단순히 처리를 한다.
암호문공격에 취약하며 사이즈가 큰 문서의 암호는 어울리지 않아 크게 쓰이고 있지는 않은듯하다.

http://jo.centis1504.net/wp-content/uploads/2010/11/Encryption-ECB_MODE.swf

두번째로 CBC모드인데 앞서의 ECB에서의 암호화한 Block의 결과를 다음의 Block에 XOR 연산하여 나가는게 특징이다. 이때 제일 처음의 암호화시에는 마지막 블럭결과를 이용하거나 IV (Initial Vector)를 이용하게 된다.

http://jo.centis1504.net/wp-content/uploads/2010/11/Encryption-CBC_MODE.swf

세번째로 OFB모드인데 IV를 암호화하여 그것을 다시 암호화한 후 계속 난수를 생성한다. 그렇게 생성된 난수리스트를 XOR 연산에 의해 원문에 적용하여 암호화하는 방식이다. 즉, Block암호를 Stream암호와 같이 사용한다고 보면 되겠다.

http://jo.centis1504.net/wp-content/uploads/2010/11/Encryption-OFB_MODE.swf

상세정보는 http://www.triplefalcon.com/Lexicon/Encryption-Block-Mode-1.htm 를 참조할것.
그럼 Java에서 AES-128-CBC를 이용하는 방법은 아래와 같다.
공통키를 생성하는 방법은 이전 포스트를 참조할것.

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secureKey);byte[] iv = cipher.getIV();
byte[] encryptedData = cipher.doFinal("".getBytes());
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");cipher.init(Cipher.ENCRYPT_MODE,secureKey);
byte[] iv = cipher.getIV();byte[] encryptedData = cipher.doFinal("".getBytes());

복호화는 아래와 같다.

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
AlgorithmParameters iv = AlgorithmParameters.getInstance("AES");
iv.init("IV".getBytes());
cipher.init(Cipher.DECRYPT_MODE, secureKey, iv);
byte[] plainData = cipher.doFinal(encryptedData);


일년에 열번도 사용하지 않을 서버를 개발하고 있다.


중요한 건 나름 다양한 기술이 포함되어 있다는 것이다.


TCP/IP는 기본... 해당 서버쪽에서는 Health 체크도 했으면 싶어한다.


AES 암호화가 기본이기도 하다.

암호화를 잘 모르지만 나름 잘 정리된 싸이트와 라이브러리를 찾았다.

덕분에 매우 잘 쓰고 있다.


출처는 AES 암호/복호 C/C++ 기능 구현 이다.

아래의 내용은 해당 출처 내용의 복사본이다.


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

AES 암호/복호 C/C++ 기능 구현

요즘 암호화가 필요해서 언어별로 AES 알고리즘을 가지고 암/복호화 하는 라이브러리를 개발하고 있습니다. 저와 같이 필요한 분들이 있을 것 같아 공유해 드립니다.

1. Cryptopp(Crypto++) 다운로드
 - Cryptopp 홈피 : http://cryptopp.sourceforge.net/
 - 다운로드 : http://sourceforge.net/project/showfiles.php?group_id=6152&package_id=6210

2. Cryptopp(Crypto++) 컴파일
 - 다운 받은 파일을 압축 vna
 - make;make install(make 만 한다음 libcryptopp.a,  cryptest.exe 나오면 성공
 - 원하는 libcryptopp.a 파일과 .h파일을 include디렉토리에 카피

3. AES 암호화/복호화 샘플 예제
 - 앞서 등록한 포스트인 Flex 소스와 상호 암/복호가 가능하게 구현함
 - key, iv를 활용하며 암호 문자열은 base64 인코딩함
 - 추후 Java, ASP, PHP, C#도 공개할 예정
#include <iostream>
#include <iomanip>

#include "cryptopp/cryptlib.h"
#include "cryptopp/modes.h"
#include "cryptopp/aes.h"
#include "cryptopp/filters.h"
#include "cryptopp/base64.h"

void hex2byte(const char *in, uint len, byte *out)
{
for (uint i = 0; i < len; i+=2) {
char c0 = in[i+0];
char c1 = in[i+1];
byte c = (
((c0 & 0x40 ? (c0 & 0x20 ? c0-0x57 : c0-0x37) : c0-0x30)<<4) |
((c1 & 0x40 ? (c1 & 0x20 ? c1-0x57 : c1-0x37) : c1-0x30))
);
out[i/2] = c;
}
}

int main(int argc, char* argv[]) {
//
// 키 할당
//
byte key[CryptoPP::AES::DEFAULT_KEYLENGTH];
memset(key, 0x00, CryptoPP::AES::DEFAULT_KEYLENGTH );
char* rawKey="f4150d4a1ac5708c29e437749045a39a";
hex2byte(rawKey, strlen(rawKey), key);

// IV 할당
byte iv[CryptoPP::AES::BLOCKSIZE];
memset(iv, 0x00, CryptoPP::AES::BLOCKSIZE );
char* rawIv="86afc43868fea6abd40fbf6d5ed50905";
hex2byte(rawIv, strlen(rawIv), iv);
//
// 평문 할당
//
std::string plaintext = "http://mimul.com/pebble/default";
std::string ciphertext;
std::string base64encodedciphertext;
std::string decryptedtext;
std::string base64decryptedciphertext;

//
// 평문 출력
//
std::cout << "Plain Text (" << plaintext.size() <<
" bytes)" << std::endl;
std::cout << plaintext;
std::cout << std::endl << std::endl;

unsigned int plainTextLength = plaintext.length();

//
// AES 암호화 수행
//
CryptoPP::AES::Encryption
aesEncryption(key, CryptoPP::AES::DEFAULT_KEYLENGTH);
CryptoPP::CBC_Mode_ExternalCipher::Encryption
cbcEncryption(aesEncryption, iv);

CryptoPP::StreamTransformationFilter
stfEncryptor(cbcEncryption, new CryptoPP::StringSink( ciphertext));
stfEncryptor.Put(reinterpret_cast<const unsigned char*>
(plaintext.c_str()), plainTextLength + 1);
stfEncryptor.MessageEnd();

//
// Base64 인코딩
//
CryptoPP::StringSource(ciphertext, true,
new CryptoPP::Base64Encoder(
new CryptoPP::StringSink(base64encodedciphertext)
) // Base64Encoder
); // StringSource
//
// Base64 인코딩 문자열 출력
//
std::cout << "Cipher Text (" << base64encodedciphertext.size()
<< " bytes)" << std::endl;
std::cout << "cipher : " << base64encodedciphertext << std::endl;
std::cout << std::endl << std::endl;

//
// Base64 디코딩
//
CryptoPP::StringSource(base64encodedciphertext, true,
new CryptoPP::Base64Decoder(
new CryptoPP::StringSink( base64decryptedciphertext)
) // Base64Encoder
); // StringSource

//
// AES 복호화
//
CryptoPP::AES::Decryption aesDecryption(key,
CryptoPP::AES::DEFAULT_KEYLENGTH);
CryptoPP::CBC_Mode_ExternalCipher::Decryption
cbcDecryption(aesDecryption, iv );

CryptoPP::StreamTransformationFilter
stfDecryptor(cbcDecryption, new CryptoPP::StringSink(decryptedtext));
stfDecryptor.Put( reinterpret_cast<const unsigned char*>
(base64decryptedciphertext.c_str()), base64decryptedciphertext.size());
stfDecryptor.MessageEnd();

//
// 복호화 문자열 출력
//
std::cout << "Decrypted Text: " << std::endl;
std::cout << decryptedtext;
std::cout << std::endl << std::endl;
return 0;
}
4. 실행 결과
 - 아래 결과를 여기서 암호문을 디코딩해보세요. 정상적으로 복호화됩니다.
Plain Text (31 bytes)
http://mimul.com/pebble/default

Cipher Text (65 bytes)
cipher :
TrP74bYahMS3MRB0zbP9HEitaqVMsfWh/pmpygYE9iA3JN4p5tyMejSf9u2GBS/9

Decrypted Text:
http://mimul.com/pebble/default
Tags : 



Re: AES 암호/복호 C/C++ 기능 구현

와 이런 이런게 있는지 모랐네요

퍼갈게요 ^^

 

Avatar: 박선원

Re: AES 암호/복호 C/C++ 기능 구현

안녕하세요. 제가 지금 C로 AES 암/복호화를 만들고 있습니다. 그런데 암호시 출력되는 사이즈가 32바이트가 맞습니까? 키 사이즈는 256 입니다. 답변 주시면 감사합니다.

Re: AES 암호/복호 C/C++ 기능 구현

AES256은 256비트 이니까 바이트로 바꾸면 32byte가 맞지요.

Re: AES 암호/복호 C/C++ 기능 구현

많은 도움이 되었습니다 대단히 감사합니다

Re: AES 암호/복호 C/C++ 기능 구현

Re: AES 암호/복호 C/C++ 기능 구현

제가 찾던 정보를 정확히 알려주셔서 고맙습니다.

 

 

Re: AES 암호/복호 C/C++ 기능 구현

정말 많은 도움을 받았습니다. 
다만, 약간의 수정이 필요할 듯 하여 코멘트를 남깁니다.

1. AES 과정 중
As is
stfEncryptor.Put(reinterpret_cast<const unsigned char*> (plaintext.c_str()), plainTextLength + 1); 

To be
stfEncryptor.Put(reinterpret_cast<const unsigned char*> (plaintext.c_str()), plainTextLength); 

2. Base64 과정 중
As is
new CryptoPP::StringSink(base64encodedciphertext) 

To be
new CryptoPP::StringSink(base64encodedciphertext),false



Producer Consumer 큐를 만들었다.

이와 함께 Job을 가상화 하여 일정한 규칙(동일 DB Insert)을 가지는 다양한 Job을 처리하게 하였다.

그리고 그것도 템플릿으로 또 다시 묶었다. (너무 과잉인가.. ㅠ.ㅠ)


문제는 테스트 코드에서는 문제가 없는데...

상용 코드에서는 자꾸 에러가 나는 것이다.


pthread_mutex_lock()을 콜하면 "130"을 반환하고 errno로 "0"을 줬다.

프로그램은 lock이 걸려서 더 진행되지 않았다.


스택오버플로우도 뒤져보고 해서...

뭐 테스트 쓰레드도 만들고 다 해봤는데 또 잘 된다.


결론은!!!!

상용코드에서 init()을 콜하지 않았다. ㅠ.ㅠ

해당 init 코드에서 pthread_mutex_init()을 통해 mutex를 초기화 해야 하는데

초기화 하지 않은 mutex를 사용하려니 문제를 발생 시킨 것이다.


그런데 그게 다른 에러가 아니라... 130번이 반환되서 고생한 것이었다. ㅠ.ㅠ

"EOWNERDEAD"

최근 개발중 Producer(생산자) / Consumer(소비자) Queue를 만들었다.

매우 간단하다.

두개의 뮤텍스로 각자의 큐를 관리하고 일정 시간마다 Productor에 쌓인 데이터를 Consumer로 전달한다.

이렇게 나누는 중요한 이유는 생산자와 소비자를 별도로 관리하기 위함이다.


생산자는 빠른 시간에 데이터를 생산자 큐에 전달하고 다음 작업을 진행한다.

소비자는 소비자 큐의 작업 목록을 순서대로 처리한다.

두 작업이 서로 간섭하지 않게 하기 위해 두개의 큐와 두개의 뮤텍스로 이 기능을 제공한다.


1초마다 생산자 큐를 비우고 소비자 큐에 추가하도록 작업 하였다.


문제는 해당 기능의 큐에 std::vector를 사용하였다.

이와 함께 Job을 다양화 하기 위해 Templete를 사용하였다.


개인적으로 Template는 기초적인 사용외에는 해본적이 없다.


결국 사방에서 문제가 발생했는데 현재까지 걸렸다 해결한 문제는 아래와 같다.




1. iterator 사용

  • vector<T*>::iterator _iter = q.begin() 을 사용하면 아래와 같은 에러를 발생한다
  • error: expected `;' before '_iter'
  • 여기저기 뒤지다가 찾은 정답은 요기 에 가면 나온다
  • typename vector<T*>::iterator _iter = q.begin()  <= typename 사용시
  • class vector<T*>::iterator _iter = q.begin()  <= class 사용시



2. 매우 허접한 코드이지만 그래도 누군가에게 조금의 도움이라도 되길 바라며 올려 본다.

//---------------------------------------------------------------------------------------------------------------------
// pcqueue.hpp
//
// Asker's product consumer queue
//---------------------------------------------------------------------------------------------------------------------
#pragma once

#include <iostream>
#include <vector>
#include <pthread.h>

#include "errno.h"

#include "common.hpp"

using namespace std;
//---------------------------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------------------------
template <class T>
class CPCQueue
{
private:
	pthread_mutex_t ProducerLock;
	pthread_mutex_t ConsumerLock;

	vector<T*> ProducerQueue;
	vector<T*> ConsumerQueue;

private:
	bool LockProducer()
	{
		int _nError = pthread_mutex_lock( &ProducerLock );
		if ( _nError )
		{
			cout << "error in lock producer. return : " << _nError << ", err :" << strerror(errno) << endl;
			formatlog( LOG_CRIT, "error in lock producer. return(%d) ErrNo(%d) ErrMsg(%s)", _nError, errno, strerror(errno));
			return false;
		}
	
		return true;
	}

	bool UnlockProducer()
	{
		int _nError = pthread_mutex_unlock( &ProducerLock );
		if ( _nError )
		{
			cout << "error in unlock producer. return : " << _nError << ", err :" << strerror(errno) << endl;
			formatlog( LOG_CRIT, "error in unlock producer. return(%d) ErrNo(%d) ErrMsg(%s)", _nError, errno, strerror(errno));
			return false;
		}
	
		return true;
	}

	bool LockConsumer()
	{
		int _nError = pthread_mutex_lock( &ConsumerLock );
		if ( _nError )
		{
			cout << "error in lock consumer. return : " << _nError << ", err :" << strerror(errno) << endl;
			formatlog( LOG_CRIT, "error in lock consumer. return(%d) ErrNo(%d) ErrMsg(%s)", _nError, errno, strerror(errno));
			return false;
		}

		return true;
	}

	bool UnlockConsumer()
	{
		int _nError = pthread_mutex_unlock( &ConsumerLock );
		if ( _nError )
		{
			cout << "error in unlock consumer. return : " << _nError << ", err :" << strerror(errno) << endl;
			formatlog( LOG_CRIT, "error in unlock consumer. return(%d) ErrNo(%d) ErrMsg(%s)", _nError, errno, strerror(errno));
			return false;
		}

		return true;
	}


public:
	CPCQueue(){}
	virtual ~CPCQueue()
	{
		for ( class vector<T*>::iterator _iter = ProducerQueue.begin(); _iter != ProducerQueue.end(); _iter++)
			delete *_iter;

		for ( class vector<T*>::iterator _iter = ConsumerQueue.begin(); _iter != ConsumerQueue.end(); _iter++)
			delete *_iter;

		ProducerQueue.clear();
		ConsumerQueue.clear();
	}

	bool Init()
	{
		int _ErrorNo = pthread_mutex_init( &ProducerLock, NULL);
		if (_ErrorNo)
			return false;
	
		_ErrorNo = pthread_mutex_init( &ConsumerLock, NULL);
		if (_ErrorNo)
			return false;
	
		return true;
	}

	// Product에 하나를 추가한다
	bool AddProduct( T* _Job )
	{
		if ( false == LockProducer())
		{
			// lock 실패
			formatlog( LOG_CRIT, "CPCQueue::%s(%d) Lock Fail. fatal issue.", __func__, __LINE__);
			return false;
		}

		ProducerQueue.push_back(_Job);
	 
		if ( false == UnlockProducer())
		{
			// unlock 실패
			formatlog( LOG_CRIT, "CPCQueue::%s(%d) Unlock Fail. fatal issue.", __func__, __LINE__);
			return false;
		}

		return true;
	}

	// Consumer 쪽에서 하나씩 빼낸다.
	bool PopFromConsumer(T** _Value)
	{
		*_Value = NULL;

		if ( false == LockConsumer())
		{
			// lock 실패
			formatlog( LOG_CRIT, "CPCQueue::%s(%d) Lock Fail. fatal issue.", __func__, __LINE__);
			return false;
		}

		if (!ConsumerQueue.empty())
		{
			*_Value = ConsumerQueue.front();
			ConsumerQueue.erase(ConsumerQueue.begin());
		}

		if ( false == UnlockConsumer())
		{
			// unlock 실패
			formatlog( LOG_CRIT, "CPCQueue::%s(%d) Unlock Fail. fatal issue.", __func__, __LINE__);
			return false;
		}

		return true;
	}

	// 합할 것이 하나도 없는 경우 false를 반환한다
	bool MoveProducerToConsumer(int& _MoveCount)
	{
		if ( false == LockConsumer())
		{
			// lock 실패
			formatlog( LOG_CRIT, "CPCQueue::%s(%d) Lock Fail. fatal issue.", __func__, __LINE__);
			return false;
		}

		if ( false == LockProducer())
		{
			// lock 실패
			formatlog( LOG_CRIT, "CPCQueue::%s(%d) Lock Fail. fatal issue.", __func__, __LINE__);
			return false;
		}

		_MoveCount = ProducerQueue.size();
		if (!ProducerQueue.empty())
		{
			for ( class vector<T*>::const_iterator _iter = ProducerQueue.begin(); _iter != ProducerQueue.end(); _iter++)
				ConsumerQueue.push_back(*_iter);
	
			ProducerQueue.clear();
		}

		if ( false == UnlockProducer())
		{
			// unlock 실패
			formatlog( LOG_CRIT, "CPCQueue::%s(%d) Unlock Fail. fatal issue.", __func__, __LINE__);
			return false;
		}

		if ( false == UnlockConsumer())
		{
			// unlock 실패
			formatlog( LOG_CRIT, "CPCQueue::%s(%d) Unlock Fail. fatal issue.", __func__, __LINE__);
			return false;
		}

		return true;
	}

	int GetConsumerSize() const
	{
		return ConsumerQueue.size();
	}

};
//---------------------------------------------------------------------------------------------------------------------