본문 바로가기
OS/Window Lecture

Openssl 을 통한 파일 암호화

by 현이빈이 2008. 8. 22.
반응형

1절. 소개

최근에는 단지 방화벽, IDS 로 침입차단/침입탐지 정도의 수준만이 아닌 중요한 데이타의 암호화까지를 이용해서 보안수준을 높이고자 노력하고 있다. 이글은 데이타와 암호화와 관련된 많은 방법중 쉽게 사용할수 있을만한 방법을 제안한다.


2절. OpenSSL

우리는 데이타 암호화를 위해서 가장 널리사용된다고 믿어지고 있는 SSL의 Open 버젼인 OpenSSL 라이브러리를 이용할것이다. 이 라이브러리는 Apache, PGP, SMTP 서버, POP 서버 등 데이타를 교환하는 많은 인터넷 서비스 프로그램에서 사용되어지고 있으며, 로컬데이타(파일) 암호화를 위한 도구로도 널리 사용되고 있다.

OpenSSL 을 사용하는 가장유명한 어플리케이션은 SSH 와 https 를 지원하는 웹브라우저와 웹서버가 될것이다(Opera, Mozilla, explore, lynx 등 대부분의 웹브라우저와 Apache, IIS 웹서버).

이번장에서는 SSL 에 대한 기본적인 개념과 사용방법에 대해서 알아볼것이다.


2.1절. SSL 에 대해서

SSL 은 Secure Sockets Layer 의 줄임말이며, 네트웍 소켓의 데이타 입출력을 암호화 하기 위한 (프로그램)계층(Layer) 이다. SSL 은 넷스케이프 브라우저로 유명한 Netscape 회사에서 만들었으며 산업표준으로 사용되어 지고 있다.

SSL 을 만든이유는 기존의 TCP/IP 가 데이타암호화를 위한 어떠한 방법도 제공해주지 않기 때문으로, 네트웍내에서의 메시지 전송을 안전하게 하기 위한 용도로 만들어졌다. SSL TCP/IP 가 제공하지 않는 데이타 암호화를 지원하기 위해서 다음과 같이 Socket Layer 와 NetWork 계층사에 놓이며, Socket Layer 를 통해 전달된 데이타를 암호화 해서 NetWork 계층에 넘겨주며, Network 계층을 통해서 전달된 암호화 데이타를 복호화 해서 Socket Layer 로 전달해주는 구조를 가진다.

그림 1. SSL 계층구조

SSL 은 전자서명에 널리사용되는 암호화 알고리즘인 RSA 를 주 알고리즘으로 사용한다. RSA 는 암호화와 복호화를 위해서 개인키공개키 를 사용한다. 간단히 말해서 암호화에 사용하는 키와 복호화에 사용되는 키가 분리되어 있는 알고리즘이다.

개인키는 말그대로 개인이 유일하게 가지고 있는키이며 이 키를 이용해서 데이타를 암호화 시킨다. 암호화된 데이타를 복호화 하기 위해서는 공개키를 사용해야 하는데, 이 공개키는 모두에게(데이타를 보내고자하는) 공개된다.

  1. 데이타를 수신 하는 사람에게 공개키를 보낸다.

  2. 보내고자 하는데이타를 개인키를 이용해서 암호화 한다.

  3. 암호화된 데이타를 수신자에게 전송한다. 암호화되어 있음으로 다른 사람은 볼수 없다.

  4. 데이타 수신자는 공개키를 이용해서 데이타를 복호화 한다.

메일의 암호화를 위해서 PGP 를 사용해본적이 있다면, 위의 과정을 쉽게 이해할수 있을것이다. PGP 를 사용할경우 데이타 송신자는 자신 데이타 암호화를 위한 개인키와 복호화를 위한 공개키를 생성하고 만들어진 공개키를 요러가지 경로를 통해서 데이타 수신자에게 전달한다(디스크, 혹은 메일, 웹등으로). 그후 데이타를 보낼때는 개인키로 데이타를 암호화 해서 보내고, 받은 측은 공개키를 이용해서 데이타를 복호화 한다.

이상 간단하게 SSL 에 대해서 알아보았는데, SSL 은 매우 방대하며 복잡한 내용임으로 여기에서 모두 다룰수는 없다. 여기에 관한 내용은 openssl.org를 참고하기 바란다.


3절. Openssl 을 통한 암호화

3.1절. blowfish 알고리즘을 이용한 데이타 암호화

이 문서에서는 RSA 암호화 알고리즘대신 blowfish 알고리즘을 사용해서 데이타를 암/복호화 할것이다. Blowfish 는 데이타 암호화및 복호화를 위해서 동일한 키를 사용하는 대칭 알고리즘(symmetric algorithm)을 사용하고 있다.

암/복호화를 위한 다른키를 사용하는 RSA 알고리즘에 비해서 사용하기가 간단하고, 더 빠른 수행능력을 보여준다는 장점을 가진다.


3.2절. 데모프로그램 설계문서

우리는 파일을 암호화해서 저장하는 어플리케이션을 제작할 것이다. 코딩에 들어가기 전에 간단단하게 기능과 원리를 정의하는 설계도(설계도라고 하기엔 부족하지만)를 만들어보도록 하겠다.


3.2.1절. 프로그램 기능설명

  • 프로그램은 key 를 생성하고 생성된 key 를 이용해서 프로그램 인자로 주어지는 파일을 암호화 하게 된다. 암호화된 파일을 복호화 하기 위해서는 key 가 필요하게 되는데, 이 key 는 별도의 파일로 저장하게 된다. 그럼으로 암호화된 파일을 복호화 하기 위해서는 반드시 key 파일이 존재해야만 한다.

  • 암호화된 파일의 가장 앞부분에는 암호화된 파일의 정보를 알려주는 헤더가 들어갈것이다. 이것은 이 파일이 우리가 만든 프로그램을 통해서 암호화 되었다는 정보와 함께 버젼정보, 사용한 암호화 알고리즘 종류, key 를 제작한사람 등에 관한 정보가 들어간다.

  • 파일에 이처럼 다양한 부가정보를 추가하는 이유는 확장이 필요한 경우가 생길수 있기 때문이다. 예를들어 지금은 단지 blowfish 알고리즘만을 사용하고 있지만 다른 암호화알고리즘을 지원하도록 추가할수도 있기 때문이다.

  • 또한 어플리케이션은 동적라이브러리를 지원하도록 만들어진다. 이유는 다른 암호화 알고리즘이 추가되었을경우 코드를 다시 컴파일하는 불편함을 줄이기 위해서이다.


3.2.2절. Key 생성하기

fcrypt 에서는 암/복호화를 위한 키의 크기를 128bit 로 하겠다. 만들어진 키를 이용해서 암호화를 한후 별도의 파일로 저장될것이다. 이유는 나중에 복호화 할때도 동일한 키를 사용해야 하기 때문이다. 복호화 할때는 존재하고 있는 키 파일에서 파일에 대한 키를 얻어오고 이 데이타를 이용해서 복호화 할것이다.

키의 생성은 커널난수 생성기인 /dev/random 문자장치파일을 이용할것이다.


3.3절. 코딩

프로그램의 이름은 fcrypt 로 할것이다. 코드는 크개 2개의 부분으로 이루어지는데, 하나는 fcrypt.c 로써 실제 사용자에게 제공되는 프로그램이며, 다른 하나는 bolwfish.c 로써 암호화기능을 제공하는 프로그램이다.

blowfish.c 는 main 함수를 제공하지 않으며, 동적라이브러리형태로 제작될 것이며, fcrypt 에서는 이 라이브러리를 적재해서 파일을 암호화 하게 될것이다.


3.3.1절. 동적라이브러리 제작을 위한 API 인터페이스

동적라이브러리 형태로 만드는 이유는 사용자이 필요에 따라서 다양한 암호화 알고리즘을 적용시킬수 있도록 유연성을 높이기 위함이다. 일종의 Plug in 형태로 작동하게 된다.

이처럼 본체프로그램과 기능추가를 위한 Plug in 이 따로 분리 되어서 제작될경우에 Plug in 의 제작을 위한 공통 인터페이스를 제공하게 된다. 그래야만 본체프로그램 코드의 수정없이 단지 plug in 라이브러리만 추가함으로써, 기능을 확장시킬수 있기 때문이며, 그러기 위해서는 공통된 인터페이스에 따라서 Plug in 라이브러리를 제작해야 한다.

동적라이브러리는 반드시 아래와 같은 4가지의 인터페이스를 (정확히 말하자면 함수선언)을 이용해서 프로그래밍 되어야한다. 본체 프로그램은 단지 이 4가지 함수들만을 호출해서 모든 작업을 할수 있도록 라이브러리 제작자는 신경을 써야한다.

int crypt_open(); int mycrypt(char *filename, cryptinfo header); int mydecrypt(char *filename, cryptinfo header, char *keyfile); int crypt_close();
crypt_open()과 crypt_close() 는 라이브러리 개발자의 편의를 위해서 제공되는 인터페이스다. 예를들자면 특정 암호화알고리즘을 적용해서 라이브러리를 만들경우 여러가지 초기화작업이 필요한 경우가 있을것이다. 메모리 할당, 초기화, 메모리 해제 등과 같은 작업이 대표적일 것이다. crypt_open() 은 이러한 초기화 작업을 위해서 사용되며, crypt_close() 는 메모리 해제와 같은 작업을 위해서 사용된다.

mycrypt() 는 암호화를 위한 함수이다. filename 은 암호화 하고자 하는 파일이름이며 header는 헤더정보를 입력받기 위해서 사용되는 아규먼트이다.

mydecrypt() 는 복호화를 위한 함수이다. filename 은 복호화 하고자 하는 파일이름 이며, header 는 헤더정보, keyfile 은 복호화 하기 위해서 사용하는 키가 저장된 파일의 이름이다. header 이 인자로 넘겨지는 이유는 복호화 하고자 하는 파일의 버젼체크등을 위한 목적이다. 암호파일의 버젼과 본체프로그램의 버젼이 전혀 틀릴경우 (메이져 버젼에서 차이가 생길경우) 복호화가 제대로 되지 않을수 있음으로 이를 체크하기 위한 목적이다.

그럼으로 main() 함수를 포함하는 본체 프로그램은 단지 다음과 같이 함수를 호출함으로써 모든 작업을 마치게 되며, 암호/복호화 알고리즘 내부에서 어떤 일을 하는지 신경쓰지 않아도 된다. 일종의 캡슐화라고 볼수 있다.

main() { 아규먼트 처리.. ... 아규먼트를 분석해서 어떤 알고리즘을 원하는지 확인한다. plugin 설정파일에서 해당 알고리즘을 위한 라이브러리 이름을 얻어온다. 라이브러리를 로드 해서 라이브러리에 대한 handler 를 만든다. crypt_open() 를 호출한다. 만약 암호화일경우 mycrypt() 를 호출한다. 복호화일경우 mydecrypt() 를 호출한다. crypt_close()를 호출한다. 로드한 라이브러리를 close 한다. ... }


3.3.2절. fcrypt.c

이 코드는 아규먼트로 암/복호화 할 파일의 이름을 받아들인다. 암호화 할것인지 복호화 할것인지는 역시 아규먼트를 통해서 지정할수 있도록 할것이다. 역시 아규먼트를 통하여서 암호화방법을 지정할수 있으며, 아규먼트로 지정된 암호화 방법에 따라서 어플리케이션은 적당한 라이브러리를 동적으로 적재해서 암호화 혹은 복호화 하게 된다.

이 프로그램은 암호화종류에 따른 라이브러리를 동적으로 적재하기 위해서 설정파일을 가지며, 아규먼트를 통해서 선택된 암호화방법에 적당한 라이브러리를 찾기 위한 목적으로 사용된다. 설정파일은 다음과 같이 구성된다. 설정파일의 이름은 plugin.cfg 로 하겠다.

blowfish,libblowfish,blowfish 알고리즘 mycrypt,libmycrypt,사용자 정의 암호화 알고리즘 null,libnull,null function 알고리즘
3개의 필드를 가진다. 첫번째 필드에는 암호화이름 두번째 필드는 불러들일 라이브러리 이름, 세번째 필드는 간단한 설명이다.

암호화할때는 파일의 가장앞에 암호화된 파일의 정보구조체를 집어 넣을것이다. 이 구조체는 다음과 같다.

struct cryptinfo { char progname[8]; // 프로그램이름 short int maversion; // 메이저 버젼 short int miversion; // 마이너 버젼 char crypttype[80]; // 암호화타입 char maker[20]; // 만든사람정보 };

다음은 실제코드이며, 설명은 주석으로 대신하도록 한다.

예제 : fcrypt.c

#include <dlfcn.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/stat.h> #include <sys/types.h> #include <fcntl.h> #define CRYPT 1 #define DECRYPT 2 // 프로그램의 메이저 버젼 #define MAVERSION 1 // 프로그램의 마이너 버젼 즉 1.o #define MIVERSION 0 // 프로그램 이름 #define PROGNAME "fcrypt" // 읽어들일 플러그인 파일 #define CFGFILE "plugin.cfg" // 암호화된 파일의 가장 앞부분에 저장될 // 헤더정보 파일 typedef struct _cryptinfo { // 프로그램 이름 char progname[8]; // 메이저 버젼 short int maversion; // 마이너 버젼 short int miversion; // 암호화 타입 char crypttype[12]; // 암호화한 사람 char maker[20]; } cryptinfo; // 도움말 void help(); // plug in 파일이 있는지 검사 int checkcfgfile(); // 라이브러리를 동적으로 적재한다. void *loadlibrary(char *); // plbu in 파일에서 로드시켜야될 // 라이브러리 이름을 가져온다. int getlibname(char *,char *); // 동적으로 적재된 라이브러리를 해제한다. void closelibrary(void *handle); // 플로그인 목록을 보여준다. int showpluginlist(); // 암호화될 파일에 들어갈 헤더를 만든다. cryptinfo makeheader(char*, char*); int main(int argc, char **argv) { int opt; // 암호화인지 복호화인지 판단. int iscrypt; // 플러그인 파일이름 char cfgfile[80]; // 암호화시킬 파일이름 char filename[80]; // 암호화 종료 char crypttype[80]; int filenum; char buf[256]; // 키파일 이름 char keyfile[80]; FILE *fp; // 동적라이브러리 핸들러 void *handle; // 핸들러를 통해서 읽어들일 함수포인터들 int (*crypt_open)(); int (*crypt_close)(); int (*encrypt)(char *, cryptinfo); int (*decrypt)(char *, cryptinfo, char *); int isfileinfo = 0; char *error; memset(keyfile, 0x00, 80); // 아규먼트를 읽어들여서 // 값을 세팅한다. while((opt = getopt(argc, argv, "lit:hcdk:")) != -1) { switch(opt) { // 플러그인 리스트를 보여준다. case 'l': showpluginlist(); return 1; break; // 암호화된 파일정보를 보여줄것인지.. case 'i': isfileinfo = 1; break; // 암호화 방법 가져오기 case 't': strncpy(crypttype, optarg,80); break; // 도움말 보여주기 case 'h': help(); return 0; break; // 암호화 할때 case 'c': iscrypt = CRYPT; break; // 복호화 할때 case 'd': iscrypt = DECRYPT; break; // 키파일 이름 얻어온다. case 'k': strncpy(keyfile, optarg, 80); break; // 암호화 시킬 파일 default: printf("파일 : %s\n", optarg); break; } } // 복호화를 위해서는 반드시 key 파일이 // 지정되어 있어야 한다. if (iscrypt == DECRYPT && strlen(keyfile) == 0) { printf("KEY file can not find\n"); help(); return 1; } // 압축해야될 파일이름을 얻어온다. // 반드시 하나의 파일이름이어여 한다. // 파일 목록을 지원하도록 하도 싶다면 // 각자 수정해 보기 바란다. filenum = argc - optind; if (filenum != 1) { printf("only one file\n"); return 1; } strncpy(filename, argv[optind],80); // 암호화된 파일의 정보를 얻어온다. if (isfileinfo) { fileinfo(filename); return 0; } // ----------------------------------------------- // 실제 암호화 루틴 // 암호화혹은 복호화할 파일을 열고 // 암호화타입에 의하여 해당 라이브러리를 링크해서 // iscrypt 값에 따라서 1이면 암호화 0이면 복호화 // 한다. // ----------------------------------------------- handle = loadlibrary(crypttype); if (handle == NULL) { fprintf(stderr, "library load error\n"); exit(0); } crypt_open = dlsym(handle, "crypt_open"); if ((error = dlerror()) != NULL) { fprintf(stderr, "%s\n", error); exit(0); } if (crypt_open() == -1) { fprintf(stderr, "crypt_open error\n"); exit(0); } // 만약 암호화 이라면 if (iscrypt == CRYPT) { encrypt = dlsym(handle, "mycrypt"); if ((error = dlerror()) != NULL) { fprintf(stderr, "%s\n", error); exit(0); } // makeheader 를 이용해서 암호화된 파일에 들어갈 // 헤더를 만든다. if (encrypt(filename,makeheader("yundream", crypttype)) == -1) { printf("encrypt error\n"); exit(0); } printf("ok encrypt\n"); } // 만약 복호화 이라면 else if (iscrypt == DECRYPT) { decrypt = dlsym(handle, "mydecrypt"); if ((error = dlerror()) != NULL) { fprintf(stderr, "%s\n", error); exit(0); } if(decrypt(filename,makeheader("yundream",crypttype), keyfile) == -1) { printf("decrypt error\n"); exit(0); } } crypt_close = dlsym(handle, "crypt_close"); if ((error = dlerror()) != NULL) { fprintf(stderr, "%s\n", error); exit(0); } crypt_close(); // 동적라이브러리 핸들러 닫기 closelibrary(handle); return 1; } // 도움말 void help() { printf("Usage\n"); printf("encrypt : fcrypt -c crypt.c -t blowfish\n"); printf("decrypt : fcrypt -d crypt.c.cry -t blowfish -k crypt.c.key\n"); printf("info : fcrypt -i crypt.c.cry\n"); printf("plugin list : fcrypt -l\n"); } // plugin 파일을 확인한다. int checkcfgfile() { if (access(CFGFILE, F_OK) == 0) { return 1; } return -1; } // 플러그인 파일로 부터 // 로드해야할 라이브러리이름을 얻어온다. // 리턴값으로 동적라이브러리에 대한 핸들값을 // 되돌려준다. void *loadlibrary(char *crypttype) { void *handle; char *libname; libname = (char *)malloc(80); if (getlibname(libname, crypttype) == -1) { free(libname); return NULL; } handle = dlopen(libname, RTLD_NOW); if (!handle) { printf("%s\n", dlerror()); return NULL; } free(libname); return handle; } // 플러그인 설정파일을 읽어들이고 // 파싱해서 해당 라이브러리 이름을 돌려준다. int getlibname(char *libname, char *acrypttype) { FILE *fp; char buf[80]; char null[2]; char crypttype[80]; char uselib[80]; char desc[80]; fp = fopen(CFGFILE, "r"); if (fp == NULL) { printf("cannot open cfg file : %s\n", CFGFILE); return -1; } while(fgets(buf, 80, fp)!=NULL) { sscanf(buf, "%[^,]%[,]%[^,]%[,]%[^\n]", crypttype, null, uselib, null, desc); if (strcmp(crypttype,acrypttype) == 0) { snprintf(libname, 80, "plugin/%s", uselib); return 1; } } fclose(fp); printf("cannot used crypt type %s\n", acrypttype); return -1; } // 동적 라이브러리 핸들러를 닫는다. void closelibrary(void *handle) { dlclose(handle); } // 헤더정보를 만든다. cryptinfo makeheader(char *maker, char *crypttype) { cryptinfo header; memset((void *)&header, 0x00, sizeof(header)); strncpy(header.progname, PROGNAME, 8); strncpy(header.crypttype, crypttype, 12); strncpy(header.maker, maker, 20); header.maversion = MAVERSION; header.miversion = MIVERSION; return header; } // 암호화된 파일에 대한 정보를 얻어온다. // 정보는 헤더파일을 분석해서 가져온다. int fileinfo(char *filename) { int fd; int n; cryptinfo header; if ((fd = open(filename, O_RDONLY)) < 0) { fprintf(stderr, "%s not found\n", filename); return -1; } n = read(fd, (void *)&header,sizeof(header)); // fcrypt 에서 지원하는 암호화된 파일인지를 // 확인한다. if (n < sizeof(header) || (strcmp(header.progname, PROGNAME) != 0)) { fprintf(stderr, "Unknown file format\n"); return -1; } printf("Version : %d.%d\n", header.maversion, header.miversion); printf("crypttype : %s\n", header.crypttype); close(fd); return 1; } // 플러그인 설정파일을 분석해서 // 지원되는 플러그인 목록을 보여준다. int showpluginlist() { FILE *fp; char buf[80]; char null[2]; char crypttype[80]; char uselib[80]; char desc[80]; fp = fopen(CFGFILE,"r"); if (fp == NULL) { printf("Cannot open cfgfile : %s\n", CFGFILE); return -1; } while(fgets(buf, 80, fp)!=NULL) { sscanf(buf, "%[^,]%[,]%[^,]%[,]%[^\n]", crypttype, null, uselib, null, desc); printf("%-20s%-20s%-20s\n", crypttype, uselib, desc); } fclose(fp); return 1; }

fcrypt.c 는 다음과 같이 컴파일하면 된다.

[root@localhost crypt2]# gcc -o fcrypt fcrypt.c -ldl -lcrypto


3.3.3절. blowfish.c

이것은 fcrypt 프로그램에서 동적으로 적재할 플러그인 프로그램으로 blowfish 알고리즘을 통해서 주어진 파일을 암호화 하고 복호화 한다.

blowfish 는 앞에서 말했듯이 암호화에 사용된 key 를 가지고 복호화를 하게 된다. 당연히 이러한 key 데이타를 파일로 저장하고 있어야 할것이다.

그럼으로 blowfish 는 파일을 암호화 하게 될경우 암호화된 파일과 암호화에 사용된 key 를 저장하는 파일 이렇게 2개의 파일을 생성한다. 암호화된 파일은 원본파일뒤에 ".cry" 를 붙이고, key 파일은 원본파일 뒤에 ".key"를 붙이도록 해서 생성하도록 할것이다.

key 사이즈는 128 bit 로 할것이다. 이 blowfish 는 openssl 의 crypto 라이브러리에서 제공한다. 그리고 랜덤한 key 를 생성하기 위해서 /dev/random 커널 난수 생성기를 이용할것이다. 커널 난수 생성기에 대해서는 random 값 얻어오기를 참고하기 바란다.

crypto 에서 제공하는 blofish 암호화 알고리즘에는 key 외에도 initalization vector 가 필요하다. 이것의 크기는 64bit 로 할것이며, 마찬가지로 /dev/random 을 이용해서 랜덤하게 생걱할것이다. 고로 실제 key 파일에는 key 값과 initalization vector 값이 저장될 것이다.

그리고 Cipher Block Chaining(CBC) 모드 상태에서 암/복호화를 하게 될것이다.

예제 : blowfish.c

#include <unistd.h> #include <stdlib.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/types.h> #include <string.h> #include <openssl/evp.h> #define IP_SIZE 256 #define OP_SIZE (256+EVP_MAX_BLOCK_LENGTH) // 128bit key 와 // 64bit initalization vector 를 // 저장하기 위한 구조체 typedef struct _keybuf { char key[16]; char vec[8]; } keybuf; // 암호화된 파일에 들어갈 헤더 정보 typedef struct _cryptinfo { char progname[8]; short int maversion; short int miversion; char crypttype[12]; char maker[20]; } cryptinfo; keybuf mykey; // 인터페이스를 통일시키기 위한 // 공통 함수 // 필요에 따라서 각종초기화및 메모리 할당 // 관련 코드를 넣을수 있다. int crypt_open() { return 1; } // /dev/random 을 이용해서 // key 와 initalization vector 에 들어갈 값을 // 생성한다. int keygen() { int fd; if ((fd = open("/dev/random", O_RDONLY)) == -1) { perror("open error"); return -1; } if ((read(fd, mykey.key, 16)) == -1) { perror("read key error"); return -1; } if ((read(fd, mykey.vec, 8)) == -1) { perror("read key error"); return -1; } close (fd); return 1; } // 복호화 함수 // filename 을 암호화하며, // header 를 암호화된 파일 가장앞에 write 한다. // 또한 복호화에 사용될 keyfile 을 지정해준다. int mydecrypt(char *filename, cryptinfo header, char *keyfile) { int fd; char inbuf[IP_SIZE]; char outbuf[OP_SIZE]; int n, olen; int ecode; cryptinfo localheader; EVP_CIPHER_CTX ctx; getkeyvalue(keyfile); EVP_CIPHER_CTX_init (&ctx); EVP_DecryptInit(&ctx, EVP_bf_cbc(), mykey.key, mykey.vec); // 암호화된 파일을 오픈한다. if ((fd = open(filename, O_RDONLY)) == -1) { perror("debug error"); return -1; } // 헤더정보를 읽어들인다. if (read(fd, (void *)&localheader, sizeof(cryptinfo)) == -1) { perror("Header read error\n"); return -1; } // 헤더정보를 토대로 버젼체크와 // 암호화 종류를 체크한다. if ( (ecode = checkcryptfile(header, localheader)) == -1) { return ecode; } // 파일 내용을 읽어들여서 // 복호화 한다. while(1) { memset(inbuf, 0x00, IP_SIZE); if ((n = read(fd, inbuf, IP_SIZE)) == -1) { perror("read error"); return -1; } else if (n == 0) { break; } memset(outbuf, 0x00, OP_SIZE); if (EVP_DecryptUpdate(&ctx, outbuf, &olen, inbuf, n) != 1) { printf("error in decrypt\n"); return -1; } // 복호화한 내용은 화면 출력시킨다. write(1, outbuf, olen); } if (EVP_DecryptFinal(&ctx, outbuf, &olen) != 1) { printf("error in decrypt final\n"); return -1; } write(1, outbuf, olen); close(fd); EVP_CIPHER_CTX_cleanup (& ctx); } // key 파일을 만든다. int makekeyfile(char *filename) { int fd; if((fd = open(filename, O_WRONLY|O_CREAT)) == -1) { return -1; } write(fd, (void *)&mykey, sizeof(mykey)); close(fd); } // key 파일로 부터 // key 정보를 얻어온다. int getkeyvalue(char *filename) { int fd; if ((fd = open(filename, O_RDONLY)) == -1) { return -1; } read (fd, (void *)&mykey, sizeof(mykey)); close(fd); } // 암호화에 사용되는 함수 // filename 파일을 암호화 한다. // 암호화된 파일의 가장 앞에는 header 정보를 붙인다. int mycrypt(char *filename, cryptinfo header) { char inbuf[IP_SIZE]; char outbuf[OP_SIZE]; int olen, n; int outfd; int fd; char defile[80]; char keyfile[80]; EVP_CIPHER_CTX ctx; // 암호화파일이름 snprintf(defile, 80, "%s.cry", filename); // key 파일 이름 snprintf(keyfile, 80, "%s.key", filename); keygen(); // key 파일을 생성한다. makekeyfile(keyfile); EVP_CIPHER_CTX_init (&ctx); EVP_EncryptInit(&ctx,EVP_bf_cbc(), mykey.key, mykey.vec); // 암호화된 내용이 저장될 파일을 연다. if ((outfd = open(defile, O_WRONLY|O_CREAT)) == -1) { return -1; } // 암호화할 원본 파일을 연다. if ((fd = open(filename, O_RDONLY|O_CREAT)) == -1) { return -1; } // 암호화 파일의 가장 앞부분에 헤더정보를 넣는다. if (write(outfd, (void *)&header, sizeof(header)) == -1) { perror("header write error"); return -1; } // 원본파일로 부터 내용을 읽어서 // 암호화하고 이것을 파일로 저장한다. while(1) { memset(inbuf, 0x00, IP_SIZE); if ((n = read(fd, inbuf, IP_SIZE)) == -1) { perror("read error"); return -1; } else if (n == 0) break; if (EVP_EncryptUpdate(&ctx, outbuf, &olen, inbuf, n) !=1 ) { printf("error in encrypt update\n"); return -1; } if ((n = write(outfd, outbuf, olen)) == -1) { perror("write error"); return -1; } } if (EVP_EncryptFinal(&ctx, outbuf, &olen) != 1) { printf("error in encrypt final\n"); return -1; } if ((n = write(outfd, outbuf, olen)) == -1) { perror("write error"); return -1; } close(outfd); close(fd); EVP_CIPHER_CTX_cleanup (&ctx); return 1; } // 암호화된 파일의 헤더내용을 이용해서 // 버젼 체크와 암호화방법에 대한 체크를 한다. int checkcryptfile(cryptinfo h1, cryptinfo h2) { if (h1.maversion != h2.maversion) { fprintf(stderr, "wrong version\n"); return -1; } if (strcmp(h1.crypttype, h2.crypttype) != 0) { fprintf(stderr, "wrong encrypt type\n"); return -1; } return 1; } int crypt_close() { return 1; }

blowfish.c 는 공유라이브러리 형태로 만들어줘야 한다. 다음과 같이 공유라이브러리 형태로 만들어주자.

[root@localhost crypt2]# gcc -c -fPIC blowfish.c [root@localhost crypt2]# gcc -shared -W1,-soname,libblowfish.so.1 -o libblowfish.so.1.0.1 blowfish.o
이렇게 하면 libblowfish.so.1.0.1 이라는 라이브러 파일이 만들어 진다. 이 파일은 plugin 디렉토리를 만들어서 복사하고 다음과 같이 심볼릭 링크를 걸어주도록 하자.
[root@localhost crypt2]# mkdir plugin [root@localhost crypt2]# cp libblowfish.so.1.0.1 plugin/ [root@localhost crypt2]# cd plugin [root@localhost plugin]# ln -s libblowfish.so.1.0.1 libblowfish.so


3.4절. 테스트

테스트를 하기 위해서는 plugin.cfg 파일이 있어야 하며, plugin 디렉토리 밑에 해당 암호화 알고리즘을 지원하는 라이브러리가 존재하고 있어야 한다. 다음은 필자의 plugin.cfg 의 파일이다.

blowfish,libblowfish.so,blowfish 알고리즘 null,libnull.so,null function 알고리즘
blowfish 는 무엇인지 알테고, null 은 필자가 동적라이브러리 테스트용으로 만든 것으로 말그대로 null 이다. 즉 아무런 암호화 과정없이 그대로 원본내용을 그대로해서 암호화파일을 만든다. 각자 만들어 보기 바란다.

plugin 밑에는 다음과 같은 파일들이 있다.

[root@localhost plugin]# ls libblowfish.so libblowfish.so.1.0.1 libnull.so libnull.so.1.0.1

이제 테스트를 해보자, 테스트 결과의 확인을 쉽게 하기 위해서 text 파일을 테스트에 사용하도록 하자. 필자는 fcrypt.c 를 암호화/복화화 테스트에 사용하도록 했다.

다음은 암호화 테스트로 fcrypt.c 를 blowfish 알고리즘을 사용한 테스트 결과이다.

[root@localhost crypt2]# ./fcrypt -c fcrypt.c -t blowfish ok encrypt
실행결과 암호화된 파일인 fcrypt.c.cry 와 key 가 저장된 fcrypt.c.key 가 생성되었음을 확인할수 있을것이다.

다음은 암호화된 파일의 정보를 얻어오는 테스트이다.

[root@localhost crypt2]# ./fcrypt -i fcrypt.c.cry Version : 1.0 crypttype : blowfish
다음은 fcrypt 가 현재 지원하는 plug in 알고리즘의 목록을 출력한 것이다.
[root@localhost crypt2]# ./fcrypt -l blowfish libblowfish.so blowfish 알고리즘 null libnull.so null function 알고리즘
다음은 복호화 시킨 결과이다.
[root@localhost crypt2]# ./fcrypt -d fcrypt.c.cry -t blowfish -k fcrypt.c.key #include <dlfcn.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/stat.h> #include <sys/types.h> #include <fcntl.h> #define CRYPT 1 #define DECRYPT 2 // 프로그램의 메이저 버젼 ....


4절. 결론

이상 openssl 을 이용한 암호화방법에 대해서 알아보았다. 위의 예제 프로그램들은 수정해야할 여지가 많을것이다.

여기에서는 단지 파일만을 예로 들었지만, 네트웍 프로그래밍등에도 충분히 응용이 가능할것이다. 

반응형