본문 바로가기

개발 개발

[OpenSSL 프로그래밍]

출처 : http://funkylocker.tistory.com/ : 각타이틀을 누르면 원작자 페이지로 이동함.

[OpenSSL 프로그래밍] AES 암호화 (EVP)

1. AES 암호화 알고리즘
- 기본적으로 암호화란 평문(plain text)을 암호문(cipher text)으로 바꾸는 작업을 말한다.
- AES 란 Advanced Encryption Standard의 약자로 가장 많이 쓰이는 블럭 암호화 알고리즘으로 128 bit/192 bit/256 bit 의 대칭키를 이용하여 데이터를 암호화 한다. 

2. OpenSSL 라이브러리 제공 (AES 암호화 방식)
  1) EVP 라이브러리
  2) AES 라이브러리

OpenSSL에서는 두 가지 방식의 AES 암복호화 라이브러리를 제공한다. 기본 AES 암호화 API 이외에 EVP 라이브러리가 바로 그것이다. 오늘은 두 방식 중 EVP API를 이용한 AES 암호화 프로그래밍에 대해서 알아 보도록 하자.

3. 암호화 과정
- 모든 암호화 라이브러리는 init, update, final 과정을 거친다. 
- init: 암호화 관련 정보를 설정한다. (암호화 방식, 키 길이, 비밀 번호 등)
- update: 설정한 암호화 방식으로 블럭을 분할해 암호화를 수행한다. 
- final: 입력한 plain text의 크기가 블럭의 배수가 아닐 경우 데이터 끝에 여분의 데이터 바이트가 남게 되는 해당 바이트를 패딩하여 처리가능한 크기의 블럭으로 만든 다음 암호화를 수행한다.


4. 암호화 프로그래밍

- 일단 코드를 보자. 아래는 입력 받은 plain text를 복호화 키를 이용하여 cipher text로 변환하는 함수이다.

#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/ssl.h>

/* AES Encrypt Process */
int encrypt_block(unsigned char* cipherText, unsigned char* plainText, unsigned int plainTextLen, unsigned char* key)
{
    EVP_CIPHER_CTX ctx;
    int addLen=0, orgLen=0;
    unsigned long err=0;
    
    ERR_load_crypto_strings();
    EVP_CIPHER_CTX_init(&ctx);
    if (EVP_EncryptInit(&ctx, EVP_aes_128_ecb(), key, NULL) != 1) {
        err = ERR_get_error();
        printf("ERR: EVP_EncryptInit() - %s\n", ERR_error_string (err, NULL));
        return -1;
     }
    if (EVP_EncryptUpdate(&ctx, cipherText, &orgLen, plainText, plainTextLen) != 1) {
        err = ERR_get_error();
        printf("ERR: EVP_EncryptUpdate() - %s\n", ERR_error_string (err, NULL));
    
        return -1;
    }
 
    if (EVP_EncryptFinal(&ctx, cipherText+orgLen, &addLen) != 1) {
        err = ERR_get_error();
        printf("ERR: EVP_EncryptFinal() - %s\n", ERR_error_string (err, NULL));
        return -1;
    }
    EVP_CIPHER_CTX_cleanup(&ctx);
    ERR_free_strings();
    return addLen + orgLen;
}

- EVP 라이브러리를 사용하기 전에는 먼저 context를 설정해야 하는데.. context 구조체에는 암호화에 관련된 정보가 기록된다. EVP_CIPHER_CTX_init()로 초기화를 수행하고, EVP_CIPHER_CTX_cleanup()을 통해 해제를 수행한다. 
- 블럭 암호화 알고리즘에는 ECB, CBC 등의 모드가 사용되는데 해당 함수에서는 ECB 방식을 사용한다. (모드에 대한 설명은 향후에 시간이 나면.. ^^;;)

- 아래는 AES로 암호화된 cipher text를 plain text로 복호화하는 함수이다.

#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/ssl.h>

/* AES Decrypt Process */
int decrypt_block(unsigned char* plainText, unsigned char* cipherText, unsigned int cipherTextLen, unsigned char* key)
{
    EVP_CIPHER_CTX ctx;
    unsigned long err=0;
    int toLen=0, outLen=0;
    int ret=0;
  
    ERR_load_crypto_strings();
    EVP_CIPHER_CTX_init(&ctx);
  
    if (EVP_DecryptInit(&ctx, EVP_aes_128_ecb(), key, NULL) != 1) {
        err = ERR_get_error();
        printf("ERR: EVP_DecryptInit() - %s\n", ERR_error_string (err, NULL));
        return -1;
    }
    if (EVP_DecryptUpdate(&ctx, plainText, &toLen, cipherText, cipherTextLen) != 1) {
        err = ERR_get_error();  
        printf("ERR: EVP_DecryptUpdate() - %s\n", ERR_error_string (err, NULL));
    
        return -1;
    }
  
    if (EVP_DecryptFinal(&ctx, &plainText[cipherTextLen], &outLen) != 1) {
        err = ERR_get_error();
        printf("ERR: EVP_DecryptFinal() - %s\n", ERR_error_string (err, NULL));
  
        return -1;
    }
  
    EVP_CIPHER_CTX_cleanup(&ctx);
    ERR_free_strings();
  
    return toLen+outLen;
}


1. OpenSSL 라이브러리 제공 (AES 암호화 방식)
  1) EVP 라이브러리
  2) AES 라이브러리

OpenSSL에서는 두 가지 방식의 AES 암복호화 라이브러리를 제공한다. 기본 AES 암호화 API 이외에 EVP 라이브러리가 바로 그것이다. 오늘은 두 방식 중 기본 AP로 제공되는 AES 암호화 프로그래밍에 대해서 알아 보도록 하자.

2. 암호화 프로그래밍
- 아래는 입력 받은 plain text를 암호화 키를 이용하여 cipher text로 변환하는 함수이다.
- AES_set_encrypt_key()로 암호화 키를 AES_KEY 구조체로 변환한 후 암호화를 수행한다.

 #include <openssl/aes.h>

/* AES Encrypt Process */
bool encrypt_block(unsigned char* cipherText, unsigned char* plainText, unsigned char* key)
{
    AES_KEY encKey;
   

    if (AES_set_encrypt_key(key, 128, &encKey) < 0)
        return false;

    AES_encrypt(plainText, cipherText, &encKey);

    return true;
}



- 아래는 입력 받은 cipher text를 복호화 키를 이용하여 plain text로 변환하는 함수이다.
- AES_set_decrypt_key()로 암호화 키를 AES_KEY 구조체로 변환한 후 복호화를 수행한다.

#include <openssl/aes.h>

 /* AES Decrypt Process */
bool decrypt_block(unsigned char* cipherText, unsigned char* plainText, unsigned char* key)
{
    AES_KEY decKey;
    

    if (AES_set_decrypt_key(key, 128, &decKey) < 0)
        return false;

    AES_decrypt(cipherText, plainText, &decKey);

    return true;
}



- 다음은 ase.h 에 정의된 aes 암호화 함수이다. (ecb, ebc 암호화 모드 등을 지원한다.)

void AES_ecb_encrypt(const unsigned char *in, unsigned char *out,
                                 const AES_KEY *key, const int enc);

void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
                                 const unsigned long length, const AES_KEY *key, 
                                 unsigned char *ivec, const int enc);

void AES_cfb128_encrypt(const unsigned char *in, unsigned char *out,
                                    const unsigned long length, const AES_KEY *key,
                                    unsigned char *ivec, int *num, const int enc);

void AES_cfb1_encrypt(const unsigned char *in, unsigned char *out,
                                 const unsigned long length, const AES_KEY *key,
                                 unsigned char *ivec, int *num, const int enc);

void AES_cfb8_encrypt(const unsigned char *in, unsigned char *out,
                                 const unsigned long length, const AES_KEY *key,
                                 unsigned char *ivec, int *num, const int enc);

void AES_cfbr_encrypt_block(const unsigned char *in,unsigned char *out,
                                          const int nbits,const AES_KEY *key,
                                          unsigned char *ivec,const int enc);

void AES_ofb128_encrypt(const unsigned char *in, unsigned char *out,
                                    const unsigned long length, const AES_KEY *key,
                                    unsigned char *ivec, int *num);

void AES_ctr128_encrypt(const unsigned char *in, unsigned char *out,
                                   const unsigned long length, const AES_KEY *key,
                                   unsigned char ivec[AES_BLOCK_SIZE],
                                   unsigned char ecount_buf[AES_BLOCK_SIZE],
                                   unsigned int *num)


1. OpenSSL 이란

Secure Sockets Layer (SSL v2/v3) 와 Transport Layer Security (TLS v1) 프로토콜 뿐만 아니라 보다 강력한 암호화 라이브러리 제공을 위한 오픈 소스 toolkit 이다.

 

현재(2007년) 버전은 OpenSSL 0.9.8e 까지 나와 있는 상태이며, http://openssl.org/source 에서 소스를 받아 설치 할 수 있다.

 

2. 함수 설명

2.1 RSA(3) - RSA public key 암호화시스템

- RSA 란 public key 기반의 암호화 알고리즘으로, 현재 가장 널리 사용되는 암고리즘 중의 하나이다.

 

- RSA 구조체


 struct {
        BIGNUM *n;              // public modulus
        BIGNUM *e;              // public exponent
        BIGNUM *d;              // private exponent
        BIGNUM *p;              // secret prime factor
        BIGNUM *q;              // secret prime factor
        BIGNUM *dmp1;           // d mod (p-1)
        BIGNUM *dmq1;           // d mod (q-1)
        BIGNUM *iqmp;           // q^-1 mod p
        // ...
 } RSA;


RSA 구조체는 BIGNUM components 로 구성되어 있으며, public key 와 private key 모두를 가지고 있다.

 

- 주요 함수 프로토 타입


#include <openssl/rsa.h>
 #include <openssl/engine.h>

 

 // 객체 생성, 삭제

 RSA * RSA_new(void);
 void RSA_free(RSA *rsa);

 

 // RSA 암, 복호화 

 int RSA_public_encrypt(int flen, unsigned char *from, unsigned char *to, RSA *rsa, int padding);

  int RSA_private_decrypt(int flen, unsigned char *from, unsigned char *to, RSA *rsa, int padding);
 int RSA_private_encrypt(int flen, unsigned char *from, unsigned char *to, RSA *rsa,int padding);
 int RSA_public_decrypt(int flen, unsigned char *from, unsigned char *to, RSA *rsa,int padding);

 

 // RSA 서명 및 검증

 int RSA_sign(int type, unsigned char *m, unsigned int m_len, unsigned char *sigret, unsigned int *siglen, RSA *rsa);
 int RSA_verify(int type, unsigned char *m, unsigned int m_len, unsigned char *sigbuf, unsigned int siglen, RSA *rsa);

 

 int RSA_size(const RSA *rsa);

 

  // RSA 키 생성 및 검사

 RSA *RSA_generate_key(int num, unsigned long e, void (*callback)(int,int,void *), void *cb_arg);

 int RSA_check_key(RSA *rsa);

 

 int RSA_blinding_on(RSA *rsa, BN_CTX *ctx);
 void RSA_blinding_off(RSA *rsa);

 

 void RSA_set_default_method(const RSA_METHOD *meth);
 const RSA_METHOD *RSA_get_default_method(void);
 int RSA_set_method(RSA *rsa, const RSA_METHOD *meth);
 const RSA_METHOD *RSA_get_method(const RSA *rsa);
 RSA_METHOD *RSA_PKCS1_SSLeay(void);
 RSA_METHOD *RSA_null_method(void);
 int RSA_flags(const RSA *rsa);
 RSA *RSA_new_method(ENGINE *engine);

 

 // RSA context 출력

 int RSA_print(BIO *bp, RSA *x, int offset);
 int RSA_print_fp(FILE *fp, RSA *x, int offset);

 

 int RSA_get_ex_new_index(long argl, char *argp, int (*new_func)(), int (*dup_func)(), void (*free_func)());
 int RSA_set_ex_data(RSA *r,int idx,char *arg);
 char *RSA_get_ex_data(RSA *r, int idx);

 

 int RSA_sign_ASN1_OCTET_STRING(int dummy, unsigned char *m, unsigned int m_len, unsigned char *sigret, unsigned int *siglen, RSA *rsa);

 int RSA_verify_ASN1_OCTET_STRING(int dummy, unsigned char *m, unsigned int m_len, unsigned char *sigbuf, unsigned int siglen, RSA *rsa);

- 전부 사용할 일도 없거니와 사용하기도 싫으니 별도의 설명은 생략한다. (http://openssl.org 에 document 탭에서 찾아 볼 수 있다.)

 

 

2.2 테스트 프로그램

- RSA 알고리즘을 활용하여 간단한 테스트 프로그램을 작성해 보자.

- 아래 코드는 텍스트를 암, 복호화 하는 코드이다.

 

#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <openssl/rsa.h>
 #include <openssl/evp.h>

 int main(int argc, char *argv[])
 {
        RSA *rsa;
        int len, ret;
        unsigned char *cipherText;
        unsigned char *plainText = "Hello RSA!";
        unsigned char *originText;

       

        rsa = RSA_generate_key(1024, 3, NULL, NULL);

       

        if (RSA_check_key(rsa) != 1)
        {
              perror("not gnerate RSA key");
              exit(0);
         }

       

        cipherText = (unsigned char *) calloc (1, (size_t) RSA_size(rsa));

       

        len = RSA_public_encrypt(strlen(plainText), plainText, cipherText, rsa, RSA_PKCS1_PADDING);

 

        if (len == -1)
       {
              perror("not encrypt data");
              exit(0);
        }

       

        printf("plain text: %s\n", plainText);
        printf("ciper text: %s\n", cipherText);

 

        originText = (unsigned char *) calloc(1, strlen(plainText)+1);
 
        ret = RSA_private_decrypt(len, cipherText, originText, rsa, RSA_PKCS1_PADDING);

 

        if (ret == -1)
        {
              perror("not decrypt data");
              exit(0);
        }

 

        printf("origin text: %s\n", originText);
 

        free(cipherText);
        free(originText);
        RSA_free(rsa);

 

        return 0;

}

 


3. 에러 체크

- OpenSSL 라이브러리는 에러 상황에 대한 print out 에 대한 함수를 제공한다.

- 자세한 내용은 http://www.openssl.org/support/faq.html 에 나와 있다.

 

- 에러 메시지 확인 방법은 다음과 같다.

 

/* errlog.c */ 

 

 #include ...

 

 int main(int arg, char** argv)

 {

    .....

    pPubKey = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL);

   

    if (pPubKey == NULL)

    {

        err_log();

        exit(0);

    }

    .....

 }

 

 void err_log()

 {

    FILE* err;

 

    err = fopen("err.log", "wb");

 

    ERR_load_crypto_strings();

    ERR_print_errors_fp(err);

 

    fclose(err);

 }


- 에러 메시지를 확인해도 모르겠다면, openssl-users 에서 에러 메시지를 검색해 보거나 질문을 한다면 개발자가 답변을 해줄 것이다.

 


OpenSSL 에서 에러를 출력하는 방법에는 여러가지가 있지만 여기서는 가장 간단하게 문제점을 확인해 볼 수 있는 방법을 소개하도록 한다.

 

아래 코드는 RSA private key 로 암호화 된 데이터를 이에 대응되는 RSA public key 로 복호화 하는 함수이다. 어떠한 이유에 의해서든 복호화에 실패할 경우 에러 메시지를 화면에 출력하게 된다.

 

#include <openssl/err.h>

 

static void decrypt_data (unsigned char *buf, unsigned int size)

{

    RSA *rsa=RSA_new();

    int ret=0;

    unsigned long err=0;

 

    OpenSSL_add_all_algorithms();

    ERR_load_crypto_strings();

 

    .............

 

    ret = RSA_public_decrypt(.......);

 

    if (ret == -1)

    {

        err = ERR_get_error();

        printf("ERR: RSA_public_decrypt() - %s\n", ERR_error_string (err, NULL));

    }

    .......

 

    ERR_free_strings();

    RSA_free(rsa);

}

 

[함수 설명]

 void ERR_get_error(void):

가장 최근에 발생한 에러 코드를 쓰레드의 에러 큐에서 가져온다. 

char *ERR_error_string(unsigned long e, char *buf): 
주어진 에러 코드에 대해서 인식할 수 있는 문자로 변환한다.

출력되는 에러 코드의 포맷은 다음과 같다.

 error:[error code]:[library name]:[function name]:[reason string]

 

좀 더 자세한 에러코드 출력법에 대해 알고 싶다면 http://www.openssl.org/docs/crypto/err.html 여기를 참조하기 바란다.


'개발 개발' 카테고리의 다른 글

Common keyboard shortcuts  (0) 2012.07.23
소프트웨어 제작의 기본  (3) 2012.05.21
HG ( Mercurial ) 한글 파일 문제 해결  (0) 2012.02.07
Yum httpd 에 mod_jk 설치 하기  (0) 2012.01.04
fails sanity check 에러가 날때  (0) 2012.01.04