SSL/TLS 란 무엇이고 어떻게 구현하는가? (with Python)

SSL/TLS 란?

SSL(보안 소켓 레이어)과 TLS(전송 계층 보안)는 인터넷에서 데이터를 안전하게 전송하기 위한 표준 기술입니다. 이 기술들은 웹 브라우저와 서버 간의 데이터 전송을 암호화하여 개인정보, 금융정보 등의 중요한 데이터를 보호합니다.

그에 따라 최근에는 다수의 표준에서 TLS 1.2 이상의 버전을 의무적으로 요구하고 있다.

History

  1. SSL 1.0 (1994년): 넷스케이프(Netscape)에서 처음 개발했지만, 공개되지 않았습니다.
  2. SSL 2.0 (1995년): 첫 공개 버전으로, 넷스케이프에 의해 개발되었습니다. 보안 취약점이 있어 널리 사용되지는 않았습니다.
  3. SSL 3.0 (1996년): SSL 2.0의 보안 취약점을 개선한 버전입니다. 오늘날의 SSL/TLS 프로토콜의 기반이 되었습니다.
  4. TLS 1.0 (1999년): SSL 3.0을 기반으로 하여, 국제 인터넷 표준화 기구(IETF)에 의해 표준화된 버전입니다. SSL 대신 TLS라는 새로운 이름이 사용되었습니다.
  5. TLS 1.1 (2006년): TLS 1.0의 약점을 해결한 업데이트 버전입니다.
  6. TLS 1.2 (2008년): 보안을 크게 강화한 중요한 업데이트 버전으로, 새로운 암호화 방법과 알고리즘을 도입했습니다.
  7. TLS 1.3 (2018년): 최신 버전으로, 연결 설정 시간을 단축하고 보안을 더욱 강화했습니다. 이전 버전들과의 호환성 문제를 해결하고, 보다 효율적인 암호화 프로세스를 제공합니다.

인증서(Certificate) 란 ?

SSL/TLS 인증서는 웹사이트가 안전하고 신뢰할 수 있음을 증명하는 디지털 문서입니다.

이 인증서는 웹사이트와 사용자 간의 데이터를 암호화하여, 정보가 안전하게 전송될 수 있도록 합니다.

주요 기능

  1. 암호화: 사용자와 웹사이트 간에 교환되는 데이터를 암호화하여, 제3자가 정보를 읽거나 조작하는 것을 방지합니다.
  2. 신원 검증: 인증서는 웹사이트의 신뢰성을 보장하며, 웹사이트가 실제로 주장하는 조직에 속해 있음을 확인합니다.

구성요소

  1. 인증서의 발급자 (Issuer): 인증서를 발급한 기관의 이름과 정보입니다. 이는 인증서의 신뢰성을 보증합니다.
  2. 인증서의 주체 (Subject): 인증서가 발급된 대상(서버, 조직, 개인)의 정보입니다. 일반적으로 서버의 도메인 이름이 포함됩니다.
  3. 공개 키 (Public Key): 암호화 통신에 사용되는 공개 키입니다. 이 키를 통해 데이터를 안전하게 암호화하고 전송할 수 있습니다.
  4. 유효 기간 (Validity): 인증서의 시작 날짜와 만료 날짜를 나타내며, 이 기간 동안 인증서가 유효합니다.
  5. 인증서 서명 알고리즘 (Signature Algorithm): 인증서가 서명된 알고리즘의 유형입니다. 이는 인증서의 진위를 검증하는 데 사용됩니다.
  6. 인증서 지문 (Fingerprint): 인증서의 무결성을 확인하는 데 사용되는 고유한 해시 값입니다.
  7. 확장 필드 (Extensions): 추가 기능을 제공하는 확장 필드로, 인증서의 사용 범위, 키 사용 제약, 대체 이름 등이 포함될 수 있습니다.
  8. 시리얼 넘버 (Serial Number): 각 인증서에 고유하게 할당된 식별 번호입니다.

인증서 발급 과정

  1. 키 생성: 웹사이트 소유자는 공개 키와 비공개 키를 생성합니다. 비공개 키는 서버에 안전하게 보관되며, 공개 키는 인증서와 함께 공개됩니다.
  2. CSR 생성: Certificate Signing Request (CSR)을 생성합니다. CSR은 웹사이트의 정보(도메인 이름, 조직 이름 등)와 공개 키를 포함합니다.
  3. CSR 제출: 생성된 CSR을 인증 기관(Certificate Authority, CA)에 제출합니다. CA는 웹사이트의 정보와 도메인 소유권을 검증합니다.
  4. 인증서 발급: 검증 과정을 통과하면, CA는 공개 키를 포함하는 디지털 인증서를 발급합니다. 이 인증서는 CA의 디지털 서명이 포함되어 있습니다.
  5. 서버 설치: 발급받은 인증서를 웹 서버에 설치합니다. 이를 통해 클라이언트와의 안전한 통신이 가능해집니다.
SSL/TLS 인증서 발급과정

CA (인증기관, Certificate Authority)

인증 기관(CA)은 디지털 인증서를 발급하고 관리하는 신뢰할 수 있는 기관입니다.

CA는 웹사이트의 신원을 검증하고, 이를 바탕으로 인증서를 발급하여 웹사이트의 신뢰성을 보증합니다.

Self-signed Certificate와 인증된 CA 서명 인증서의 차이

항목CA-issued CertificateSelf-signed certificate
신뢰성보장됨보장되지 않음
보안안전함취약함
사용웹 사이트, 이메일, 원격 접속 등에서 일반적으로 사용됨개인적인 용도로 사용됨

주요 알려진 CA 들은 다음과 같습니다.

  1. Symantec: 웹 보안 분야에서 가장 큰 이름 중 하나로, VeriSign을 인수하며 시장에서 중요한 위치를 차지하고 있습니다.
  2. Comodo CA: 가장 많이 사용되는 CA 중 하나로, 다양한 보안 솔루션과 인증서를 제공합니다.
  3. DigiCert: 고급 SSL 인증서를 제공하며, 특히 Symantec의 인증서 사업을 인수한 후 시장 점유율이 증가했습니다.
  4. Let’s Encrypt: 무료 SSL/TLS 인증서를 제공하는 비영리 기관으로, 사용의 용이성과 접근성으로 인해 인기가 높습니다.
  5. GlobalSign: 전 세계적으로 신뢰받는 CA로, 다양한 기업에 디지털 인증 및 보안 솔루션을 제공합니다.
  6. GoDaddy: 웹 호스팅과 도메인 서비스로 잘 알려진 GoDaddy도 SSL 인증서 서비스를 제공합니다.
  7. Thawte: 세계 최초의 공개 CA 중 하나로, 광범위한 인증서 옵션을 제공합니다.
  8. GeoTrust: 중소기업을 대상으로 하는 SSL 인증서를 제공하며, 비용 효율적인 솔루션으로 인기가 있습니다.
  9. Entrust Datacard: 기업에 중점을 둔 보안 솔루션을 제공하며, 특히 전자상거래 사이트에 적합한 인증서를 제공합니다.
  10. Sectigo: 이전의 Comodo CA로, 다양한 보안 제품과 서비스를 제공합니다.

SSL/TLS 과정

SSL/TLS 과정 설명

TLS는 클라이언트와 서버가 서로의 신원을 확인하고, 데이터를 안전하게 전송하기 위한 프로토콜입니다.

SSL/TLS 과정

  1. 클라이언트 헬로(ClientHello): 클라이언트는 서버에 연결을 시작하면서 ‘ClientHello’ 메시지를 보냅니다. 이 메시지에는 클라이언트가 지원하는 SSL/TLS 버전, 사용 가능한 암호화 알고리즘(암호화 스위트), 클라이언트 랜덤(난수 값), 세션 ID(이전에 연결된 세션이 있을 경우) 등이 포함됩니다.
  2. 서버 헬로(ServerHello): 서버는 ‘ServerHello’ 메시지로 응답합니다. 이 메시지에는 서버가 선택한 암호화 알고리즘, 서버 랜덤, 세션 ID(클라이언트가 제안한 세션을 재사용하기로 결정한 경우) 등이 담겨 있습니다.
  3. 서버 인증서(Server Certificate): 서버는 자신의 인증서를 클라이언트에게 전송합니다. 클라이언트는 이 인증서를 검증하여 서버의 신뢰성을 확인합니다. 인증서에는 서버의 공개 키가 포함되어 있어, 이후의 통신에서 사용됩니다.
  4. 서버 키 교환(Server Key Exchange): (TLS에서만 필요) 특정 암호화 스위트를 사용하는 경우 서버는 추가 키 교환 파라미터를 클라이언트에게 보낼 수 있습니다.
  5. 서버 헬로 종료(ServerHello Done): 이 메시지는 서버가 클라이언트 헬로에 대한 응답을 완료했음을 클라이언트에 알립니다.
  6. 클라이언트 키 교환(Client Key Exchange): 클라이언트는 서버의 공개 키를 사용하여 세션 키를 암호화하여 서버에 전송합니다. 이로써 양쪽은 통신을 암호화하는 데 사용할 공유된 비밀을 가지게 됩니다.
  7. 암호화 사양 변경(Change Cipher Spec): 클라이언트는 이 메시지를 보내어 이후의 메시지부터는 새롭게 협상된 암호화 알고리즘과 키를 사용하겠다는 것을 서버에 알립니다.
  8. 클라이언트 종료(Client Finished): 클라이언트는 핸드셰이크의 모든 메시지를 검증하는 메시지를 보내어 핸드셰이크 절차가 정상적으로 이루어졌음을 확인합니다.
  9. 서버의 암호화 사양 변경(Change Cipher Spec, Server): 서버 또한 클라이언트에게 암호화 사양 변경 메시지를 보내어 암호화 알고리즘 변경을 알립니다.
  10. 서버 종료(Server Finished): 서버도 핸드셰이크의 모든 메시지를 검증하는 메시지를 보내어 핸드셰이크가 정상적으로 완료되었음을 클라이언트에 알립니다.
  11. 암호화 통신
SSL_TLS_통신_과정

구현

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 기법을 사용하기도 하는데,

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()

참고링크

https://www.digicert.com/kr/what-is-ssl-tls-and-https

(AWS) SSL/TLS 인증서란 무엇인가요?

Leave a Comment