서론
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
- OpenSSL Contex 생성
- aria_128_ccm으로 init
- iv length 세팅
- tag length 세팅
- key값, iv값 init
- plaintext length 세팅
- aad, aad length 값 세팅
- plaintext 암호화
- tag 값 가져오기
- 암호문 뒤에 tag값 이어붙이기
- 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
- OpenSSL Contex 생성
- aria_128_ccm으로 init
- iv length 세팅
- tag length값으로 암호문의 tag영역 분리
- tag 값 세팅
- key, iv값 세팅
- 암호문 복호화
- 복호문 길이 세팅
- 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
'Programming > C' 카테고리의 다른 글
openssl을 활용한 SSL 통신 (0) | 2025.03.17 |
---|---|
unistd.h의 getopt() 함수를 활용한 argument 핸들링 (0) | 2025.03.15 |
if-else 구조 vs lookup table구조 (0) | 2025.03.13 |
YAML 개요 및 libyaml 사용법 (0) | 2025.03.10 |
qsort를 사용하여 배열 정렬하기 (0) | 2025.01.27 |