Programming/C

OpenSSL로 ARIA-CCM 구현

moxie2ks 2025. 3. 4. 18:01
728x90
반응형

서론

ARIA(Advanced Reversible Information Algorithm)는 한국에서 개발된 블록 암호 알고리즘으로, 128비트 블록 크기와 128, 192, 256비트 키 길이를 지원한다. ARIA는 한국 표준으로 채택되어 다양한 보안 응용 프로그램에서 사용되고 있으며, 특히 데이터의 기밀성을 보장하는 데 중요한 역할을 한다.
CCM(Counter with CBC-MAC)은 인증된 암호화 모드로, 데이터의 기밀성뿐만 아니라 무결성도 보장한다. CCM 모드는 블록 암호를 사용하여 데이터를 암호화하고, 동시에 인증 태그를 생성하여 데이터가 변조되지 않았음을 확인할 수 있게 한다. 이러한 특성 덕분에 CCM 모드는 네트워크 프로토콜, 파일 암호화, 그리고 다양한 보안 응용 프로그램에서 널리 사용된다.
OpenSSL은 암호화 및 보안 통신을 위한 강력한 라이브러리로, 다양한 암호화 알고리즘과 프로토콜을 지원한다. 이 라이브러리를 사용하여 ARIA 알고리즘의 CCM 모드를 구현함으로써, 개발자들은 ARIA의 강력한 보안 기능을 손쉽게 활용할 수 있다. 본 글에서는 OpenSSL을 활용하여 ARIA-CCM의 암호화 및 복호화 과정을 구현하고, 이를 통해 CCM 모드의 동작 원리를 이해하는 데 필요한 기초 지식을 제공하고자 한다.

구현 목표

  • OpenSSL을 사용하여 aria의 ccm을 구현해본다.
  • 테스트 벡터는 KISA에서 다운로드 받았다.
  • 예제로 쓰일 테스트 벡터 셋은 ARIA128(CCM)GE.txt 파일을 참조하였다.

예제 코드

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

#define TAG_LEN 64
void hexstr_to_char(const char* hexstr, unsigned char* byte_array, int len) {
    for (int i = 0; i < len; ++i) {
        sscanf(hexstr + (i * 2), "%2hhx", &byte_array[i]);
    }
}

void print_hex(const char *valName, const unsigned char *data, const int dataLen)
{
    int i = 0;

    printf("%s [%dbyte] :", valName, dataLen);
    for (i = 0; i < dataLen; i++)
    {
        printf("%02X", data[i]);
    }
    printf("\\n");
}

int ccm_encrypt(unsigned char *plaintext, int plaintext_len,
                unsigned char *aad, int aad_len,
                unsigned char *key,
                unsigned char *iv, int iv_len,
                unsigned char *ciphertext,
                unsigned char *tag, int tag_len)
{
    EVP_CIPHER_CTX *ctx;
    int len;
    int ciphertext_len;

    /* Create and initialise the context */
    if(!(ctx = EVP_CIPHER_CTX_new()))
        printf("EVP_CIPHER_CTX_new() failed.\\n");

    /* Initialise the encryption operation. */
    if(1 != EVP_EncryptInit_ex(ctx, EVP_aria_128_ccm(), NULL, NULL, NULL))
        printf("EVP_EncryptInit_ex(ctx, EVP_aes_256_ccm(), NULL, NULL, NULL) failed.\\n");

    /* Setting IV len to iv_len. */
    if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_IVLEN, iv_len, NULL))
        printf("EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_IVLEN, iv_len, NULL) failed.\\n");

    /* Set tag length */
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, tag_len, NULL);

    /* Initialise key and IV */
    if(1 != EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv))
        printf("EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv) failed.\\n");

    /* Provide the total plaintext length */
    if(1 != EVP_EncryptUpdate(ctx, NULL, &len, NULL, plaintext_len))
        printf("EVP_EncryptUpdate(ctx, NULL, &len, NULL, plaintext_len) failed.\\n");

    /* Provide any AAD data. This can be called zero or one times as required */
    if(1 != EVP_EncryptUpdate(ctx, NULL, &len, aad, aad_len))
        printf("EVP_EncryptUpdate(ctx, NULL, &len, aad, aad_len) failed.\\n");

    /*
     * Provide the message to be encrypted, and obtain the encrypted output.
     * EVP_EncryptUpdate can only be called once for this.
     */
    if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len))
        printf("EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len) failed.\\n");
    ciphertext_len = len;

    if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len))
       printf("EVP_EncryptFinal_ex(ctx, ciphertext + len, &len) failed.\\n");
   ciphertext_len += len;

   /* Get the tag */
   if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_GET_TAG, tag_len, tag))
       printf("EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_GET_TAG, tag_len, tag) failed.\\n");

   memcpy(ciphertext + ciphertext_len, tag, tag_len);
   ciphertext_len += tag_len;
    /*
     * Finalise the encryption. Normally ciphertext bytes may be written at
     * this stage, but this does not occur in CCM mode.
     */
    if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len))
        printf("EVP_EncryptFinal_ex(ctx, ciphertext + len, &len) failed.\\n");
    ciphertext_len += len;

    /* Clean up */
    EVP_CIPHER_CTX_free(ctx);

    return ciphertext_len;
}

int ccm_decrypt(unsigned char *ciphertext, int ciphertext_len,
                unsigned char *aad, int aad_len,
                int tag_len,
                unsigned char *key,
                unsigned char *iv, int iv_len,
                unsigned char *plaintext)
{
    EVP_CIPHER_CTX *ctx;
    int len;
    int plaintext_len = ciphertext_len - tag_len;
    int ret;
    
    /* Create and initialise the context */
    if(!(ctx = EVP_CIPHER_CTX_new()))
        printf("EVP_CIPHER_CTX_new() failed.\\n");

    /* Initialise the decryption operation. */
    if(1 != EVP_DecryptInit_ex(ctx, EVP_aria_128_ccm(), NULL, NULL, NULL))
        printf("EVP_DecryptInit_ex(ctx, EVP_aes_256_ccm(), NULL, NULL, NULL) failed.\\n");

    /* Setting IV len to iv_len. */
    if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_IVLEN, iv_len, NULL))
        printf("EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_IVLEN, iv_len, NULL) failed.\\n");

    /* Extract tag from ciphertext */
    unsigned char tag[tag_len];
    memcpy(tag, ciphertext + (ciphertext_len - tag_len), tag_len);
    ciphertext_len -= tag_len;
    
    print_hex("dec-tag", tag, tag_len);

    /* Set expected tag value. */
    if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, tag_len, tag))
        printf("EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, tag_len, tag) failed.\\n");

    /* Initialise key and IV */
    if(1 != EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv))
        printf("EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv) failed.\\n");

    /* Provide the total ciphertext length */
    if(1 != EVP_DecryptUpdate(ctx, NULL, &len, NULL, ciphertext_len))
        printf("EVP_DecryptUpdate(ctx, NULL, &len, NULL, ciphertext_len) failed.\\n");

    /* Provide any AAD data. This can be called zero or more times as required */
    if(1 != EVP_DecryptUpdate(ctx, NULL, &len, aad, aad_len))
        printf("EVP_DecryptUpdate(ctx, NULL, &len, aad, aad_len) failed.\\n");

    /*
     * Provide the message to be decrypted, and obtain the plaintext output.
     * EVP_DecryptUpdate can be called multiple times if necessary
     */
    ret = EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len);

    plaintext_len = len;

    /* Clean up */
    EVP_CIPHER_CTX_free(ctx);

    if(ret > 0) {
        /* Success */
        return plaintext_len;
    } else {
        /* Verify failed */
        return -1;
    }
}

int main(void) {
    const char *k_str = "01ECA65C08A45FA85E173FB272D66BE1";
    const char *n_str = "F39504A7A74AE1DE59F54F30";
    const char *a_str = "D460FA004195";
    const char *p_str = "A2E1E25ED02784812092961F62A34FE5";
    const int tag_len_bits = 128;
    int tag_len = tag_len_bits / 8;

    int k_len = (int)strlen(k_str) / 2;
    int n_len = (int)strlen(n_str) / 2;
    int a_len = (int)strlen(a_str) / 2;
    int p_len = (int)strlen(p_str) / 2;

    unsigned char k[k_len];
    unsigned char n[n_len];
    unsigned char a[a_len];
    unsigned char p[p_len];

    // Convert hex strings to byte arrays
    hexstr_to_char(k_str, k, k_len);
    hexstr_to_char(n_str, n, n_len);
    hexstr_to_char(a_str, a, a_len);
    hexstr_to_char(p_str, p, p_len);

    // Encrypt the plaintext
    int ciphertext_len = p_len + tag_len;
    unsigned char ciphertext[ciphertext_len];
    unsigned char tag[tag_len];
    ciphertext_len = ccm_encrypt(p, p_len, a, a_len, k, n, n_len, ciphertext, tag, tag_len);
    
    printf("ccm_encrypt\\n");
    print_hex("plaintext", p, p_len);
    print_hex("ciphertext", ciphertext, ciphertext_len);
    print_hex("tag", tag, tag_len);
    printf("\\n");
    
    // Try to decrypt and verify the values, using the same key, nonce, and additional data
    printf("ccm_decrypt\\n");
    int decrypted_len = ciphertext_len - tag_len;
    unsigned char decrypted[decrypted_len];
    int ret = ccm_decrypt(ciphertext, ciphertext_len, a, a_len, tag_len, k, n, n_len, decrypted);

    if (ret > 0) {
        print_hex("decrypted", decrypted, decrypted_len);
    } else {
        printf("Decryption failed.\\n");
    }

    return ret > 0 ? 0 : 1;
}

코드 설명

Hex 문자열 처리

  • 테스트 벡터에 사용할 input을 쉽게 사용하기 위해 hex 문자열 형식을 unsigned char로 바꾸는 함수인 hexstr_to_char 를 추가했다.
  • 또한 hex string을 쉽게 확인 할 수 있는 print 함수인 print_hex 함수를 추가했다.
void hexstr_to_char(const char* hexstr, unsigned char* byte_array, int len) {
    for (int i = 0; i < len; ++i) {
        sscanf(hexstr + (i * 2), "%2hhx", &byte_array[i]);
    }
}

void print_hex(const char *valName, const unsigned char *data, const int dataLen)
{
    int i = 0;

    printf("%s [%dbyte] :", valName, dataLen);
    for (i = 0; i < dataLen; i++)
    {
        printf("%02X", data[i]);
    }
    printf("\\n");
}

Encryption

  1. OpenSSL Contex 생성
  2. aria_128_ccm으로 init
  3. iv length 세팅
  4. tag length 세팅
  5. key값, iv값 init
  6. plaintext length 세팅
  7. aad, aad length 값 세팅
  8. plaintext 암호화
  9. tag 값 가져오기
  10. 암호문 뒤에 tag값 이어붙이기
  11. OpenSSL Context 해제
int ccm_encrypt(unsigned char *plaintext, int plaintext_len,
                unsigned char *aad, int aad_len,
                unsigned char *key,
                unsigned char *iv, int iv_len,
                unsigned char *ciphertext,
                unsigned char *tag, int tag_len)
{
    EVP_CIPHER_CTX *ctx;
    int len;
    int ciphertext_len;

    /* Create and initialise the context */
    if(!(ctx = EVP_CIPHER_CTX_new()))
        printf("EVP_CIPHER_CTX_new() failed.\\n");

    /* Initialise the encryption operation. */
    if(1 != EVP_EncryptInit_ex(ctx, EVP_aria_128_ccm(), NULL, NULL, NULL))
        printf("EVP_EncryptInit_ex(ctx, EVP_aes_256_ccm(), NULL, NULL, NULL) failed.\\n");

    /* Setting IV len to iv_len. */
    if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_IVLEN, iv_len, NULL))
        printf("EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_IVLEN, iv_len, NULL) failed.\\n");

    /* Set tag length */
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, tag_len, NULL);

    /* Initialize key and IV */
    if(1 != EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv))
        printf("EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv) failed.\\n");

    /* Provide the total plaintext length */
    if(1 != EVP_EncryptUpdate(ctx, NULL, &len, NULL, plaintext_len))
        printf("EVP_EncryptUpdate(ctx, NULL, &len, NULL, plaintext_len) failed.\\n");

    /* Provide any AAD data. This can be called zero or one times as required */
    if(1 != EVP_EncryptUpdate(ctx, NULL, &len, aad, aad_len))
        printf("EVP_EncryptUpdate(ctx, NULL, &len, aad, aad_len) failed.\\n");

    /*
     * Provide the message to be encrypted, and obtain the encrypted output.
     * EVP_EncryptUpdate can only be called once for this.
     */
    if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len))
        printf("EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len) failed.\\n");
    ciphertext_len = len;

    if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len))
       printf("EVP_EncryptFinal_ex(ctx, ciphertext + len, &len) failed.\\n");
   ciphertext_len += len;

   /* Get the tag */
   if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_GET_TAG, tag_len, tag))
       printf("EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_GET_TAG, tag_len, tag) failed.\\n");

   memcpy(ciphertext + ciphertext_len, tag, tag_len);
   ciphertext_len += tag_len;
    /*
     * Finalise the encryption. Normally ciphertext bytes may be written at
     * this stage, but this does not occur in CCM mode.
     */
    if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len))
        printf("EVP_EncryptFinal_ex(ctx, ciphertext + len, &len) failed.\\n");
    ciphertext_len += len;

    /* Clean up */
    EVP_CIPHER_CTX_free(ctx);

    return ciphertext_len;
}

Dceryption

  1. OpenSSL Contex 생성
  2. aria_128_ccm으로 init
  3. iv length 세팅
  4. tag length값으로 암호문의 tag영역 분리
  5. tag 값 세팅
  6. key, iv값 세팅
  7. 암호문 복호화
  8. 복호문 길이 세팅
  9. OpenSSL Context 해제
int ccm_decrypt(unsigned char *ciphertext, int ciphertext_len,
                unsigned char *aad, int aad_len,
                int tag_len,
                unsigned char *key,
                unsigned char *iv, int iv_len,
                unsigned char *plaintext)
{
    EVP_CIPHER_CTX *ctx;
    int len;
    int plaintext_len = ciphertext_len - tag_len;
    int ret;
    
    /* Create and initialize the context */
    if(!(ctx = EVP_CIPHER_CTX_new()))
        printf("EVP_CIPHER_CTX_new() failed.\\n");

    /* Initialise the decryption operation. */
    if(1 != EVP_DecryptInit_ex(ctx, EVP_aria_128_ccm(), NULL, NULL, NULL))
        printf("EVP_DecryptInit_ex(ctx, EVP_aes_256_ccm(), NULL, NULL, NULL) failed.\\n");

    /* Setting IV len to iv_len. */
    if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_IVLEN, iv_len, NULL))
        printf("EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_IVLEN, iv_len, NULL) failed.\\n");

    /* Extract tag from ciphertext */
    unsigned char tag[tag_len];
    memcpy(tag, ciphertext + (ciphertext_len - tag_len), tag_len);
    ciphertext_len -= tag_len;
    
    print_hex("dec-tag", tag, tag_len);

    /* Set expected tag value. */
    if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, tag_len, tag))
        printf("EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, tag_len, tag) failed.\\n");

    /* Initialise key and IV */
    if(1 != EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv))
        printf("EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv) failed.\\n");

    /* Provide the total ciphertext length */
    if(1 != EVP_DecryptUpdate(ctx, NULL, &len, NULL, ciphertext_len))
        printf("EVP_DecryptUpdate(ctx, NULL, &len, NULL, ciphertext_len) failed.\\n");

    /* Provide any AAD data. This can be called zero or more times as required */
    if(1 != EVP_DecryptUpdate(ctx, NULL, &len, aad, aad_len))
        printf("EVP_DecryptUpdate(ctx, NULL, &len, aad, aad_len) failed.\\n");

    /*
     * Provide the message to be decrypted, and obtain the plaintext output.
     * EVP_DecryptUpdate can be called multiple times if necessary
     */
    ret = EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len);

    plaintext_len = len;

    /* Clean up */
    EVP_CIPHER_CTX_free(ctx);

    if(ret > 0) {
        /* Success */
        return plaintext_len;
    } else {
        /* Verify failed */
        return -1;
    }
}

 

결론

본 글에서는 OpenSSL을 사용하여 ARIA 알고리즘의 CCM 모드를 구현하고, 이를 통해 암호화 및 복호화 과정을 실습하였다. 테스트 벡터로 KISA에서 제공하는 ARIA128(CCM)GE.txt 파일의 데이터를 사용하였으며, Hex 문자열을 바이트 배열로 변환하는 유틸리티 함수와 암호화 및 복호화에 필요한 주요 함수들을 구현하였다. 이를 통해 CCM 모드의 동작 원리를 이해하고, 실제로 암호화와 복호화가 어떻게 이루어지는지를 확인할 수 있었다.
이 코드는 암호화 프로토콜이나 보안 시스템을 개발하는 데 있어 기초적인 예제로 활용될 수 있으며, OpenSSL 라이브러리의 기능을 이해하는 데 도움을 줄 것이다. 앞으로의 연구에서는 다양한 알고리즘의 비교, 성능 최적화, 그리고 보안성을 평가하는 방향으로 나아갈 수 있다.

참고문헌

[1] KISA. (n.d.). ARIA128(CCM)GE.txt. Retrieved from KISA
[2] OpenSSL Project. (n.d.). OpenSSL Documentation. Retrieved from OpenSSL
[3] National Institute of Standards and Technology (NIST). (2001). Specification for the Advanced Encryption Standard (AES). Retrieved from NIST

728x90
반응형