본문 바로가기

개발 개발

[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