해당 포스팅에 소개한 소스는 누구에게나 공개된 기본적인 샘플 소스를 이용하였습니다.
오늘 포스팅은 오랜만의 제 직업과 관련된 포스팅 입니다. 제 직업과 관련된 프로그래밍 관련 주제는 블로거 초기에 창고 용도로 이용할때 올린 몇가지 공개해도 좋은 기초적인 소스들이 있긴 한데 그 후로는 오랫동안 대부분의 방문자 분들이 흥미없어 하는 이 주제는 거의 다루지 않았습니다.
그럼에도 오랜만에 프로그래밍 관련 글을 포스팅 하는 이유는 개인적인 호기심에 검색을 해보았는데 원하는 해당 결과를 찾기가 어려웠기 때문 입니다. 혹시 저와 동일한 궁금증을 가지고 계시다면 수고를 덜어주고 싶은 마음에 포스팅을 하였습니다. 매우 기초적인 소스와 개념이지만 프로그래밍과 연관 없는 대다수의 분들에게는 정말로 재미없고 지루한 포스팅이 될 듯해서 이번 글은 그냥 PASS 하셔도 무방합니다.
제가 가졌던 호기심은 "암호화 과정이 느리다는 상식 정도만 알고 있던 공개키 암호화는 실제로 얼마나 느릴까?" 하는 호기심이었습니다. 암호화에 조금이라도 관련한 일을 해 보셨다면 아시겠지만 공개키 암호화는 느린 암호화, 복호화 속도 때문에 대부분 데이터를 암호화 하는데 쓰여지기 보다는 좀더 속도가 빠른 대칭키 암호화 방식의 비교적 짧은 길이의 키를 암호화 해서 전달하는 키 교환에 많이 쓰입니다. 그런데 이리 저리 구글 검색을 해 봐도 느리다는 이야기는 있었지만 얼마나 시간이 걸렸는지를 예를 들어주는 글을 찾지는 못했습니다. 이런 경우에는 호기심을 풀기 위해서는 직접 해보는 수 밖에 없습니다.
공개키 암호화 예시에 자주 등장하는 밥 과 앨리스
이미지 출처 : http://blog.naver.com/kookh1/120186150477
공개키 암호화는 대개 소인수 분해라는 수학적으로 복잡한 계산을 통해 암호 풀기의 어려움을 구현하였습니다. 공개키와 개인키라는 키쌍으로, 공개된 키로 암호화 한 데이터는 공개하지 않은 개인키로만 풀수가 있습니다. 테스트에 사용된 RSA 암화화의 경우 개인키의 암호를 해독하려면 슈퍼컴퓨터로도 1만년의 시간이 필요 합니다. 하지만 요즘의 전 세계 PC의 유휴자원을 이용하는 클라우드 컴퓨팅과 PC 성능의 발전, 양자 컴퓨터 등의 등장 예상 등, 컴퓨팅 파워의 발전으로 인해서 어쩌면 근 미래에는 금방 풀어낼 수 있게 될지도 모릅니다. 그러나 현재까지는 여전히 쉽게 풀리지 않는 암호화 기법의 하나 입니다.
대표적인 공개키 암호화 기법인 RSA에 대해 상식 선에서 궁금하시다면 네이버 지식백과의 간략한 정의 내용(여기)을 참조하시기 바랍니다.
앞서 이야기 했듯 이 암호화 방식은 실시간으로 데이터를 교환하는 과정에 적용하기에는 너무 느리기에 보다 덜 안전하지만 속도가 빠른 다른 대칭키 암호화 방식의 키를 암호화 하는데 주로 이용됩니다. "그럼 도대체 얼마나 느리기에 그럴까?"가 제 호기심이었습니다.
먼저 테스트한 환경은 IBM AIX 의 서버 환경과 클라이언트의 경우 펌웨어를 이용하는 매우 낮은 사양의 단말기기 였습니다. 직무 윤리상 각각의 사양은 아주 사소한 것이지만 공개하지 못함을 양해해 주시기 바랍니다. 서버는 일반적인 UNIX서버, 클라이언트는 단순 기능 용도에 이용되는 매우 저사양의 기기로만 이해 하시면 될듯 합니다.
테스트는 OpenSSL에 포함되어 있는 RSA 라이브러리를 이용하였습니다. OpenSSL 버전은 0.9.7l 입니다. 아래의 소스는 사실 간단한 궁금증 해결을 위해서 대충 대충 급하게 인터넷에 공개된 윈도우용 샘플 소스를 UNIX에 맞게 수정하고 시간을 출력하는 기능만 추가하였으므로 소스의 완성도는 양해해 주시기 바랍니다. RSA키는 2048bit 키를 이용하였습니다. 테스트에 사용된 RSA 샘플 소스와 라이브러리는 OpenSSL에 공개되어 있는 라이브러리와 소스임을 다시 한번 참고하시기 바랍니다.
#include <sys/time.h>
#include <time.h>
#include <ctype.h>
#include<stdio.h>
#include<sys/timeb.h>
#include "openssl/ssl.h"
#include "openssl/rsa.h"
#include "openssl/rand.h"
typedef struct {
int year;
int month;
int day;
int hour;
int minute;
int sec;
int msec;
int wday;
int yday;
} TIME_TAB;
TIME_TAB sTime;
void Set_Date (char *sDate);
int main()
{
char sDate[21+1];
RSA *keyA, *keyB, *pkeyA, *pkeyB;
unsigned char cipher_text[4096];
unsigned char plain_text_sender[]="This is RSA test Program 37Byte Text!";
unsigned char plain_text_receiver[4096];
unsigned int num;
int pkA_len, pkB_len;
unsigned char *pkA, *temp_pkA;
unsigned char *pkB, *temp_pkB;
double duration;
time_t start, finish, now;
int i = 0;
// A의 RSA키 생성
Set_Date(sDate);
printf("A 키 생성 시작시간 %s \n", sDate);
RAND_status(); // 랜덤 시드 생성
keyA=RSA_new(); // key 객체 생성
keyA=RSA_generate_key(2048,3,
NULL,NULL); // key 생성
if(RSA_check_key(keyA)==1) // key 유효성 확인
Set_Date(sDate);
printf("A 키 생성 종료 시간 %s \n", sDate);
// B의 RSA키 생성
Set_Date(sDate);
printf("B 키 생성 시작시간 %s \n", sDate);
RAND_status(); // 랜덤 시드 생성
keyB=RSA_new(); // key 객체 생성
keyB=RSA_generate_key(2048,3,
NULL,NULL); // key 생성
if(RSA_check_key(keyB)==1) // key 유효성 확인
Set_Date(sDate);
printf("B 키 생성 종료 시간 %s \n", sDate);
// 상대방에게 전송할 공개키 추출
// A의 공개키 추출
Set_Date(sDate);
printf("A 공개키 추출 시작시간 %s\n", sDate);
pkA = (unsigned char *)malloc(2048);
memset(pkA, 0, 2048);
temp_pkA = pkA;
pkA_len=i2d_RSAPublicKey(keyA, &pkA); // 공개키 추출
pkA = temp_pkA;
Set_Date(sDate);
printf("A 공개키 추출 완료시간 %s\n", sDate);
// B의 공개키 추출
Set_Date(sDate);
printf("B 공개키 추출 시작시간 %s\n", sDate);
pkB = (unsigned char *)malloc(2048);
memset(pkB, 0, 2048);
temp_pkB = pkB;
pkB_len=i2d_RSAPublicKey(keyB, &pkB); // 공개키 추출
pkB = temp_pkB;
Set_Date(sDate);
printf("B 공개키 추출 완료 시간 %s\n", sDate);
printf("B공개키 길이 : %d \n", pkB_len );
printf("공개키 교환했다고 간주!\n");
// 공개키는 서로 교환한 것으로 간주
// A -> B로 데이터를 암호화하여 보내는 경우
// B의 공개키 등록
// 암호화 시간 측정
Set_Date(sDate);
printf("B 공개키로 암호화 시작시간 %s A plaintext=%s \n", sDate, plain_text_sender);
pkeyB = d2i_RSAPublicKey(NULL, (const unsigned char **)&pkB, (long)pkB_len);
for (i=0;i<sizeof(RSA);i++)
{
printf("%02X", pkeyB[i]);
}
printf("\n");
// B의 공개키로 데이터 암호화
num=RSA_public_encrypt(sizeof(
plain_text_sender)-1, plain_text_sender, cipher_text, pkeyB, RSA_PKCS1_PADDING);
// 암호화 시간 측정
Set_Date(sDate);
printf("암호화 완료 시간 %s \n", sDate);
// B가 암호화된 데이터를 받은 것으로 간주
// B는 자신의 비밀키로 복호화
Set_Date(sDate);
printf("B 개인키로 복호화 시작 시간 %s \n", sDate);
num=RSA_private_decrypt(num, cipher_text, plain_text_receiver, keyB, RSA_PKCS1_PADDING);
plain_text_receiver[num]='\0';
Set_Date(sDate);
printf("복호화 완료 시간 %s plaintext=%s \n",sDate, plain_text_receiver);
memset(cipher_text, 0, 256);
memset(plain_text_receiver, 0, 256);
// B -> A로 데이터를 암호화하여 보내는 경우
// A의 공개키 등록
pkeyA = d2i_RSAPublicKey(NULL, (const unsigned char **)&pkA, (long)pkA_len);
// B -> A, A의 공개키로 데이터 암호화
printf("plaintext=%s\n",plain_
text_sender);
num=RSA_public_encrypt(sizeof(
plain_text_sender)-1, plain_text_sender, cipher_text, pkeyA, RSA_PKCS1_PADDING);
// A가 암호화된 데이터를 받은 것으로 간주
// A는 자신의 비밀키로 복호화
num=RSA_private_decrypt(num, cipher_text, plain_text_receiver, keyA, RSA_PKCS1_PADDING);
plain_text_receiver[num]='\0';
printf("plaintext=%s\n",plain_
text_receiver);
RSA_free(keyA);
RSA_free(keyB);
RSA_free(pkeyA);
RSA_free(pkeyB);
//free(pkA);
//free(pkB);
}
/*----------------------------
------------------------------ -----------------*/ /* 일자 구하는 함수
*/ /*----------------------------
------------------------------ -----------------*/ void Set_Date (char *sDate)
{
struct tm *Tm;
struct timeval tp;
struct timezone tzp;
gettimeofday (&tp, &tzp);
Tm = (struct tm *) localtime (&tp.tv_sec);
/* 현재일자에서 하루만큼을 뺌 */
Tm = (struct tm *) localtime (&tp.tv_sec);
sTime.year = Tm->tm_year + 1900;
sTime.month = Tm->tm_mon + 1;
sTime.day = Tm->tm_mday;
sTime.hour = Tm->tm_hour;
sTime.minute = Tm->tm_min;
sTime.sec = Tm->tm_sec;
sTime.msec = (int) (tp.tv_usec / 1000L);
sTime.wday = Tm->tm_wday;
sTime.yday = Tm->tm_yday;
sprintf (sDate,"%02d:%02d:%02d:%03d",
sTime.hour,sTime.minute, sTime.sec,sTime.msec);
}
컴파일시 Makefile에 참조한 SSL 라이브러리는 아래와 같습니다. 소스는 파일로도 첨부하였습니다.
RSALIB = $(SYS_HOME)/libcrypto.a \
$(SYS_HOME)/libssl.a
결론 부터 말하면 서버에서 키 생성의 경우 1초, 암호화에 0.001초, 복화화에 0.016초 정도가 평균적으로 소요 되었습니다. 우리 생활속에서는 이 수치는 작은 수치 같지만 비교적 강력한 성능을 가진 서버에서 수행된것 치고는 상당히 느린 속도 입니다.
문제는 클라이언트 기기 였는데 키 생성은 도저히 시간을 잴 수가 없었습니다. 해당 기기가 거의 멈춰버리다시피 했기 때문입니다. 결국 소스를 수정해서 키가 있는 상태로 암호화만 해봤는데 37바이트 데이터를 암호화만 하는데 무려 1.8초 가 걸렸습니다. 사실 이 정도 속도면 실제 일반적인 비지니스 환경에서는 사용이 불가능할 정도로 느린 속도입니다. 클라이언트에서의 복호화 부분은 테스트를 하지 않았는데 아마도 수십초대의 속도가 예상 됩니다.
더 큰 문제는 다중 처리를 해야하는 성능이 강력한 일반적인 서버에서 조차 엄청난 CPU 자원을 필요로 했다는 점 입니다. 10개 세션을 동시 복호화시 40~50% 정도의 CPU를 점유 하였습니다. 참고로 CPU는 멀티코어 였습니다.
왜 공개키 암호화가 다른 암호화 방식의 키 암호화 정도에만 이용되는지 다시 한 번 깨달은 계기가 되었습니다. 혹시 유사한 호기심을 푸시려는 분이라면 참고가 되었으면 합니다.