SSL/TLS 란?
SSL(보안 소켓 레이어)과 TLS(전송 계층 보안)는 인터넷에서 데이터를 안전하게 전송하기 위한 표준 기술입니다. 이 기술들은 웹 브라우저와 서버 간의 데이터 전송을 암호화하여 개인정보, 금융정보 등의 중요한 데이터를 보호합니다.
그에 따라 최근에는 다수의 표준에서 TLS 1.2 이상의 버전을 의무적으로 요구하고 있다.
History
- SSL 1.0 (1994년): 넷스케이프(Netscape)에서 처음 개발했지만, 공개되지 않았습니다.
- SSL 2.0 (1995년): 첫 공개 버전으로, 넷스케이프에 의해 개발되었습니다. 보안 취약점이 있어 널리 사용되지는 않았습니다.
- SSL 3.0 (1996년): SSL 2.0의 보안 취약점을 개선한 버전입니다. 오늘날의 SSL/TLS 프로토콜의 기반이 되었습니다.
- TLS 1.0 (1999년): SSL 3.0을 기반으로 하여, 국제 인터넷 표준화 기구(IETF)에 의해 표준화된 버전입니다. SSL 대신 TLS라는 새로운 이름이 사용되었습니다.
- TLS 1.1 (2006년): TLS 1.0의 약점을 해결한 업데이트 버전입니다.
- TLS 1.2 (2008년): 보안을 크게 강화한 중요한 업데이트 버전으로, 새로운 암호화 방법과 알고리즘을 도입했습니다.
- TLS 1.3 (2018년): 최신 버전으로, 연결 설정 시간을 단축하고 보안을 더욱 강화했습니다. 이전 버전들과의 호환성 문제를 해결하고, 보다 효율적인 암호화 프로세스를 제공합니다.
인증서(Certificate) 란 ?
SSL/TLS 인증서는 웹사이트가 안전하고 신뢰할 수 있음을 증명하는 디지털 문서입니다.
이 인증서는 웹사이트와 사용자 간의 데이터를 암호화하여, 정보가 안전하게 전송될 수 있도록 합니다.
주요 기능
- 암호화: 사용자와 웹사이트 간에 교환되는 데이터를 암호화하여, 제3자가 정보를 읽거나 조작하는 것을 방지합니다.
- 신원 검증: 인증서는 웹사이트의 신뢰성을 보장하며, 웹사이트가 실제로 주장하는 조직에 속해 있음을 확인합니다.
구성요소
- 인증서의 발급자 (Issuer): 인증서를 발급한 기관의 이름과 정보입니다. 이는 인증서의 신뢰성을 보증합니다.
- 인증서의 주체 (Subject): 인증서가 발급된 대상(서버, 조직, 개인)의 정보입니다. 일반적으로 서버의 도메인 이름이 포함됩니다.
- 공개 키 (Public Key): 암호화 통신에 사용되는 공개 키입니다. 이 키를 통해 데이터를 안전하게 암호화하고 전송할 수 있습니다.
- 유효 기간 (Validity): 인증서의 시작 날짜와 만료 날짜를 나타내며, 이 기간 동안 인증서가 유효합니다.
- 인증서 서명 알고리즘 (Signature Algorithm): 인증서가 서명된 알고리즘의 유형입니다. 이는 인증서의 진위를 검증하는 데 사용됩니다.
- 인증서 지문 (Fingerprint): 인증서의 무결성을 확인하는 데 사용되는 고유한 해시 값입니다.
- 확장 필드 (Extensions): 추가 기능을 제공하는 확장 필드로, 인증서의 사용 범위, 키 사용 제약, 대체 이름 등이 포함될 수 있습니다.
- 시리얼 넘버 (Serial Number): 각 인증서에 고유하게 할당된 식별 번호입니다.
인증서 발급 과정
- 키 생성: 웹사이트 소유자는 공개 키와 비공개 키를 생성합니다. 비공개 키는 서버에 안전하게 보관되며, 공개 키는 인증서와 함께 공개됩니다.
- CSR 생성: Certificate Signing Request (CSR)을 생성합니다. CSR은 웹사이트의 정보(도메인 이름, 조직 이름 등)와 공개 키를 포함합니다.
- CSR 제출: 생성된 CSR을 인증 기관(Certificate Authority, CA)에 제출합니다. CA는 웹사이트의 정보와 도메인 소유권을 검증합니다.
- 인증서 발급: 검증 과정을 통과하면, CA는 공개 키를 포함하는 디지털 인증서를 발급합니다. 이 인증서는 CA의 디지털 서명이 포함되어 있습니다.
- 서버 설치: 발급받은 인증서를 웹 서버에 설치합니다. 이를 통해 클라이언트와의 안전한 통신이 가능해집니다.
CA (인증기관, Certificate Authority)
인증 기관(CA)은 디지털 인증서를 발급하고 관리하는 신뢰할 수 있는 기관입니다.
CA는 웹사이트의 신원을 검증하고, 이를 바탕으로 인증서를 발급하여 웹사이트의 신뢰성을 보증합니다.
Self-signed Certificate와 인증된 CA 서명 인증서의 차이
항목 | CA-issued Certificate | Self-signed certificate |
---|---|---|
신뢰성 | 보장됨 | 보장되지 않음 |
보안 | 안전함 | 취약함 |
사용 | 웹 사이트, 이메일, 원격 접속 등에서 일반적으로 사용됨 | 개인적인 용도로 사용됨 |
주요 알려진 CA 들은 다음과 같습니다.
- Symantec: 웹 보안 분야에서 가장 큰 이름 중 하나로, VeriSign을 인수하며 시장에서 중요한 위치를 차지하고 있습니다.
- Comodo CA: 가장 많이 사용되는 CA 중 하나로, 다양한 보안 솔루션과 인증서를 제공합니다.
- DigiCert: 고급 SSL 인증서를 제공하며, 특히 Symantec의 인증서 사업을 인수한 후 시장 점유율이 증가했습니다.
- Let’s Encrypt: 무료 SSL/TLS 인증서를 제공하는 비영리 기관으로, 사용의 용이성과 접근성으로 인해 인기가 높습니다.
- GlobalSign: 전 세계적으로 신뢰받는 CA로, 다양한 기업에 디지털 인증 및 보안 솔루션을 제공합니다.
- GoDaddy: 웹 호스팅과 도메인 서비스로 잘 알려진 GoDaddy도 SSL 인증서 서비스를 제공합니다.
- Thawte: 세계 최초의 공개 CA 중 하나로, 광범위한 인증서 옵션을 제공합니다.
- GeoTrust: 중소기업을 대상으로 하는 SSL 인증서를 제공하며, 비용 효율적인 솔루션으로 인기가 있습니다.
- Entrust Datacard: 기업에 중점을 둔 보안 솔루션을 제공하며, 특히 전자상거래 사이트에 적합한 인증서를 제공합니다.
- Sectigo: 이전의 Comodo CA로, 다양한 보안 제품과 서비스를 제공합니다.
SSL/TLS 과정
SSL/TLS 과정 설명
TLS는 클라이언트와 서버가 서로의 신원을 확인하고, 데이터를 안전하게 전송하기 위한 프로토콜입니다.
SSL/TLS 과정
- 클라이언트 헬로(ClientHello): 클라이언트는 서버에 연결을 시작하면서 ‘ClientHello’ 메시지를 보냅니다. 이 메시지에는 클라이언트가 지원하는 SSL/TLS 버전, 사용 가능한 암호화 알고리즘(암호화 스위트), 클라이언트 랜덤(난수 값), 세션 ID(이전에 연결된 세션이 있을 경우) 등이 포함됩니다.
- 서버 헬로(ServerHello): 서버는 ‘ServerHello’ 메시지로 응답합니다. 이 메시지에는 서버가 선택한 암호화 알고리즘, 서버 랜덤, 세션 ID(클라이언트가 제안한 세션을 재사용하기로 결정한 경우) 등이 담겨 있습니다.
- 서버 인증서(Server Certificate): 서버는 자신의 인증서를 클라이언트에게 전송합니다. 클라이언트는 이 인증서를 검증하여 서버의 신뢰성을 확인합니다. 인증서에는 서버의 공개 키가 포함되어 있어, 이후의 통신에서 사용됩니다.
- 서버 키 교환(Server Key Exchange): (TLS에서만 필요) 특정 암호화 스위트를 사용하는 경우 서버는 추가 키 교환 파라미터를 클라이언트에게 보낼 수 있습니다.
- 서버 헬로 종료(ServerHello Done): 이 메시지는 서버가 클라이언트 헬로에 대한 응답을 완료했음을 클라이언트에 알립니다.
- 클라이언트 키 교환(Client Key Exchange): 클라이언트는 서버의 공개 키를 사용하여 세션 키를 암호화하여 서버에 전송합니다. 이로써 양쪽은 통신을 암호화하는 데 사용할 공유된 비밀을 가지게 됩니다.
- 암호화 사양 변경(Change Cipher Spec): 클라이언트는 이 메시지를 보내어 이후의 메시지부터는 새롭게 협상된 암호화 알고리즘과 키를 사용하겠다는 것을 서버에 알립니다.
- 클라이언트 종료(Client Finished): 클라이언트는 핸드셰이크의 모든 메시지를 검증하는 메시지를 보내어 핸드셰이크 절차가 정상적으로 이루어졌음을 확인합니다.
- 서버의 암호화 사양 변경(Change Cipher Spec, Server): 서버 또한 클라이언트에게 암호화 사양 변경 메시지를 보내어 암호화 알고리즘 변경을 알립니다.
- 서버 종료(Server Finished): 서버도 핸드셰이크의 모든 메시지를 검증하는 메시지를 보내어 핸드셰이크가 정상적으로 완료되었음을 클라이언트에 알립니다.
- 암호화 통신
구현
Self-signed 인증서 생성
OpenSSL을 사용하여 SSL 통신을 위한 ca_cert.pem
(CA 인증서), certificate.pem
(서버 인증서), 그리고 privatekey.pem
(서버의 비공개 키)를 생성하는 방법을 안내해드리겠습니다.
1. CA(인증 기관) 개인키와 인증서 생성
CA의 개인 키(ca_privatekey.pem
)와 인증서(ca_cert.pem
)를 생성합니다.
본 인증서는 테스트를 위한 목적으로 100년 유효 기간으로 설정합니다.
# Generate CA's private key openssl genrsa -out ca_privatekey.pem 2048 # Generate CA's self-signed certificate openssl req -new -x509 -key ca_privatekey.pem -out ca_cert.pem -days 36500
2. 서버의 개인키와 인증서 생성
# Generate private key on server openssl genrsa -out privatekey.pem 2048 # Generate a certificate signing request (CSR) for the server openssl req -new -key privatekey.pem -out server.csr # Sign the server certificate using the CA openssl x509 -req -in server.csr -CA ca_cert.pem -CAkey ca_privatekey.pem -CAcreateserial -out certificate.pem -days 36500
3. 인증서 생성시 물어보는 정보
문구 | 설명 | Example |
---|---|---|
Country Name (C) | 인증서를 발급받은 국가 | 한국(KR) |
State or Province Name (ST) | 인증서를 발급받은 주 | Gyeonggi-do |
Locality Name (L) | 인증서를 발급받은 도시 | Anyang |
Organization Name (O) | 인증서를 발급받은 조직 | My Coporation |
Organizational Unit Name (OU) | 조직 내의 특정 부서 | R&D |
Common Name (CN) | 인증서를 사용하는 컴퓨터의 이름 | My Server |
Email Address (E) | 인증서를 발급받은 조직의 이메일 주소 |
SSL/TLS 적용
사실 요즘 제공되는 대부분의 Framework은 Http를 기본으로 해서 Https(SSL/TLS)를 기본 제공하지만
C++ 같은 언어로 SSL/TLS를 적용하기란 여간 쉽지 않다.
그래서 다음과 같은 Tunneling 기법을 사용하기도 하는데,
이번 예제에서는 Python을 통해 서버와 Client에 SSL/TLS를 적용하여 터널링하는 Server / Client 샘플 코드를 공유합니다.
일부 미흡한 부분이 있어 개선이 필요합니다.
server_ssl_tunnel.py
# server_ssl_tunnel.py import socket import ssl import signal import sys def create_ssl_server(certfile, keyfile, port): print('Server) Creating SSL server on port {}...'.format(port)) context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) context.load_cert_chain(certfile=certfile, keyfile=keyfile) context.verify_mode = ssl.CERT_NONE server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) print('Server) Binding to port {}...'.format(port)) server_socket.bind(('10.14.0.147', port)) print('Server) Listening...') server_socket.listen(1) print('Server) Accepting connections...') return context.wrap_socket(server_socket, server_side=True) def forward_data(ssl_port, target_port, certfile, keyfile): ssl_server = create_ssl_server(certfile, keyfile, ssl_port) ssl_server.settimeout(10) # Set a timeout on blocking socket operations while True: try: ssl_conn, _ = ssl_server.accept() with ssl_conn: ssl_conn.settimeout(5) # Setting for timeout (s) target_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) print('Server) Connecting to target on port {}...'.format(target_port)) target_socket.connect(('10.14.0.147', target_port)) with target_socket: while True: print('Server) Waiting for data from client...') data = ssl_conn.recv(4096) if not data: break print('Server) Sending data to target...') target_socket.sendall(data) response = target_socket.recv(4096) print('Server) Sending data to client...') ssl_conn.sendall(response) except socket.timeout: continue except KeyboardInterrupt: break def signal_handler(sig, frame): print('Interrupt received, shutting down...') sys.exit(0) def main(): signal.signal(signal.SIGINT, signal_handler) forward_data(55002, 104, 'server_certificate.pem', 'server_privatekey.pem') if __name__ == '__main__': main()
client_ssl_tunnel
# client_ssl_tunnel.py import socket import ssl import threading import signal import sys def create_ssl_connection(server_hostname, server_port, ca_cert_file): context = ssl.create_default_context() context.load_verify_locations(ca_cert_file) context.check_hostname = False context.verify_mode = ssl.CERT_NONE wrapped_socket = context.wrap_socket(socket.socket(socket.AF_INET), server_hostname=server_hostname) print('Client) Connecting to server on port {}...'.format(server_port)) wrapped_socket.connect((server_hostname, server_port)) return wrapped_socket def tunnel_data(local_port, remote_host, remote_port, ca_cert_file): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as local_socket: local_socket.bind(('10.14.0.147', local_port)) # Bind to the local host and port print('Client) Listening...') local_socket.listen(1) local_socket.settimeout(10) # Set a timeout on blocking socket operations while True: try: print('Client) Accepting connections...') client_socket, addr = local_socket.accept() with client_socket: client_socket.settimeout(5) # 타임아웃 설정 ssl_conn = create_ssl_connection(remote_host, remote_port, ca_cert_file) while True: data = client_socket.recv(4096) print('Client) Waiting for data from client...') if not data: break print('Client) Sending data to server...') ssl_conn.sendall(data) response = ssl_conn.recv(4096) print('Client) Sending data to client...') client_socket.sendall(response) except socket.timeout: continue except KeyboardInterrupt: break def signal_handler(sig, frame): print('Interrupt received, shutting down...') sys.exit(0) def main(): signal.signal(signal.SIGINT, signal_handler) tunnel_data(55001, '10.14.0.147', 55002, 'ca_cert.pem') if __name__ == '__main__': main()