C 언어에서 OpenSSL을 활용한 RSA 및 AES 구현 방법

C 언어에서 OpenSSL을 활용하여 RSA 및 AES 암호화를 구현하는 것은 보안 애플리케이션을 개발하는 데 필수적인 기술입니다. OpenSSL은 강력한 암호화 기능을 제공하는 오픈소스 라이브러리로, 네트워크 보안, 파일 암호화, 데이터 보호 등에 널리 사용됩니다.

RSA는 공개 키 암호화 방식으로, 데이터를 안전하게 주고받는 데 사용되며, 키 길이가 길수록 보안성이 향상됩니다. 반면, AES는 대칭 키 암호화 방식으로, 속도가 빠르고 데이터 보호에 효과적입니다.

본 기사에서는 OpenSSL을 활용하여 RSA와 AES 암호화를 C 언어로 구현하는 방법을 단계별로 설명합니다. OpenSSL 라이브러리 설치부터 RSA 및 AES 키 생성, 암호화 및 복호화 과정, 그리고 최적화 및 보안 고려사항까지 다룹니다. 이를 통해 보안이 중요한 애플리케이션을 개발하는 방법을 익힐 수 있습니다.

OpenSSL과 암호화 개요


OpenSSL은 오픈소스 암호화 라이브러리로, TLS/SSL 통신 보안, 데이터 암호화, 디지털 서명 등의 기능을 제공합니다. C 언어 기반으로 개발되었으며, 다양한 플랫폼에서 활용할 수 있습니다.

RSA와 AES 개념


RSA와 AES는 대표적인 암호화 방식으로 각각 공개 키 암호화와 대칭 키 암호화를 대표합니다.

RSA (Rivest-Shamir-Adleman)

  • 공개 키 암호화 방식(Public Key Cryptography)
  • 데이터를 암호화할 때 공개 키(Public Key)를 사용하고, 복호화할 때 개인 키(Private Key)를 사용
  • 키 길이가 길어질수록 보안성이 높지만 암호화 및 복호화 속도가 느림
  • 주로 보안 키 교환이나 디지털 서명에 사용

AES (Advanced Encryption Standard)

  • 대칭 키 암호화 방식(Symmetric Key Cryptography)
  • 암호화와 복호화에 동일한 키를 사용
  • 빠르고 강력한 암호화 성능을 제공하며, 128, 192, 256비트 키를 지원
  • 파일 암호화, 데이터 보호, VPN 등에 사용

OpenSSL에서 제공하는 주요 암호화 기능


OpenSSL은 RSA 및 AES를 포함하여 다양한 암호화 알고리즘을 지원합니다.

  • RSA: 키 생성, 암호화/복호화, 디지털 서명 및 검증
  • AES: ECB, CBC, CFB, OFB, GCM 등의 다양한 모드 제공
  • SHA: SHA-1, SHA-256, SHA-512 등 해시 함수 지원
  • HMAC: 메시지 인증 코드(Message Authentication Code) 생성

이제 OpenSSL을 C 언어에서 활용하기 위한 환경 설정 및 설치 방법을 알아보겠습니다.

OpenSSL 설치 및 환경 설정


OpenSSL을 활용하여 RSA 및 AES 암호화를 구현하려면 먼저 OpenSSL 라이브러리를 설치하고 개발 환경을 설정해야 합니다. 아래에서는 Linux, macOS, Windows 환경에서 OpenSSL을 설치하는 방법과 C 프로그램에서 사용할 수 있도록 설정하는 방법을 설명합니다.

Linux에서 OpenSSL 설치


대부분의 Linux 배포판에는 OpenSSL이 기본적으로 포함되어 있지만, 최신 버전을 설치하거나 개발용 라이브러리를 추가로 설치해야 할 수도 있습니다.

Ubuntu/Debian 계열

sudo apt update
sudo apt install openssl libssl-dev

CentOS/RHEL 계열

sudo yum install openssl openssl-devel

설치가 완료되면 다음 명령어로 OpenSSL 버전을 확인할 수 있습니다.

openssl version -a

macOS에서 OpenSSL 설치


macOS에서는 기본적으로 OpenSSL이 포함되어 있지만, 최신 버전을 사용하려면 Homebrew를 통해 설치하는 것이 좋습니다.

brew install openssl

설치 후 OpenSSL 라이브러리를 사용할 수 있도록 환경 변수를 설정합니다.

export LDFLAGS="-L/usr/local/opt/openssl/lib"
export CPPFLAGS="-I/usr/local/opt/openssl/include"
export PKG_CONFIG_PATH="/usr/local/opt/openssl/lib/pkgconfig"

Windows에서 OpenSSL 설치


Windows에서는 OpenSSL을 직접 다운로드하여 설치해야 합니다. 가장 간편한 방법은 Shining Light Productions에서 제공하는 OpenSSL 바이너리를 다운로드하는 것입니다.

설치 후 환경 변수에 OpenSSL 실행 파일이 포함된 디렉터리를 추가해야 합니다.

set PATH=C:\Program Files\OpenSSL-Win64\bin;%PATH%

그리고 OpenSSL 버전을 확인합니다.

openssl version -a

C 프로그램에서 OpenSSL 라이브러리 사용 설정


OpenSSL을 C 코드에서 사용하려면 컴파일 시 적절한 라이브러리를 링크해야 합니다.

  • 컴파일 옵션 예시 (Linux/macOS)
  gcc -o my_program my_program.c -lssl -lcrypto
  • Windows에서 MinGW를 사용하는 경우
  gcc -o my_program.exe my_program.c -lssl -lcrypto -lws2_32

이제 OpenSSL을 활용한 RSA 키 생성 및 암호화를 진행할 준비가 되었습니다.

RSA 암호화 개념 및 키 생성

RSA 암호화는 공개 키 암호화(Public Key Cryptography) 방식으로, 데이터를 암호화할 때 공개 키(Public Key)를 사용하고 복호화할 때 개인 키(Private Key)를 사용합니다. 이는 보안성이 뛰어나지만 연산 속도가 상대적으로 느리므로 주로 키 교환이나 디지털 서명에 활용됩니다.

RSA 암호화 원리


RSA 알고리즘은 큰 소수 두 개를 기반으로 키를 생성하며, 암호화 및 복호화 과정은 다음과 같습니다.

  1. 두 개의 큰 소수 ( p )와 ( q )를 선택하고 곱하여 ( n )을 생성:
    ( n = p \times q )
  2. 오일러 토션트 함수 ( \phi(n) = (p-1) \times (q-1) )를 계산
  3. 공개 키 ( e ) 선택 (일반적으로 65537 사용)
  4. 개인 키 ( d ) 계산:
    ( d \times e \equiv 1 \mod \phi(n) )
  5. 암호화:
    ( C = M^e \mod n )
  6. 복호화:
    ( M = C^d \mod n )

OpenSSL을 이용한 RSA 키 생성


OpenSSL을 활용하면 RSA 키를 쉽게 생성할 수 있습니다.

1. 명령어를 이용한 RSA 키 생성


다음 명령어를 실행하여 2048비트 RSA 개인 키를 생성합니다.

openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048

개인 키로부터 공개 키를 추출하려면 다음 명령어를 실행합니다.

openssl rsa -pubout -in private_key.pem -out public_key.pem

2. C 코드에서 RSA 키 생성


OpenSSL의 RSA_generate_key_ex 함수를 사용하여 RSA 키를 생성할 수 있습니다.

#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <stdio.h>

void generate_rsa_key() {
    int bits = 2048;
    RSA *rsa = RSA_new();
    BIGNUM *e = BN_new();

    if (!BN_set_word(e, RSA_F4)) {
        fprintf(stderr, "Error setting exponent\n");
        return;
    }

    if (!RSA_generate_key_ex(rsa, bits, e, NULL)) {
        fprintf(stderr, "Error generating RSA key\n");
        return;
    }

    FILE *private_file = fopen("private_key.pem", "wb");
    FILE *public_file = fopen("public_key.pem", "wb");

    if (private_file && public_file) {
        PEM_write_RSAPrivateKey(private_file, rsa, NULL, NULL, 0, NULL, NULL);
        PEM_write_RSA_PUBKEY(public_file, rsa);
        printf("RSA keys generated successfully.\n");
    }

    fclose(private_file);
    fclose(public_file);
    RSA_free(rsa);
    BN_free(e);
}

int main() {
    generate_rsa_key();
    return 0;
}

위 코드를 실행하면 private_key.pempublic_key.pem 파일이 생성됩니다. 이 키를 사용하여 데이터를 암호화 및 복호화할 수 있습니다. 다음 단계에서는 RSA 암호화 및 복호화 방법을 설명합니다.

OpenSSL을 이용한 RSA 암호화 및 복호화

RSA 키를 생성한 후, 이를 이용하여 데이터를 암호화하고 복호화할 수 있습니다. OpenSSL을 활용하면 C 언어에서 RSA 암호화를 쉽게 구현할 수 있습니다.

RSA 암호화 및 복호화 과정

  1. 공개 키를 사용하여 데이터를 암호화
  2. 개인 키를 사용하여 데이터를 복호화

OpenSSL에서는 RSA_public_encrypt 함수를 이용해 데이터를 암호화하고, RSA_private_decrypt 함수를 사용해 복호화할 수 있습니다.

C 언어에서 RSA 암호화 구현

아래 예제에서는 OpenSSL을 활용하여 RSA 공개 키를 사용한 암호화와 개인 키를 사용한 복호화를 수행합니다.

#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <stdio.h>
#include <string.h>

#define KEY_LENGTH 2048
#define PUBLIC_EXPONENT RSA_F4
#define MESSAGE "Hello, OpenSSL RSA!"

void handle_openssl_error() {
    ERR_print_errors_fp(stderr);
    exit(EXIT_FAILURE);
}

RSA *load_public_key(const char *filename) {
    FILE *fp = fopen(filename, "rb");
    if (!fp) {
        perror("Failed to open public key file");
        return NULL;
    }
    RSA *rsa = PEM_read_RSA_PUBKEY(fp, NULL, NULL, NULL);
    fclose(fp);
    return rsa;
}

RSA *load_private_key(const char *filename) {
    FILE *fp = fopen(filename, "rb");
    if (!fp) {
        perror("Failed to open private key file");
        return NULL;
    }
    RSA *rsa = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL);
    fclose(fp);
    return rsa;
}

void rsa_encrypt_decrypt() {
    // 공개 키 로드
    RSA *public_rsa = load_public_key("public_key.pem");
    if (!public_rsa) handle_openssl_error();

    // 개인 키 로드
    RSA *private_rsa = load_private_key("private_key.pem");
    if (!private_rsa) handle_openssl_error();

    // 암호화
    unsigned char encrypted[KEY_LENGTH / 8];
    int encrypted_length = RSA_public_encrypt(strlen(MESSAGE) + 1, (unsigned char *)MESSAGE, encrypted, public_rsa, RSA_PKCS1_OAEP_PADDING);
    if (encrypted_length == -1) handle_openssl_error();
    printf("Encrypted message: ");
    for (int i = 0; i < encrypted_length; i++) {
        printf("%02x", encrypted[i]);
    }
    printf("\n");

    // 복호화
    unsigned char decrypted[KEY_LENGTH / 8];
    int decrypted_length = RSA_private_decrypt(encrypted_length, encrypted, decrypted, private_rsa, RSA_PKCS1_OAEP_PADDING);
    if (decrypted_length == -1) handle_openssl_error();
    printf("Decrypted message: %s\n", decrypted);

    // 메모리 해제
    RSA_free(public_rsa);
    RSA_free(private_rsa);
}

int main() {
    rsa_encrypt_decrypt();
    return 0;
}

코드 설명

  1. load_public_keyload_private_key 함수를 통해 공개 키와 개인 키를 로드합니다.
  2. RSA_public_encrypt 함수를 사용하여 데이터를 공개 키로 암호화합니다.
  3. RSA_private_decrypt 함수를 사용하여 암호화된 데이터를 개인 키로 복호화합니다.
  4. 결과를 출력한 후 메모리를 해제합니다.

실행 결과 예시

Encrypted message: 6f2b3a2c1d...
Decrypted message: Hello, OpenSSL RSA!

위와 같이 RSA를 활용한 암호화 및 복호화가 정상적으로 수행됩니다. 다음 단계에서는 AES 암호화 및 키 관리에 대해 알아보겠습니다.

AES 암호화 개념 및 키 관리

AES(Advanced Encryption Standard)는 빠르고 안전한 대칭 키 암호화 알고리즘으로, 보안성이 뛰어나고 성능이 우수하여 광범위하게 사용됩니다. RSA와 달리 동일한 키를 사용하여 데이터를 암호화하고 복호화하는 것이 특징입니다.

AES 암호화 원리


AES는 128비트, 192비트, 256비트 키 크기를 지원하며, 다음과 같은 과정을 통해 데이터를 보호합니다.

  1. 키 생성: 특정 길이(128, 192, 256비트)의 암호화 키 생성
  2. 암호화 과정: AES 블록 암호화를 수행하여 데이터를 보호
  3. 복호화 과정: 동일한 키를 사용하여 암호화된 데이터를 원본으로 복원

AES는 다양한 운영 모드(ECB, CBC, CFB, OFB, GCM 등)를 지원하며, 보안성과 응용 분야에 따라 적절한 모드를 선택해야 합니다.

AES 운영 모드

  • ECB (Electronic Codebook): 동일한 평문 블록이 동일한 암호문 블록으로 변환 (보안성 낮음)
  • CBC (Cipher Block Chaining): 이전 암호문 블록과 현재 평문 블록을 XOR 후 암호화 (보안성 향상)
  • CFB (Cipher Feedback) / OFB (Output Feedback): 스트림 암호 방식과 유사한 동작
  • GCM (Galois/Counter Mode): 인증 및 무결성 검증 지원, TLS 및 네트워크 보안에 사용

OpenSSL을 이용한 AES 키 관리


OpenSSL을 사용하여 AES 키를 안전하게 생성하고 관리할 수 있습니다.

1. OpenSSL 명령어를 이용한 키 생성


AES 256비트(32바이트) 키를 생성하려면 다음 명령어를 사용합니다.

openssl rand -hex 32

AES에서 IV(초기화 벡터)가 필요한 경우, 다음과 같이 생성할 수 있습니다.

openssl rand -hex 16

2. C 코드에서 AES 키 생성


OpenSSL의 RAND_bytes 함수를 사용하여 안전한 난수를 생성하고 AES 키로 활용할 수 있습니다.

#include <openssl/rand.h>
#include <stdio.h>

#define AES_KEY_SIZE 32  // 256비트 키
#define AES_IV_SIZE 16   // 128비트 IV

void generate_aes_key(unsigned char *key, unsigned char *iv) {
    if (!RAND_bytes(key, AES_KEY_SIZE)) {
        fprintf(stderr, "Error generating AES key\n");
        return;
    }
    if (!RAND_bytes(iv, AES_IV_SIZE)) {
        fprintf(stderr, "Error generating IV\n");
        return;
    }
}

int main() {
    unsigned char key[AES_KEY_SIZE];
    unsigned char iv[AES_IV_SIZE];

    generate_aes_key(key, iv);

    printf("Generated AES Key: ");
    for (int i = 0; i < AES_KEY_SIZE; i++) printf("%02x", key[i]);
    printf("\n");

    printf("Generated AES IV: ");
    for (int i = 0; i < AES_IV_SIZE; i++) printf("%02x", iv[i]);
    printf("\n");

    return 0;
}

결론


AES 키 관리는 보안성을 유지하는 데 중요한 요소입니다. OpenSSL을 활용하면 강력한 난수를 생성하여 안전한 AES 키 및 IV를 관리할 수 있습니다.
다음 단계에서는 OpenSSL을 사용하여 AES 암호화 및 복호화를 구현하는 방법을 설명하겠습니다.

OpenSSL을 이용한 AES 암호화 및 복호화

AES 암호화를 구현하려면 OpenSSL의 EVP_EncryptInit_ex, EVP_EncryptUpdate, EVP_EncryptFinal_ex API를 활용할 수 있습니다. OpenSSL은 AES-128, AES-192, AES-256을 지원하며, CBC, GCM 등 다양한 모드를 선택할 수 있습니다.

이제 AES-256-CBC 모드를 사용하여 C 언어로 데이터를 암호화하고 복호화하는 방법을 살펴보겠습니다.

C 언어에서 AES 암호화 구현

다음 코드는 AES-256-CBC 모드를 사용하여 데이터를 암호화하고 복호화하는 예제입니다.

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

#define AES_KEY_SIZE 32  // 256비트 키
#define AES_IV_SIZE 16   // 128비트 IV
#define BUFFER_SIZE 128  // 테스트용 버퍼 크기

void handleErrors() {
    fprintf(stderr, "An error occurred\n");
    exit(EXIT_FAILURE);
}

void aes_encrypt(const unsigned char *plaintext, int plaintext_len,
                 const unsigned char *key, const unsigned char *iv,
                 unsigned char *ciphertext, int *ciphertext_len) {
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    if (!ctx) handleErrors();

    if (EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv) != 1) handleErrors();

    int len;
    if (EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len) != 1) handleErrors();
    *ciphertext_len = len;

    if (EVP_EncryptFinal_ex(ctx, ciphertext + len, &len) != 1) handleErrors();
    *ciphertext_len += len;

    EVP_CIPHER_CTX_free(ctx);
}

void aes_decrypt(const unsigned char *ciphertext, int ciphertext_len,
                 const unsigned char *key, const unsigned char *iv,
                 unsigned char *plaintext, int *plaintext_len) {
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    if (!ctx) handleErrors();

    if (EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv) != 1) handleErrors();

    int len;
    if (EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len) != 1) handleErrors();
    *plaintext_len = len;

    if (EVP_DecryptFinal_ex(ctx, plaintext + len, &len) != 1) handleErrors();
    *plaintext_len += len;

    EVP_CIPHER_CTX_free(ctx);
}

int main() {
    unsigned char key[AES_KEY_SIZE];
    unsigned char iv[AES_IV_SIZE];
    unsigned char plaintext[BUFFER_SIZE] = "Hello, OpenSSL AES!";
    unsigned char ciphertext[BUFFER_SIZE];
    unsigned char decryptedtext[BUFFER_SIZE];
    int ciphertext_len, decryptedtext_len;

    // 난수를 사용하여 AES 키와 IV 생성
    if (!RAND_bytes(key, AES_KEY_SIZE) || !RAND_bytes(iv, AES_IV_SIZE)) {
        handleErrors();
    }

    printf("Plaintext: %s\n", plaintext);

    // 암호화
    aes_encrypt(plaintext, strlen((char *)plaintext), key, iv, ciphertext, &ciphertext_len);
    printf("Ciphertext (hex): ");
    for (int i = 0; i < ciphertext_len; i++) {
        printf("%02x", ciphertext[i]);
    }
    printf("\n");

    // 복호화
    aes_decrypt(ciphertext, ciphertext_len, key, iv, decryptedtext, &decryptedtext_len);
    decryptedtext[decryptedtext_len] = '\0';
    printf("Decrypted text: %s\n", decryptedtext);

    return 0;
}

코드 설명

  1. AES-256-CBC 모드 사용
  • EVP_aes_256_cbc()를 사용하여 AES-256-CBC 방식으로 암호화 및 복호화 수행
  1. AES 암호화 (aes_encrypt)
  • EVP_EncryptInit_ex를 호출하여 암호화 설정
  • EVP_EncryptUpdate를 사용하여 데이터 암호화
  • EVP_EncryptFinal_ex를 사용하여 마지막 블록 처리
  1. AES 복호화 (aes_decrypt)
  • EVP_DecryptInit_ex를 호출하여 복호화 설정
  • EVP_DecryptUpdate를 사용하여 데이터 복호화
  • EVP_DecryptFinal_ex를 사용하여 마지막 블록 처리

실행 결과 예시

Plaintext: Hello, OpenSSL AES!
Ciphertext (hex): 8c4f5d3a1b...
Decrypted text: Hello, OpenSSL AES!

결론


이제 OpenSSL을 활용하여 AES 암호화를 구현할 수 있습니다. 다음 단계에서는 RSA 및 AES 암호화된 데이터를 저장하고 안전하게 전송하는 방법을 알아보겠습니다.

암호화 데이터의 저장 및 전송

암호화된 데이터를 안전하게 저장하고 전송하는 것은 보안 시스템에서 중요한 요소입니다. 암호화된 데이터가 제대로 보호되지 않으면 공격자가 복호화 키를 얻거나 데이터를 변조할 위험이 있습니다. 이 장에서는 RSA 및 AES 암호화 데이터를 안전하게 저장하고 전송하는 방법을 살펴봅니다.

암호화 데이터의 저장

암호화된 데이터를 저장할 때 고려해야 할 사항은 다음과 같습니다.

  1. 파일 저장 시 데이터 무결성 유지
  • 암호화된 데이터를 저장할 때 해시(SHA-256) 또는 HMAC을 사용하여 무결성을 검증할 수 있습니다.
  1. 안전한 키 저장
  • 암호화 키는 별도의 보안 저장소(HSM, 환경 변수, 안전한 파일)에 저장해야 합니다.
  1. 데이터 암호화 후 저장
  • AES를 사용하여 파일을 암호화한 후 저장하면 데이터를 보호할 수 있습니다.

AES 암호화 데이터를 파일로 저장

아래 코드는 AES 암호화 데이터를 파일로 저장하고 다시 읽어오는 예제입니다.

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

#define AES_KEY_SIZE 32
#define AES_IV_SIZE 16
#define BUFFER_SIZE 128

void save_to_file(const char *filename, const unsigned char *data, int length) {
    FILE *file = fopen(filename, "wb");
    if (!file) {
        perror("File opening failed");
        return;
    }
    fwrite(data, 1, length, file);
    fclose(file);
}

int load_from_file(const char *filename, unsigned char *data, int max_length) {
    FILE *file = fopen(filename, "rb");
    if (!file) {
        perror("File opening failed");
        return -1;
    }
    int length = fread(data, 1, max_length, file);
    fclose(file);
    return length;
}

int main() {
    unsigned char key[AES_KEY_SIZE], iv[AES_IV_SIZE];
    unsigned char plaintext[BUFFER_SIZE] = "Sensitive Data for AES encryption";
    unsigned char ciphertext[BUFFER_SIZE];
    int ciphertext_len;

    // AES 키 및 IV 생성
    RAND_bytes(key, AES_KEY_SIZE);
    RAND_bytes(iv, AES_IV_SIZE);

    // AES 암호화
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv);
    EVP_EncryptUpdate(ctx, ciphertext, &ciphertext_len, plaintext, strlen((char *)plaintext));
    EVP_EncryptFinal_ex(ctx, ciphertext + ciphertext_len, &ciphertext_len);
    EVP_CIPHER_CTX_free(ctx);

    // 암호화 데이터 파일 저장
    save_to_file("encrypted_data.bin", ciphertext, ciphertext_len);

    printf("Encrypted data saved successfully.\n");

    return 0;
}

암호화 데이터의 안전한 전송

암호화된 데이터를 네트워크를 통해 전송할 때는 데이터 변조 및 탈취를 방지해야 합니다.

전송 보안 강화 방법

  • TLS(SSL) 적용
  • OpenSSL을 활용하여 TLS를 사용하면 암호화된 데이터 전송이 가능
  • HMAC(Message Authentication Code) 추가
  • HMAC-SHA256을 사용하여 데이터가 변경되지 않았음을 검증
  • RSA를 활용한 AES 키 보호
  • AES 키를 RSA 공개 키로 암호화하여 전송하면 안전성이 향상됨

RSA를 사용한 AES 키 보호 및 전송

AES 키를 보호하기 위해 RSA 공개 키로 AES 키를 암호화한 후 전송할 수 있습니다.

#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/err.h>

void rsa_encrypt_aes_key(const unsigned char *aes_key, unsigned char *encrypted_key, int *encrypted_len) {
    FILE *fp = fopen("public_key.pem", "rb");
    RSA *rsa = PEM_read_RSA_PUBKEY(fp, NULL, NULL, NULL);
    fclose(fp);

    *encrypted_len = RSA_public_encrypt(32, aes_key, encrypted_key, rsa, RSA_PKCS1_OAEP_PADDING);
    RSA_free(rsa);
}

int main() {
    unsigned char aes_key[32];
    unsigned char encrypted_key[256];
    int encrypted_len;

    RAND_bytes(aes_key, 32);
    rsa_encrypt_aes_key(aes_key, encrypted_key, &encrypted_len);

    printf("AES key encrypted with RSA and ready for transmission.\n");

    return 0;
}

결론


암호화 데이터를 안전하게 저장하고 전송하려면 AES 암호화 후 파일에 저장하고, 네트워크 전송 시 RSA를 활용하여 키를 보호하는 것이 중요합니다.
다음 단계에서는 성능 최적화 및 보안 고려사항을 설명하겠습니다.

성능 최적화 및 보안 고려사항

RSA 및 AES 암호화는 보안성이 뛰어나지만 성능 저하와 보안 취약점을 고려해야 합니다. 본 장에서는 암호화 연산의 성능을 최적화하는 방법과 보안 강화를 위한 주요 고려사항을 다룹니다.


암호화 성능 최적화 방법

암호화는 계산량이 많기 때문에 효율적으로 구현해야 합니다. 성능 최적화 방법은 다음과 같습니다.

1. RSA 대신 하이브리드 암호화 사용


RSA는 연산 비용이 크므로, 대량의 데이터를 암호화할 때는 RSA + AES 하이브리드 암호화를 사용하는 것이 일반적입니다.

  • AES로 데이터를 암호화하고,
  • AES 키를 RSA로 암호화하여 전송하는 방식입니다.
    이를 통해 성능을 높이고 보안을 유지할 수 있습니다.

2. AES-GCM 모드 활용


CBC 모드보다 GCM (Galois/Counter Mode)를 사용하면 병렬 연산이 가능하여 속도가 향상됩니다.

  • AES-CBC: 직렬 연산 방식 → 속도 느림
  • AES-GCM: 병렬 연산 가능 → 속도 빠름

OpenSSL에서 AES-GCM을 사용하려면 EVP_aes_256_gcm()을 호출하면 됩니다.

3. 하드웨어 가속 활용 (AES-NI)


AES-NI(Advanced Encryption Standard New Instructions)는 Intel 및 AMD 프로세서에서 지원하는 하드웨어 가속 기술입니다.
이를 활성화하면 암호화 속도를 크게 향상시킬 수 있습니다.

OpenSSL에서 AES-NI 지원 여부를 확인하려면 다음 명령어를 사용합니다.

openssl speed -evp aes-256-gcm

AES-NI가 활성화된 경우 성능이 최대 10배 향상될 수 있습니다.


보안 고려사항

암호화를 사용할 때 보안성을 유지하기 위해 다음 사항을 고려해야 합니다.

1. 안전한 키 관리

  • AES 키 및 RSA 개인 키 보호
  • 키를 평문으로 저장하지 않고 안전한 보관 방법을 사용해야 합니다.
  • HSM(Hardware Security Module), 환경 변수 또는 안전한 저장소 사용을 권장합니다.
  • RSA 키 크기 선택
  • 2048비트 RSA는 보안성이 충분하지만, 고도의 보안이 필요한 경우 4096비트를 고려할 수 있습니다.

2. 키 재사용 방지

  • 같은 AES 키를 계속 사용하면 보안성이 낮아지므로 세션별로 다른 키를 생성하는 것이 좋습니다.
  • OpenSSL의 RAND_bytes()를 사용하여 새로운 키를 생성해야 합니다.

3. 패딩 공격 방어 (PKCS#1 v1.5 → OAEP 사용)

  • RSA 암호화 시 PKCS#1 v1.5 패딩은 보안 취약점이 존재하므로 OAEP (Optimal Asymmetric Encryption Padding)을 사용해야 합니다.
  • OpenSSL에서는 RSA_public_encrypt()를 호출할 때 RSA_PKCS1_OAEP_PADDING 옵션을 설정합니다.

4. 사이드채널 공격 방어

  • RSA 및 AES 연산은 사이드채널 공격(예: 타이밍 공격, 캐시 공격)에 취약할 수 있습니다.
  • OpenSSL에서 제공하는 Constant-time 연산 함수를 활용하면 방어할 수 있습니다.
  • 예: CRYPTO_memcmp()를 사용하여 비교 연산 수행

최적화된 암호화 코드 예제 (AES-GCM 사용)

아래 코드는 AES-GCM 모드를 활용하여 보안성과 성능을 높인 AES 암호화를 수행하는 예제입니다.

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

#define AES_KEY_SIZE 32
#define AES_IV_SIZE 12  // GCM 모드에서는 12바이트 IV 사용
#define BUFFER_SIZE 128

void aes_gcm_encrypt(const unsigned char *plaintext, int plaintext_len,
                     const unsigned char *key, const unsigned char *iv,
                     unsigned char *ciphertext, int *ciphertext_len) {
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, key, iv);

    int len;
    EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len);
    *ciphertext_len = len;

    EVP_EncryptFinal_ex(ctx, ciphertext + len, &len);
    *ciphertext_len += len;

    EVP_CIPHER_CTX_free(ctx);
}

int main() {
    unsigned char key[AES_KEY_SIZE];
    unsigned char iv[AES_IV_SIZE];
    unsigned char plaintext[BUFFER_SIZE] = "Optimized AES-GCM Encryption";
    unsigned char ciphertext[BUFFER_SIZE];
    int ciphertext_len;

    // AES 키 및 IV 생성
    RAND_bytes(key, AES_KEY_SIZE);
    RAND_bytes(iv, AES_IV_SIZE);

    // AES-GCM 암호화 수행
    aes_gcm_encrypt(plaintext, strlen((char *)plaintext), key, iv, ciphertext, &ciphertext_len);

    printf("Encrypted data (AES-GCM): ");
    for (int i = 0; i < ciphertext_len; i++) {
        printf("%02x", ciphertext[i]);
    }
    printf("\n");

    return 0;
}

결론

  1. RSA + AES 하이브리드 암호화를 활용하면 성능을 최적화할 수 있습니다.
  2. AES-GCM 모드를 사용하면 보안성과 성능을 동시에 향상시킬 수 있습니다.
  3. AES-NI 하드웨어 가속을 활용하면 암호화 연산 속도를 대폭 증가시킬 수 있습니다.
  4. 안전한 키 관리 및 패딩 설정을 통해 보안 취약점을 방어해야 합니다.

다음 장에서는 전체 내용을 요약하여 정리하겠습니다.

요약

본 기사에서는 C 언어에서 OpenSSL을 활용하여 RSA 및 AES 암호화를 구현하는 방법을 단계별로 설명했습니다.

  1. OpenSSL 개요 및 설치: OpenSSL의 기본 개념과 설치 방법을 설명했습니다.
  2. RSA 키 생성 및 암호화: RSA 공개 키와 개인 키를 생성하고, 데이터를 암호화 및 복호화하는 방법을 소개했습니다.
  3. AES 암호화 및 키 관리: AES-256을 사용하여 데이터를 안전하게 암호화하고 복호화하는 방법을 다루었습니다.
  4. 암호화 데이터의 저장 및 전송: RSA로 AES 키를 보호하고, AES 암호화 데이터를 안전하게 저장 및 전송하는 방법을 설명했습니다.
  5. 성능 최적화 및 보안 고려사항: AES-GCM 모드 활용, RSA 하이브리드 암호화, 하드웨어 가속(AES-NI), 키 관리 및 패딩 보안 등을 최적화하는 방법을 제시했습니다.

RSA는 키 교환 및 디지털 서명에 유용하고, AES는 고속 데이터 암호화에 적합합니다. OpenSSL을 활용하면 강력한 암호화 보안을 구축할 수 있으며, 최적화 기법을 적용하면 성능 저하 없이 안전한 시스템을 개발할 수 있습니다.