C언어를 활용해 네트워크 상에서 안전한 통신을 구현하려면 SSL/TLS를 이해하고 적용하는 것이 필수적입니다. SSL/TLS는 데이터 암호화와 인증을 통해 해커로부터 데이터를 보호하며, 오늘날 대부분의 보안 프로토콜의 기본이 됩니다. 본 기사에서는 SSL/TLS의 기본 개념부터 C언어로 안전한 통신을 구현하는 구체적인 방법까지 실용적인 정보를 제공합니다. 이를 통해 보안성이 뛰어난 네트워크 애플리케이션을 개발할 수 있는 기반을 마련할 수 있습니다.
SSL/TLS란 무엇인가?
SSL(보안 소켓 계층)과 TLS(전송 계층 보안)는 인터넷 상에서 데이터를 안전하게 전송하기 위한 보안 프로토콜입니다.
SSL과 TLS의 차이점
SSL은 초기 보안 프로토콜로, 현재는 구식으로 간주됩니다. TLS는 SSL의 개선 버전으로 더 높은 보안성과 효율성을 제공합니다. 최신 환경에서는 TLS를 사용하는 것이 권장됩니다.
SSL/TLS의 주요 기능
- 데이터 암호화: 전송 중 데이터가 노출되지 않도록 암호화합니다.
- 무결성 확인: 데이터가 전송 중에 변조되지 않았음을 보장합니다.
- 인증: 통신 당사자 간의 신뢰를 검증합니다.
SSL/TLS의 동작 원리
SSL/TLS는 클라이언트와 서버 간의 핸드셰이크 과정을 통해 암호화 키를 교환하고, 이후 해당 키를 사용해 데이터를 암호화하여 전송합니다. 이 과정에서 공개 키 암호화 및 대칭 키 암호화가 조합되어 사용됩니다.
SSL/TLS는 안전한 인터넷 통신의 기본이며, C언어에서 이를 활용하면 네트워크 애플리케이션의 보안성을 크게 높일 수 있습니다.
C언어에서 SSL/TLS 사용 준비
필요한 라이브러리
C언어에서 SSL/TLS를 구현하려면 OpenSSL과 같은 SSL/TLS 라이브러리가 필요합니다. OpenSSL은 SSL/TLS 통신 구현을 위한 다양한 API를 제공하며, 대부분의 플랫폼에서 지원됩니다.
개발 환경 설정
- 라이브러리 설치:
- Linux:
apt-get install libssl-dev
(Debian/Ubuntu 기반) - macOS:
brew install openssl
- Windows: OpenSSL 공식 웹사이트에서 Windows용 바이너리를 다운로드 및 설치
- 컴파일러 설정:
OpenSSL 라이브러리를 사용하려면 컴파일러에 헤더 파일과 라이브러리 경로를 설정해야 합니다.
gcc -o program program.c -lssl -lcrypto
헤더 파일 포함
SSL/TLS 기능을 사용하려면 다음 헤더 파일을 포함해야 합니다.
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/bio.h>
SSL/TLS 초기화 코드
SSL/TLS를 사용하기 위해 프로그램의 초기화 단계에서 다음 단계를 수행해야 합니다.
SSL_library_init(); // SSL 라이브러리 초기화
SSL_load_error_strings(); // 오류 문자열 로드
OpenSSL_add_all_algorithms(); // 암호화 알고리즘 로드
환경 테스트
라이브러리가 정상적으로 설치되었는지 확인하려면 간단한 프로그램을 작성하여 SSL/TLS 초기화가 성공적으로 수행되는지 확인합니다.
#include <openssl/ssl.h>
#include <stdio.h>
int main() {
SSL_library_init();
printf("SSL/TLS 라이브러리 초기화 성공\n");
return 0;
}
C언어에서 SSL/TLS를 구현하기 위해 위 준비 단계를 완료하면 본격적인 클라이언트-서버 통신 구현에 착수할 수 있습니다.
OpenSSL 라이브러리 설치 및 설정
OpenSSL 설치
SSL/TLS 통신 구현을 위해 OpenSSL 라이브러리를 설치해야 합니다. 플랫폼별 설치 방법은 다음과 같습니다.
- Linux:
- Debian/Ubuntu:
bash sudo apt-get update sudo apt-get install libssl-dev
- Red Hat/CentOS:
bash sudo yum install openssl-devel
- macOS:
Homebrew를 사용해 설치:
brew install openssl
- Windows:
- OpenSSL 공식 웹사이트(https://www.openssl.org)에서 Windows용 바이너리를 다운로드합니다.
- 설치 후
lib
와include
디렉터리 경로를 C 컴파일러 설정에 추가합니다.
OpenSSL 초기화
OpenSSL을 사용하려면 초기화가 필요합니다. 기본 설정은 아래 코드를 참고하세요.
#include <openssl/ssl.h>
#include <openssl/err.h>
void initialize_openssl() {
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
}
void cleanup_openssl() {
EVP_cleanup();
}
컴파일러 설정
OpenSSL을 사용하는 프로그램을 컴파일하려면 라이브러리 경로와 링크 옵션을 추가해야 합니다.
예시:
gcc -o program program.c -lssl -lcrypto
테스트 코드
OpenSSL 설치와 설정이 제대로 되었는지 확인하려면 간단한 테스트 프로그램을 작성합니다.
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <stdio.h>
int main() {
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
printf("OpenSSL 라이브러리가 성공적으로 초기화되었습니다.\n");
return 0;
}
환경 변수 설정 (Windows 사용 시)
Windows에서는 OpenSSL 설치 디렉터리를 환경 변수로 추가해야 할 수 있습니다.
PATH
변수에 OpenSSL 설치 디렉터리의bin
경로를 추가합니다.INCLUDE
와LIB
변수에 각각 OpenSSL의include
와lib
경로를 추가합니다.
라이브러리 설정 문제 해결
- 컴파일 에러 발생 시: 라이브러리 경로나 파일 이름을 확인하고, 적절히 수정합니다.
- 링크 에러 발생 시:
-lssl -lcrypto
옵션이 누락되지 않았는지 확인합니다.
OpenSSL 설치 및 설정 과정을 완료하면 SSL/TLS를 활용한 네트워크 통신 구현을 시작할 준비가 완료됩니다.
SSL/TLS 인증서 생성 및 관리
SSL/TLS 인증서란?
SSL/TLS 인증서는 클라이언트와 서버 간의 신뢰를 보장하기 위해 사용됩니다. 인증서는 서버 또는 클라이언트의 정체성을 증명하며, 데이터 암호화를 위한 공개 키를 포함합니다.
인증서 생성 절차
OpenSSL을 사용해 자체 서명(Self-Signed) 인증서를 생성할 수 있습니다.
- 개인 키 생성
인증서에 사용될 RSA 개인 키를 생성합니다.
openssl genrsa -out private.key 2048
이 명령어는 2048비트 길이의 개인 키를 private.key
라는 파일에 저장합니다.
- CSR (Certificate Signing Request) 생성
개인 키를 기반으로 인증서 서명 요청 파일을 생성합니다.
openssl req -new -key private.key -out request.csr
실행 중 아래와 같은 정보를 입력해야 합니다:
- 국가 이름(Country Name, 2글자 코드)
- 주/도 이름(State or Province Name)
- 도시 이름(Locality Name)
- 조직 이름(Organization Name)
- 조직 단위(Organizational Unit Name)
- 도메인 이름(Common Name)
- 이메일 주소(Email Address)
- 자체 서명 인증서 생성
CSR 파일과 개인 키를 사용해 인증서를 생성합니다.
openssl x509 -req -days 365 -in request.csr -signkey private.key -out certificate.crt
이 명령은 certificate.crt
라는 파일에 1년 유효기간의 인증서를 생성합니다.
인증서 파일 설명
- private.key: 인증서의 개인 키 파일
- request.csr: 인증서 서명 요청 파일
- certificate.crt: 최종 인증서 파일
인증서 관리
- 유효기간 확인:
인증서의 만료일을 확인합니다.
openssl x509 -enddate -noout -in certificate.crt
- 인증서 상세 정보 확인:
인증서에 포함된 세부 정보를 출력합니다.
openssl x509 -in certificate.crt -text -noout
- 인증서 갱신:
만료된 인증서를 갱신하려면 새로운 CSR을 생성하고, 기존 키를 재사용합니다.
프로그램에서 인증서 사용
SSL/TLS를 사용하는 서버에서는 인증서와 키를 로드해야 합니다.
#include <openssl/ssl.h>
SSL_CTX *initialize_ssl_context() {
SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
if (!ctx) {
perror("Unable to create SSL context");
exit(EXIT_FAILURE);
}
SSL_CTX_use_certificate_file(ctx, "certificate.crt", SSL_FILETYPE_PEM);
SSL_CTX_use_PrivateKey_file(ctx, "private.key", SSL_FILETYPE_PEM);
if (!SSL_CTX_check_private_key(ctx)) {
fprintf(stderr, "Private key does not match the certificate\n");
exit(EXIT_FAILURE);
}
return ctx;
}
보안 권장사항
- 인증서와 개인 키 보안: 인증서와 키 파일은 암호화된 저장소에 보관합니다.
- 신뢰할 수 있는 인증서 사용: 실무에서는 자체 서명 인증서 대신 공인 인증 기관(CA)에서 발급받은 인증서를 사용합니다.
- 정기 갱신: 인증서 만료 전에 갱신 작업을 수행합니다.
위의 절차와 권장사항을 따르면 SSL/TLS 인증서를 효과적으로 생성하고 관리할 수 있습니다.
클라이언트-서버 통신 구현
SSL/TLS 서버 구현
C언어와 OpenSSL을 사용하여 SSL/TLS 서버를 구현하려면 다음 단계를 따릅니다.
- SSL 컨텍스트 초기화
서버 프로그램에서 SSL 컨텍스트를 초기화합니다.
SSL_CTX *initialize_ssl_context() {
SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
if (!ctx) {
perror("Unable to create SSL context");
exit(EXIT_FAILURE);
}
SSL_CTX_use_certificate_file(ctx, "certificate.crt", SSL_FILETYPE_PEM);
SSL_CTX_use_PrivateKey_file(ctx, "private.key", SSL_FILETYPE_PEM);
if (!SSL_CTX_check_private_key(ctx)) {
fprintf(stderr, "Private key does not match the certificate\n");
exit(EXIT_FAILURE);
}
return ctx;
}
- 소켓 설정 및 연결 대기
서버는 TCP 소켓을 생성하고 클라이언트 연결을 대기합니다.
int create_server_socket(int port) {
int sockfd;
struct sockaddr_in addr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("Unable to create socket");
exit(EXIT_FAILURE);
}
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("Unable to bind socket");
exit(EXIT_FAILURE);
}
if (listen(sockfd, 1) < 0) {
perror("Unable to listen on socket");
exit(EXIT_FAILURE);
}
return sockfd;
}
- SSL 연결 처리
클라이언트가 연결되면 SSL 핸드셰이크를 수행합니다.
void handle_ssl_connection(SSL *ssl) {
if (SSL_accept(ssl) <= 0) {
ERR_print_errors_fp(stderr);
} else {
printf("Client connected with SSL\n");
char buffer[1024] = {0};
SSL_read(ssl, buffer, sizeof(buffer));
printf("Received: %s\n", buffer);
SSL_write(ssl, "Hello, SSL client!", 18);
}
SSL_shutdown(ssl);
SSL_free(ssl);
}
SSL/TLS 클라이언트 구현
클라이언트 측에서도 SSL/TLS를 초기화하고 서버와 안전한 연결을 설정해야 합니다.
- SSL 컨텍스트 초기화
클라이언트는 TLS 클라이언트 모드를 설정합니다.
SSL_CTX *initialize_ssl_context() {
SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
if (!ctx) {
perror("Unable to create SSL context");
exit(EXIT_FAILURE);
}
return ctx;
}
- 서버 연결 설정
클라이언트 소켓을 생성하고 서버에 연결합니다.
int create_client_socket(const char *hostname, int port) {
int sockfd;
struct sockaddr_in addr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("Unable to create socket");
exit(EXIT_FAILURE);
}
struct hostent *host = gethostbyname(hostname);
if (!host) {
perror("Unable to resolve hostname");
exit(EXIT_FAILURE);
}
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
memcpy(&addr.sin_addr.s_addr, host->h_addr, host->h_length);
if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("Unable to connect to server");
exit(EXIT_FAILURE);
}
return sockfd;
}
- SSL 통신 처리
클라이언트는 서버와 데이터를 주고받습니다.
void handle_ssl_communication(SSL *ssl) {
SSL_write(ssl, "Hello, SSL server!", 18);
char buffer[1024] = {0};
SSL_read(ssl, buffer, sizeof(buffer));
printf("Received: %s\n", buffer);
}
통합 코드 예제
서버와 클라이언트 코드 각각을 실행하면 안전한 SSL/TLS 기반 통신이 가능합니다. SSL 초기화, 핸드셰이크, 데이터 송수신이 제대로 작동하는지 확인하며 개발합니다.
이로써 C언어 기반 SSL/TLS 클라이언트-서버 통신이 구현됩니다.
데이터 암호화와 복호화 처리
SSL/TLS에서 데이터 암호화
SSL/TLS는 데이터를 안전하게 보호하기 위해 암호화를 사용합니다. 이 과정은 핸드셰이크 단계에서 협상된 대칭 키를 기반으로 수행되며, 암호화된 데이터는 전송 중 해독이 불가능합니다.
암호화된 데이터 송수신
SSL/TLS는 데이터를 암호화하여 송수신하며, OpenSSL 라이브러리를 통해 이를 간단히 구현할 수 있습니다.
- SSL_write
암호화된 데이터를 전송합니다.
const char *message = "Hello, secure world!";
int bytes_sent = SSL_write(ssl, message, strlen(message));
if (bytes_sent > 0) {
printf("Sent: %s\n", message);
} else {
printf("SSL_write error: %d\n", SSL_get_error(ssl, bytes_sent));
}
- SSL_read
암호화된 데이터를 수신하고 복호화합니다.
char buffer[1024] = {0};
int bytes_received = SSL_read(ssl, buffer, sizeof(buffer) - 1);
if (bytes_received > 0) {
printf("Received: %s\n", buffer);
} else {
printf("SSL_read error: %d\n", SSL_get_error(ssl, bytes_received));
}
SSL/TLS의 암호화 알고리즘
SSL/TLS는 대칭 키 암호화와 공개 키 암호화를 결합하여 보안성을 유지합니다.
- 공개 키 암호화: 핸드셰이크 단계에서 사용됩니다. 클라이언트와 서버는 공개 키를 교환하여 대칭 키를 안전하게 협상합니다.
- 대칭 키 암호화: 데이터 전송 중 암호화와 복호화에 사용됩니다. AES, ChaCha20 등의 알고리즘이 일반적으로 사용됩니다.
OpenSSL을 활용한 직접 암호화
OpenSSL은 SSL/TLS 통신 외에도 직접 데이터 암호화/복호화를 지원합니다.
- AES 대칭 키 암호화
OpenSSL의EVP_CIPHER_CTX
API를 사용하여 데이터를 암호화합니다.
void encrypt_data(const unsigned char *key, const unsigned char *iv,
const unsigned char *plaintext, unsigned char *ciphertext) {
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv);
int len;
EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, strlen((char *)plaintext));
EVP_EncryptFinal_ex(ctx, ciphertext + len, &len);
EVP_CIPHER_CTX_free(ctx);
}
- AES 대칭 키 복호화
암호화된 데이터를 복호화합니다.
void decrypt_data(const unsigned char *key, const unsigned char *iv,
const unsigned char *ciphertext, unsigned char *plaintext) {
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv);
int len;
EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, strlen((char *)ciphertext));
EVP_DecryptFinal_ex(ctx, plaintext + len, &len);
EVP_CIPHER_CTX_free(ctx);
}
보안 권장사항
- 강력한 암호화 알고리즘 사용: AES-256, ChaCha20와 같은 강력한 대칭 키 알고리즘을 선택합니다.
- 안전한 키 관리: 대칭 키 및 공개 키는 안전한 방식으로 저장하고 교환해야 합니다.
- TLS 최신 버전 사용: TLS 1.3은 더 강력한 보안과 효율성을 제공하므로 가능한 최신 버전을 채택합니다.
실행 결과 확인
암호화 및 복호화 과정이 올바르게 작동하는지 확인하기 위해 송수신된 데이터가 원래 메시지와 동일한지 테스트합니다. 이를 통해 SSL/TLS 기반 데이터 보호를 성공적으로 구현할 수 있습니다.
통신 오류 디버깅 및 해결 방법
SSL/TLS 통신에서 발생할 수 있는 주요 오류
SSL/TLS 통신을 구현하는 과정에서 다양한 오류가 발생할 수 있습니다. 주요 오류는 다음과 같습니다.
- SSL 핸드셰이크 실패
클라이언트와 서버 간 핸드셰이크가 실패하면 연결이 설정되지 않습니다.
- 원인: 인증서 문제, 불일치하는 프로토콜, 잘못된 키 파일 등
- 인증서 유효성 실패
클라이언트가 서버의 인증서를 신뢰하지 못할 경우 발생합니다.
- 원인: 인증서가 자가 서명되었거나, 인증 기관(CA)에서 발급되지 않음
- 프로토콜 버전 불일치
클라이언트와 서버가 지원하는 SSL/TLS 버전이 다를 경우 발생합니다.
- 원인: 서버는 TLS 1.2만 지원하는데, 클라이언트는 TLS 1.3으로 연결 시도
- 데이터 송수신 오류
암호화된 데이터가 송수신 중 손상되거나 해독되지 않을 때 발생합니다.
- 원인: 잘못된 암호화 키, 네트워크 문제
오류 디버깅 도구
OpenSSL은 SSL/TLS 통신을 디버깅하는 데 유용한 도구와 옵션을 제공합니다.
- SSL 핸드셰이크 디버깅 활성화
OpenSSL의 디버깅 메시지를 활성화하여 문제의 원인을 파악합니다.
SSL_CTX_set_info_callback(ctx, SSL_debug_callback);
- 명령줄 디버깅
OpenSSL 명령줄 도구를 사용하여 연결 상태를 점검합니다.
openssl s_client -connect <server>:<port>
- 서버와 클라이언트 간의 인증서 상태 및 핸드셰이크 과정을 확인할 수 있습니다.
주요 문제 해결 방법
- SSL 핸드셰이크 실패 해결
- 인증서와 개인 키가 일치하는지 확인합니다.
- 클라이언트와 서버에서 동일한 SSL/TLS 프로토콜 버전을 사용합니다.
openssl verify
명령어로 인증서를 검증합니다.bash openssl verify -CAfile ca-cert.pem server-cert.pem
- 인증서 유효성 오류 해결
- 신뢰할 수 있는 인증 기관(CA)에서 발급된 인증서를 사용합니다.
- 자가 서명 인증서를 사용하는 경우, 클라이언트에 CA 인증서를 설치합니다.
- 프로토콜 버전 문제 해결
- 서버와 클라이언트가 동일한 버전을 지원하도록 설정합니다.
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION);
- 데이터 송수신 오류 해결
- 암호화 키와 초기화 벡터(IV)가 정확히 일치하는지 확인합니다.
- 네트워크 연결 상태를 점검하고 패킷 손실 여부를 확인합니다.
코드에서 오류 처리
OpenSSL 함수는 대부분 반환 값을 통해 오류를 나타냅니다.
- SSL_accept 및 SSL_connect
핸드셰이크 과정에서 반환 값을 확인합니다.
if (SSL_accept(ssl) <= 0) {
ERR_print_errors_fp(stderr);
}
- SSL_read 및 SSL_write
데이터 송수신 중 오류 발생 시 로그를 출력합니다.
int ret = SSL_read(ssl, buffer, sizeof(buffer));
if (ret <= 0) {
int err = SSL_get_error(ssl, ret);
printf("SSL_read error: %d\n", err);
}
디버깅 팁
- SSL/TLS의 모든 오류 메시지를 출력하려면 OpenSSL의
ERR_print_errors_fp
를 활용합니다. - Wireshark와 같은 네트워크 분석 도구를 사용하여 패킷을 캡처하고 TLS 세션을 분석합니다.
보안 관련 주의사항
- SSL/TLS 디버깅 시 실제 환경의 민감한 데이터를 노출하지 않도록 주의합니다.
- 오류를 수정할 때 최신 보안 프로토콜과 인증서를 사용하는 것을 권장합니다.
이와 같은 방법을 통해 SSL/TLS 통신 오류를 디버깅하고 해결하여 안정적인 보안 통신을 구축할 수 있습니다.
응용 예제 및 실습
SSL/TLS를 활용한 파일 전송 프로그램
다음은 SSL/TLS를 사용하여 클라이언트와 서버 간에 파일을 안전하게 전송하는 C언어 기반 프로그램의 간단한 예제입니다.
서버 코드
서버는 SSL/TLS 연결을 설정하고 클라이언트로부터 파일 데이터를 수신합니다.
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#define PORT 4443
#define BUFFER_SIZE 1024
void initialize_openssl() {
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
}
void cleanup_openssl() {
EVP_cleanup();
}
SSL_CTX *create_server_context() {
SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
if (!ctx) {
perror("Unable to create SSL context");
exit(EXIT_FAILURE);
}
if (SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM) <= 0 ||
SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
return ctx;
}
void handle_client(SSL *ssl) {
FILE *file = fopen("received_file.txt", "wb");
if (!file) {
perror("Unable to open file");
return;
}
char buffer[BUFFER_SIZE] = {0};
int bytes;
while ((bytes = SSL_read(ssl, buffer, sizeof(buffer))) > 0) {
fwrite(buffer, 1, bytes, file);
}
fclose(file);
printf("File received successfully.\n");
}
int main() {
initialize_openssl();
SSL_CTX *ctx = create_server_context();
int server_sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(PORT),
.sin_addr.s_addr = INADDR_ANY,
};
bind(server_sock, (struct sockaddr *)&addr, sizeof(addr));
listen(server_sock, 1);
printf("Server listening on port %d...\n", PORT);
int client_sock = accept(server_sock, NULL, NULL);
SSL *ssl = SSL_new(ctx);
SSL_set_fd(ssl, client_sock);
if (SSL_accept(ssl) <= 0) {
ERR_print_errors_fp(stderr);
} else {
handle_client(ssl);
}
SSL_free(ssl);
close(client_sock);
close(server_sock);
SSL_CTX_free(ctx);
cleanup_openssl();
return 0;
}
클라이언트 코드
클라이언트는 서버에 연결한 뒤 파일 데이터를 전송합니다.
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#define PORT 4443
#define BUFFER_SIZE 1024
void initialize_openssl() {
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
}
void cleanup_openssl() {
EVP_cleanup();
}
SSL_CTX *create_client_context() {
SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
if (!ctx) {
perror("Unable to create SSL context");
exit(EXIT_FAILURE);
}
return ctx;
}
void send_file(SSL *ssl, const char *filename) {
FILE *file = fopen(filename, "rb");
if (!file) {
perror("Unable to open file");
return;
}
char buffer[BUFFER_SIZE] = {0};
int bytes;
while ((bytes = fread(buffer, 1, sizeof(buffer), file)) > 0) {
SSL_write(ssl, buffer, bytes);
}
fclose(file);
printf("File sent successfully.\n");
}
int main() {
initialize_openssl();
SSL_CTX *ctx = create_client_context();
int client_sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(PORT),
.sin_addr.s_addr = inet_addr("127.0.0.1"),
};
if (connect(client_sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("Unable to connect to server");
exit(EXIT_FAILURE);
}
SSL *ssl = SSL_new(ctx);
SSL_set_fd(ssl, client_sock);
if (SSL_connect(ssl) <= 0) {
ERR_print_errors_fp(stderr);
} else {
send_file(ssl, "sample_file.txt");
}
SSL_free(ssl);
close(client_sock);
SSL_CTX_free(ctx);
cleanup_openssl();
return 0;
}
실행 방법
- 서버 실행:
./server
- 클라이언트 실행:
./client
결과 확인
클라이언트가 전송한 파일(sample_file.txt
)이 서버의 디렉토리에 received_file.txt
로 저장됩니다.
응용 및 확장
- 다중 클라이언트 지원: 서버에서 다중 클라이언트를 처리하도록 스레드를 추가합니다.
- 파일 암호화: 전송 전 추가적인 데이터 암호화를 적용합니다.
- 인증서 검증: 클라이언트가 서버 인증서를 검증하도록 설정합니다.
위 예제를 활용하면 SSL/TLS를 사용한 안전한 파일 전송 프로그램을 구현할 수 있습니다.
요약
SSL/TLS를 활용해 C언어에서 안전한 통신을 구현하는 방법을 학습했습니다. SSL/TLS의 기본 개념부터 인증서 생성, 클라이언트-서버 통신 구현, 데이터 암호화, 오류 디버깅, 파일 전송 예제까지 다루어 실제 네트워크 애플리케이션에서 보안 통신을 구현할 수 있는 기반을 마련했습니다. 이를 통해 안전하고 신뢰할 수 있는 소프트웨어 개발이 가능합니다.