개요
본 글에서는 OpenSSL 라이브러리를 활용한 SSL(Secure Socket Layer) 통신의 원리와 구현 방법에 대해 다룬다. SSL은 인터넷 통신에서 데이터의 기밀성과 무결성을 보장하는 암호화 프로토콜로, 현대 보안 통신의 근간을 이루고 있다. OpenSSL은 SSL과 TLS(Transport Layer Security) 프로토콜의 오픈소스 구현체로, 다양한 암호화 기능을 제공한다. 이 글에서는 OpenSSL을 활용하여 클라이언트-서버 간 안전한 통신을 구현하는 방법을 코드 예제와 함께 설명한다.
SSL 통신의 이해
SSL/TLS의 개념
SSL은 Netscape에 의해 개발된 보안 프로토콜로, 현재는 이를 발전시킨 TLS가 표준으로 사용되고 있다. 그러나 관행적으로 두 용어는 혼용되어 사용된다. SSL/TLS는 네트워크 통신에서 다음과 같은 보안 기능을 제공한다:
- 암호화(Encryption): 데이터를 제3자가 읽을 수 없는 형태로 변환
- 인증(Authentication): 통신 상대방의 신원 확인
- 무결성(Integrity): 데이터 전송 중 변조 여부 확인
SSL/TLS 작동 원리
SSL/TLS는 다음과 같은 핸드셰이크 과정을 통해 보안 연결을 설정한다:
- 클라이언트 헬로: 클라이언트가 서버에 연결을 요청하며 지원하는 암호화 알고리즘 목록 전송
- 서버 헬로: 서버가 선택한 암호화 알고리즘과 디지털 인증서 전송
- 인증서 검증: 클라이언트가 서버의 인증서를 검증
- 키 교환: 대칭 암호화에 사용할 세션 키 생성 및 교환
- 보안 통신 시작: 협상된 암호화 알고리즘과 키를 사용하여 암호화된 데이터 전송
OpenSSL 소개
OpenSSL은 SSL/TLS 프로토콜을 구현한 오픈소스 라이브러리로, 다음과 같은 기능을 제공한다:
- SSL/TLS 프로토콜 구현
- 다양한 암호화 알고리즘 제공
- 인증서 생성 및 관리 도구
- 암호화 관련 유틸리티 함수
OpenSSL을 활용한 SSL 구현
인증서 생성
SSL 통신을 위해서는 먼저 인증서가 필요하다. 다음은 자체 서명된(self-signed) 인증서를 생성하는 명령어이다:
# 개인키 생성
openssl genrsa -out server.key 2048
# 인증서 서명 요청(CSR) 생성
openssl req -new -key server.key -out server.csr
# 자체 서명 인증서 생성
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
SSL 서버 구현 예제
다음은 OpenSSL을 사용한 간단한 SSL 서버 구현 예제 코드이다:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
int create_socket(int port) {
int s;
struct sockaddr_in addr;
s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0) {
perror("Unable to create socket");
exit(EXIT_FAILURE);
}
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("Unable to bind");
exit(EXIT_FAILURE);
}
if (listen(s, 1) < 0) {
perror("Unable to listen");
exit(EXIT_FAILURE);
}
return s;
}
SSL_CTX* create_context() {
const SSL_METHOD* method;
SSL_CTX* ctx;
method = TLS_server_method();
ctx = SSL_CTX_new(method);
if (!ctx) {
perror("Unable to create SSL context");
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
return ctx;
}
void configure_context(SSL_CTX* ctx) {
/* 인증서와 개인키 설정 */
if (SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
if (SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
}
int main(int argc, char **argv) {
int sock;
SSL_CTX* ctx;
/* OpenSSL 초기화 */
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
/* SSL 컨텍스트 생성 및 설정 */
ctx = create_context();
configure_context(ctx);
/* 소켓 생성 및 바인드 */
sock = create_socket(4433);
/* 클라이언트 연결 수락 */
while(1) {
struct sockaddr_in addr;
unsigned int len = sizeof(addr);
SSL* ssl;
int client = accept(sock, (struct sockaddr*)&addr, &len);
if (client < 0) {
perror("Unable to accept");
exit(EXIT_FAILURE);
}
/* SSL 객체 생성 */
ssl = SSL_new(ctx);
SSL_set_fd(ssl, client);
/* SSL 핸드셰이크 수행 */
if (SSL_accept(ssl) <= 0) {
ERR_print_errors_fp(stderr);
} else {
/* 데이터 수신 및 응답 */
char buf[1024];
int bytes;
bytes = SSL_read(ssl, buf, sizeof(buf));
buf[bytes] = 0;
printf("Received: \"%s\"\n", buf);
const char reply[] = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nSecure connection established!\r\n";
SSL_write(ssl, reply, strlen(reply));
}
/* 연결 종료 */
SSL_shutdown(ssl);
SSL_free(ssl);
close(client);
}
/* 리소스 정리 */
close(sock);
SSL_CTX_free(ctx);
EVP_cleanup();
return 0;
}
SSL 클라이언트 구현 예제
다음은 OpenSSL을 사용한 간단한 SSL 클라이언트 구현 예제 코드이다:
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
int create_socket(const char* host, int port) {
int s;
struct sockaddr_in addr;
s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0) {
perror("Unable to create socket");
exit(EXIT_FAILURE);
}
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(host);
if (connect(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("Unable to connect");
exit(EXIT_FAILURE);
}
return s;
}
SSL_CTX* create_context() {
const SSL_METHOD* method;
SSL_CTX* ctx;
method = TLS_client_method();
ctx = SSL_CTX_new(method);
if (!ctx) {
perror("Unable to create SSL context");
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
return ctx;
}
int main(int argc, char **argv) {
int sock;
SSL_CTX* ctx;
SSL* ssl;
int bytes;
char hostname[] = "127.0.0.1";
char request[] = "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n";
char buf[1024];
/* OpenSSL 초기화 */
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
/* SSL 컨텍스트 생성 */
ctx = create_context();
/* 소켓 생성 및 연결 */
sock = create_socket(hostname, 4433);
/* SSL 객체 생성 */
ssl = SSL_new(ctx);
SSL_set_fd(ssl, sock);
/* SSL 연결 수행 */
if (SSL_connect(ssl) <= 0) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
printf("Connected with %s encryption\n", SSL_get_cipher(ssl));
/* 요청 전송 */
SSL_write(ssl, request, strlen(request));
/* 응답 수신 */
bytes = SSL_read(ssl, buf, sizeof(buf) - 1);
buf[bytes] = 0;
printf("Received: \"%s\"\n", buf);
/* 연결 종료 */
SSL_shutdown(ssl);
SSL_free(ssl);
close(sock);
SSL_CTX_free(ctx);
EVP_cleanup();
return 0;
}
컴파일 및 실행
위의 예제 코드를 컴파일하고 실행하는 방법은 다음과 같다:
# 서버 컴파일
gcc -o ssl_server ssl_server.c -lssl -lcrypto
# 클라이언트 컴파일
gcc -o ssl_client ssl_client.c -lssl -lcrypto
# 서버 실행
./ssl_server
# 다른 터미널에서 클라이언트 실행
./ssl_client
OpenSSL 주요 함수 설명
초기화 및 컨텍스트 관련 함수
- SSL_library_init(): OpenSSL 라이브러리 초기화
- SSL_load_error_strings(): 오류 문자열 로드
- SSL_CTX_new(): SSL 컨텍스트 생성
- SSL_CTX_free(): SSL 컨텍스트 해제
- SSL_CTX_use_certificate_file(): 인증서 파일 로드
- SSL_CTX_use_PrivateKey_file(): 개인키 파일 로드
SSL 연결 관련 함수
- SSL_new(): SSL 객체 생성
- SSL_free(): SSL 객체 해제
- SSL_set_fd(): SSL 객체와 소켓 연결
- SSL_connect(): 클라이언트 측 SSL 핸드셰이크 수행
- SSL_accept(): 서버 측 SSL 핸드셰이크 수행
- SSL_shutdown(): SSL 연결 종료
데이터 송수신 함수
- SSL_read(): SSL 연결을 통해 데이터 수신
- SSL_write(): SSL 연결을 통해 데이터 전송
- SSL_get_error(): SSL 작업 중 발생한 오류 확인
결론
본 글에서는 OpenSSL을 활용한 SSL 통신의 기본 원리와 구현 방법에 대해 살펴보았다. SSL/TLS는 현대 인터넷 보안의 핵심 요소로, 전자상거래, 온라인 뱅킹, 메시징 등 다양한 서비스에서 데이터의 안전한 전송을 위해 사용된다. OpenSSL은 이러한 SSL/TLS 프로토콜을 구현한 가장 널리 사용되는 오픈소스 라이브러리로, 본 글에서 제시한 예제 코드를 통해 기본적인 SSL 서버와 클라이언트를 구현할 수 있다.
실제 프로덕션 환경에서는 인증서 관리, 암호화 알고리즘 선택, 보안 취약점 대응 등 추가적인 고려사항이 많이 있다. 또한 OpenSSL API는 매우 방대하므로, 더 복잡한 기능을 구현하기 위해서는 공식 문서와 레퍼런스를 참고하는 것이 좋다. 암호화 통신을 구현할 때는 항상 최신 보안 권고사항을 따르고, 주기적으로 보안 업데이트를 적용하는 것이 중요하다.
참고문헌
- OpenSSL 공식 문서. https://www.openssl.org/docs/
- Viega, J., Messier, M., & Chandra, P. (2002). Network Security with OpenSSL: Cryptography for Secure Communications. O'Reilly Media.
- Rescorla, E. (2000). SSL and TLS: Designing and Building Secure Systems. Addison-Wesley Professional.
- Dierks, T., & Rescorla, E. (2008). The Transport Layer Security (TLS) Protocol Version 1.2. RFC 5246.
- Man-in-the-Middle Attacks on SSL and TLS. (2020). OWASP Foundation.
- Thomas, S. (2000). SSL and TLS Essentials: Securing the Web. John Wiley & Sons.
- Stallings, W. (2017). Cryptography and Network Security: Principles and Practice (7th ed.). Pearson.
'Programming > C' 카테고리의 다른 글
popen 함수 (0) | 2025.03.19 |
---|---|
C언어로 하는 TCP/IP 소켓 통신 (4) | 2025.03.18 |
unistd.h의 getopt() 함수를 활용한 argument 핸들링 (0) | 2025.03.15 |
if-else 구조 vs lookup table구조 (0) | 2025.03.13 |
YAML 개요 및 libyaml 사용법 (0) | 2025.03.10 |