ssl — 소켓 객체를 위한 TLS/SSL 래퍼

ssl — 소켓 객체를 위한 TLS/SSL 래퍼

socket 객체를 감싸서 네트워크 소켓에 Transport Layer Security("Secure Sockets Layer"라고도 함) 암호화와 피어 인증 기능을 클라이언트/서버 양쪽에서 제공하는 모듈이에요. OpenSSL 라이브러리를 사용해요. 선택적(optional) 모듈이고 WASI에서는 사용할 수 없어요.

출처: Python 표준 라이브러리

본문

경고: 이 모듈을 사용하기 전에 반드시 보안 고려사항(Security considerations)을 읽으세요. ssl 모듈의 기본 설정이 응용에 항상 적절한 것은 아니므로, 읽지 않으면 안전하다는 잘못된 인식을 가질 수 있어요.

이 모듈은 socket.socket 타입에서 파생된 ssl.SSLSocket 클래스를 제공해요. 소켓을 오가는 데이터를 SSL로 암호화/복호화하는 소켓류 래퍼예요. getpeercert()(연결 상대의 인증서 조회), cipher()(보안 연결에 쓰이는 암호화 방식 조회), get_verified_chain(), get_unverified_chain()(인증서 체인 조회) 같은 추가 메서드를 지원해요. 더 정교한 응용은 ssl.SSLContext 클래스로 설정과 인증서를 관리하고, SSLContext.wrap_socket()으로 만든 SSL 소켓이 이를 상속받게 해요.

3.5.3에서 OpenSSL 1.1.0 링크 지원. 3.10에서 PEP 644 구현 — OpenSSL 1.1.1 이상 필요.

소켓 생성

SSLSocket 인스턴스는 반드시 SSLContext.wrap_socket() 메서드로 만들어야 해요. create_default_context() 헬퍼는 안전한 기본 설정을 가진 새 컨텍스트를 반환해요.

기본 컨텍스트 + IPv4/IPv6 듀얼 스택 클라이언트 예:

import socket
import ssl

hostname = 'www.python.org'
context = ssl.create_default_context()

with socket.create_connection((hostname, 443)) as sock:
    with context.wrap_socket(sock, server_hostname=hostname) as ssock:
        print(ssock.version())

커스텀 컨텍스트 + IPv4 클라이언트 예:

hostname = 'www.python.org'
# PROTOCOL_TLS_CLIENT requires valid cert chain and hostname
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.load_verify_locations('path/to/cabundle.pem')

with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
    with context.wrap_socket(sock, server_hostname=hostname) as ssock:
        print(ssock.version())

localhost IPv4에서 듣는 서버 소켓 예:

context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain('/path/to/certchain.pem', '/path/to/private.key')

with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
    sock.bind(('127.0.0.1', 8443))
    sock.listen(5)
    with context.wrap_socket(sock, server_side=True) as ssock:
        conn, addr = ssock.accept()
        ...

컨텍스트 생성

ssl.create_default_context(purpose=Purpose.SERVER_AUTH, *, cafile=None, capath=None, cadata=None) 주어진 목적에 맞는 기본 설정으로 새 SSLContext를 반환해요. 보통 SSLContext 생성자를 직접 호출할 때보다 높은 보안 수준을 나타내요. cafile, capath, cadata는 인증서 검증에 신뢰할 선택적 CA 인증서예요. 셋 다 None이면 시스템 기본 CA 인증서를 신뢰할 수 있어요.

설정은 PROTOCOL_TLS_CLIENT 또는 PROTOCOL_TLS_SERVER, OP_NO_SSLv2, OP_NO_SSLv3과, RC4와 비인증 암호화 방식이 없는 고강도 암호 스위트예요. SERVER_AUTH를 purpose로 넘기면 verify_modeCERT_REQUIRED로 설정하고 CA 인증서를 로드해요. 기본 설정에 VERIFY_X509_PARTIAL_CHAINVERIFY_X509_STRICT가 포함돼요.

기본 설정이 더 제한적인 값으로 언제든 바뀔 수 있어요. 구식 SSL 3.0 클라이언트와 호환을 유지하려면:

ctx = ssl.create_default_context(Purpose.CLIENT_AUTH)
ctx.options &= ~ssl.OP_NO_SSLv3

VERIFY_X509_STRICT를 끄려면(권장되진 않음):

ctx = ssl.create_default_context()
ctx.verify_flags &= ~ssl.VERIFY_X509_STRICT

3.4 추가. 3.4.4에서 RC4 제거, 3.6에서 ChaCha20/Poly1305 추가·3DES 제거, 3.8에서 SSLKEYLOGFILE 키 로깅 지원, 3.10에서 PROTOCOL_TLS_CLIENT/PROTOCOL_TLS_SERVER 사용, 3.13에서 기본 verify 플래그 변경.

예외

  • exception ssl.SSLError — 기본 SSL 구현(OpenSSL 라이브러리)의 오류를 알리기 위해 발생. 하위 네트워크 연결 위에 얹힌 상위 암호화/인증 계층의 문제를 나타내며, OSError의 서브타입이에요. 3.3에서 OSError의 서브타입이 됨. library(오류가 발생한 OpenSSL 하위 모듈, 예: SSL, PEM, X509), reason(오류 이유, 예: CERTIFICATE_VERIFY_FAILED) 속성을 가져요.
  • exception ssl.SSLZeroReturnError — 읽기/쓰기 시도 중 SSL 연결이 깨끗하게 닫힌 경우. 3.3 추가.
  • exception ssl.SSLWantReadError — 논블로킹 SSL 소켓에서 읽기/쓰기를 시도했지만, 요청을 처리하기 전에 기본 TCP 전송에서 더 많은 데이터를 받아야 하는 경우. 3.3 추가.
  • exception ssl.SSLWantWriteError — 논블로킹 SSL 소켓에서 읽기/쓰기를 시도했지만 기본 TCP 전송에 더 많은 데이터를 보내야 하는 경우. 3.3 추가.
  • exception ssl.SSLSyscallError — SSL 소켓 연산 중 시스템 오류. 3.3 추가.
  • exception ssl.SSLEOFError — SSL 연결이 갑자기 종료된 경우. 3.3 추가.
  • exception ssl.SSLCertVerificationError — 인증서 검증 실패. 3.7 추가. verify_code(숫자 오류 번호), verify_message(사람이 읽을 수 있는 오류 문자열) 속성.
  • exception ssl.CertificateErrorSSLCertVerificationError의 별칭. 3.7에서 별칭이 됨.

난수 생성

  • ssl.RAND_bytes(num, /)num개의 암호학적으로 강한 의사 난수 바이트를 반환해요. PRNG가 충분히 시드되지 않았으면 SSLError. 거의 모든 응용에서 os.urandom()이 더 낫다. 3.3 추가.
  • ssl.RAND_status() — SSL 의사 난수 생성기가 '충분한' 난수로 시드됐으면 True. 3.3 추가.
  • ssl.RAND_add(bytes, entropy, /) — 주어진 바이트를 SSL 의사 난수 생성기에 섞어요. entropy는 문자열에 포함된 엔트로피의 하한(항상 0.0을 써도 됨). RFC 1750 참고. 3.5에서 쓰기 가능한 bytes류 객체 허용.

인증서 처리

  • ssl.cert_time_to_seconds(cert_time) — 인증서의 "notBefore"/"notAfter" 날짜를 나타내는 "%b %d %H:%M:%S %Y %Z" strptime 형식(C 로케일) 문자열을 epoch 이후 초로 반환해요:
>>> import ssl
>>> import datetime as dt
>>> timestamp = ssl.cert_time_to_seconds("Jan  5 09:34:43 2018 GMT")
>>> timestamp
1515144883
>>> print(dt.datetime.fromtimestamp(timestamp, dt.UTC))
2018-01-05 09:34:43+00:00

"notBefore"/"notAfter" 날짜는 GMT(RFC 5280)여야 해요.

  • ssl.get_server_certificate(addr, ssl_version=PROTOCOL_TLS_CLIENT, ca_certs=None[, timeout]) — SSL 보호 서버의 주소 addr((hostname, port) 쌍)로 서버 인증서를 가져와 PEM 인코딩 문자열로 반환해요. 3.3에서 IPv6 호환, 3.5에서 기본 ssl_version이 PROTOCOL_SSLv3에서 PROTOCOL_TLS로, 3.10에서 timeout 추가.
  • ssl.DER_cert_to_PEM_cert(der_cert_bytes) — DER 인코딩 바이트를 PEM 인코딩 문자열로 변환.
  • ssl.PEM_cert_to_DER_cert(pem_cert_string) — ASCII PEM 문자열을 DER 인코딩 바이트로 변환.
  • ssl.get_default_verify_paths() — OpenSSL의 기본 cafile/capath 경로를 담은 named tuple DefaultVerifyPaths 반환. 필드: cafile, capath, openssl_cafile_env, openssl_cafile, openssl_capath_env, openssl_capath. 3.4 추가.
  • ssl.enum_certificates(store_name) — Windows 시스템 인증서 저장소에서 인증서 조회. store_nameCA, ROOT, MY 중 하나. (cert_bytes, encoding_type, trust) 튜플 리스트 반환. encoding_typex509_asn 또는 pkcs_7_asn. 가용성: Windows. 3.4 추가.
  • ssl.enum_crls(store_name) — Windows 시스템 인증서 저장소에서 CRL 조회. 가용성: Windows. 3.4 추가.

상수

모든 상수는 이제 enum.IntEnum 또는 enum.IntFlag 컬렉션이에요. 3.6 추가.

검증 모드(SSLContext.verify_mode):

  • ssl.CERT_NONE — 기본 모드(PROTOCOL_TLS_CLIENT 제외). 클라이언트 쪽에서 거의 모든 인증서를 허용하고 검증 오류(신뢰되지 않거나 만료된 인증서)는 무시돼요. 서버 모드에서는 클라이언트에게 인증서를 요청하지 않아요.

  • ssl.CERT_OPTIONAL — 클라이언트 모드에서 CERT_REQUIRED와 같은 의미(클라이언트에는 CERT_REQUIRED 사용 권장). 서버 모드에서 클라이언트 인증서 요청을 보내되, 보내면 검증하고 검증 오류 시 TLS 핸드셰이크를 중단해요.

  • ssl.CERT_REQUIRED — 상대에게 인증서 요구. 없거나 검증 실패 시 SSLError. 호스트 이름을 매칭하지 않으므로 클라이언트 인증을 검증하려면 check_hostname도 켜야 해요. PROTOCOL_TLS_CLIENT는 기본으로 CERT_REQUIREDcheck_hostname을 둘 다 켜요.

  • class ssl.VerifyMode — CERT_* 상수들의 enum.IntEnum. 3.6 추가.

검증 플래그(SSLContext.verify_flags):

  • ssl.VERIFY_DEFAULT — CRL을 검사하지 않음(OpenSSL 기본). 3.4 추가.

  • ssl.VERIFY_CRL_CHECK_LEAF — 피어 인증서만 검사(중간 CA는 제외). 3.4 추가.

  • ssl.VERIFY_CRL_CHECK_CHAIN — 피어 인증서 체인의 모든 인증서의 CRL 검사. 3.4 추가.

  • ssl.VERIFY_X509_STRICT — 깨진 X.509 인증서에 대한 우회책 비활성화. 3.4 추가.

  • ssl.VERIFY_ALLOW_PROXY_CERTS — 프록시 인증서 검증 활성화. 3.10 추가.

  • ssl.VERIFY_X509_TRUSTED_FIRST — 신뢰 체인 구성 시 신뢰된 인증서 우선. 기본 활성. 3.4.4 추가.

  • ssl.VERIFY_X509_PARTIAL_CHAIN — 중간 CA를 신뢰 앵커로 취급. 3.10 추가.

  • class ssl.VerifyFlags — VERIFY_* 상수들의 enum.IntFlag. 3.6 추가.

프로토콜:

  • ssl.PROTOCOL_TLS — 클라이언트와 서버가 모두 지원하는 최고 프로토콜 버전 선택. 3.6 추가; 3.10부터 폐기(PROTOCOL_TLS_CLIENT/PROTOCOL_TLS_SERVER 권장).
  • ssl.PROTOCOL_TLS_CLIENT — 최고 버전 자동 협상 + 클라이언트 쪽 설정. 기본으로 CERT_REQUIREDcheck_hostname 활성. 3.6 추가.
  • ssl.PROTOCOL_TLS_SERVER — 최고 버전 자동 협상 + 서버 쪽 설정. 3.6 추가.
  • PROTOCOL_SSLv23(PROTOCOL_TLS의 별칭), PROTOCOL_SSLv3, PROTOCOL_TLSv1, PROTOCOL_TLSv1_1, PROTOCOL_TLSv1_2 — 모두 3.6부터 폐기. SSLv3은 안전하지 않아 사용을 강력히 비권장. 대신 SSLContext.minimum_version/maximum_version 사용.

옵션(SSLContext.options):

  • ssl.OP_ALL — 다른 SSL 구현의 각종 버그에 대한 우회책 활성화. 기본 설정. 3.2 추가.

  • ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv3, ssl.OP_NO_TLSv1, ssl.OP_NO_TLSv1_1, ssl.OP_NO_TLSv1_2, ssl.OP_NO_TLSv1_3 — 각 프로토콜 버전의 연결 방지. 3.7부터 OP_NO_SSL*/OP_NO_TLS* 모두 폐기 — minimum_version/maximum_version 사용.

  • ssl.OP_NO_RENEGOTIATION — TLSv1.2 이하의 모든 재협상 비활성화. OpenSSL 1.1.0h+에서만. 3.7 추가.

  • ssl.OP_CIPHER_SERVER_PREFERENCE — 클라이언트 대신 서버의 암호 정렬 선호 사용. 3.3 추가.

  • ssl.OP_SINGLE_DH_USE, ssl.OP_SINGLE_ECDH_USE — 서로 다른 SSL 세션에 같은 DH/ECDH 키 재사용 방지(전향적 비밀성 향상). 서버 소켓에만 적용. 3.3 추가.

  • ssl.OP_ENABLE_MIDDLEBOX_COMPAT — TLS 1.3 핸드셰이크에서 더미 Change Cipher Spec 메시지 전송. 3.8 추가.

  • ssl.OP_NO_COMPRESSION — SSL 채널의 압축 비활성화. 3.3 추가.

  • ssl.OP_NO_TICKET — 클라이언트가 세션 티켓을 요청하지 못하게. 3.6 추가.

  • ssl.OP_IGNORE_UNEXPECTED_EOF — TLS 연결의 예기치 않은 종료 무시. OpenSSL 3.0.0+. 3.10 추가.

  • ssl.OP_ENABLE_KTLS — 커널 TLS 사용 활성화. 3.12 추가.

  • ssl.OP_LEGACY_SERVER_CONNECT — OpenSSL과 패치되지 않은 서버 사이의 레거시 안전하지 않은 재협상만 허용. 3.12 추가.

  • class ssl.Options — OP_* 상수들의 enum.IntFlag.

기능 지원 상수: HAS_ALPN(3.5, RFC 7301), HAS_NEVER_CHECK_COMMON_NAME(3.7), HAS_ECDH(3.3), HAS_SNI(3.2, RFC 6066), HAS_NPN(3.3), HAS_SSLv2/HAS_SSLv3/HAS_TLSv1/HAS_TLSv1_1/HAS_TLSv1_2/HAS_TLSv1_3(3.7), HAS_PSK(3.13), HAS_PHA(3.14). 컴파일된 OpenSSL이 해당 기능을 지원하는지 여부.

기타 상수:

  • ssl.CHANNEL_BINDING_TYPES — 지원하는 TLS 채널 바인딩 타입 목록. 3.3 추가.

  • ssl.OPENSSL_VERSION — 로드된 OpenSSL 버전 문자열.

  • ssl.OPENSSL_VERSION_INFO — OpenSSL 버전의 다섯 정수 튜플 (major, minor, fix, patch, status).

  • ssl.OPENSSL_VERSION_NUMBER — OpenSSL 버전의 단일 정수.

  • ssl.ALERT_DESCRIPTION_* — RFC 5246 등의 경고 설명. set_servername_callback()의 콜백 반환값으로 사용. 3.4 추가.

  • Purpose.SERVER_AUTH / Purpose.CLIENT_AUTHcreate_default_context()load_default_certs()의 옵션. 각각 웹 서버/클라이언트 인증. 3.4 추가.

  • class ssl.TLSVersion — SSL/TLS 버전들의 enum.IntEnum. TLSVersion.MINIMUM_SUPPORTED, TLSVersion.MAXIMUM_SUPPORTED, TLSVersion.SSLv3, TLSVersion.TLSv1, TLSVersion.TLSv1_1, TLSVersion.TLSv1_2, TLSVersion.TLSv1_3. 3.7 추가. 3.10부터 TLSv1_2/TLSv1_3을 제외한 모든 멤버 폐기.

SSL 소켓

class ssl.SSLSocket(socket.socket) socket.socket의 메서드(accept(), bind(), close(), connect(), detach(), fileno(), getpeername(), getsockname(), getsockopt(), setsockopt(), gettimeout(), settimeout(), setblocking(), listen(), makefile(), recv(), recv_into(), send(), sendall(), sendfile(), shutdown())를 제공하지만, SSL/TLS 프로토콜은 TCP 위에 고유한 프레이밍을 가지므로 일반 OS 레벨 소켓과 일부 다를 수 있어요(논블로킹 소켓 주의 참고). SSLSocket 인스턴스는 반드시 SSLContext.wrap_socket()으로 만들어야 해요. 3.6부터 직접 생성은 폐기, 3.7부터 강제. 3.10부터 내부적으로 SSL_read_ex/SSL_write_ex 사용(2GB 넘는 데이터 지원).

추가 메서드/속성:

  • SSLSocket.read(len=1024, buffer=None) — 최대 len 바이트를 읽어 bytes로 반환. buffer 지정 시 그곳에 읽고 읽은 바이트 수 반환. 논블로킹에서 블록되면 SSLWantReadError/SSLWantWriteError. 3.6부터 recv() 사용 권장(폐기).
  • SSLSocket.write(data) — 데이터를 쓰고 쓴 바이트 수 반환. 3.6부터 send() 사용 권장(폐기).

read()/write()는 응용 레벨 평문 데이터를 읽고 쓰고 암호화/복호화하는 저수준 메서드예요. 활성 SSL 연결이 필요해요. 보통은 recv()/send() 같은 소켓 API 메서드를 써야 해요.

  • SSLSocket.do_handshake(block=False) — SSL 설정 핸드셰이크 수행. 3.4에서 check_hostname이 참이면 호스트 이름 매칭도 수행. 3.7에서 호스트 이름/IP 매칭을 OpenSSL이 수행(더 이상 match_hostname() 사용 안 함).
  • SSLSocket.getpeercert(binary_form=False) — 피어 인증서가 없으면 None. 핸드셰이크 전이면 ValueError. binary_form=False이면 dict(인증서가 검증되지 않았으면 빈 dict, 검증됐으면 subject, issuer 키 등). 예:
{'issuer': ((('countryName', 'IL'),),
            (('organizationName', 'StartCom Ltd.'),),
            (('organizationalUnitName',
              'Secure Digital Certificate Signing'),),
            (('commonName',
              'StartCom Class 2 Primary Intermediate Server CA'),)),
 'notAfter': 'Nov 22 08:15:19 2013 GMT',
 'notBefore': 'Nov 21 03:09:52 2011 GMT',
 'serialNumber': '95F0',
 'subject': ((('description', '571208-SLe257oHY9fVQ07Z'),),
             (('countryName', 'US'),),
             (('stateOrProvinceName', 'California'),),
             (('localityName', 'San Francisco'),),
             (('organizationName', 'Electronic Frontier Foundation, Inc.'),),
             (('commonName', '*.eff.org'),),
             (('emailAddress', '[email protected]'),)),
 'subjectAltName': (('DNS', '*.eff.org'), ('DNS', 'eff.org')),
 'version': 3}

binary_form=True이면 DER 인코딩 바이트를 반환. 3.4에서 핸드셰이크 전 ValueError, X509v3 확장 항목 추가. 3.9에서 IPv6 주소의 뒤따르는 개행 제거.

  • SSLSocket.get_verified_chain() — 검증된 인증서 체인을 DER 인코딩 바이트 리스트로 반환. 3.13 추가.
  • SSLSocket.get_unverified_chain() — 원시 인증서 체인을 DER 인코딩 바이트 리스트로 반환. 3.13 추가.
  • SSLSocket.cipher() — (암호 이름, SSL 프로토콜 버전, 비밀 비트 수) 3-튜플 반환. 연결 전이면 None.
  • SSLSocket.shared_ciphers() — 클라이언트/서버 양쪽에 모두 있는 암호 목록. 3.5 추가.
  • SSLSocket.compression() — 사용 중인 압축 알고리즘 문자열 또는 압축 안 됐으면 None. 3.3 추가.
  • SSLSocket.get_channel_binding(cb_type='tls-unique') — 채널 바인딩 데이터를 bytes로 반환. RFC 5929의 'tls-unique'만 지원. 3.3 추가.
  • SSLSocket.selected_alpn_protocol() — 핸드셰이크 중 선택된 프로토콜. 3.5 추가.
  • SSLSocket.selected_npn_protocol() — 핸드셰이크 중 선택된 상위 레벨 프로토콜. 3.3 추가; 3.10부터 NPN이 ALPN으로 대체되어 폐기.
  • SSLSocket.unwrap() — SSL 종료 핸드셰이크 수행, TLS 계층을 제거하고 기본 소켓 객체 반환.
  • SSLSocket.verify_client_post_handshake() — TLS 1.3 클라이언트에 핸드셰이크 후 인증(PHA) 요청. 3.8 추가. OpenSSL 1.1.1과 TLS 1.3 필요.
  • SSLSocket.version() — 협상된 실제 SSL 프로토콜 버전 문자열, 연결 없으면 None. 3.5 추가.
  • SSLSocket.pending() — 연결에서 읽을 수 있는 이미 복호화된 바이트 수.
  • SSLSocket.context — 이 소켓이 연결된 SSLContext. 3.2 추가.
  • SSLSocket.server_side — 서버 쪽이면 True. 3.2 추가.
  • SSLSocket.server_hostname — 서버 호스트 이름. 3.2 추가. 3.7에서 항상 ASCII 텍스트(IDN이면 A-label 형식).
  • SSLSocket.session — 이 SSL 연결의 SSLSession. 3.6 추가.
  • SSLSocket.session_reused3.6 추가.

SSL 컨텍스트

SSLContext는 단일 SSL 연결보다 오래 사는 각종 데이터(SSL 구성 옵션, 인증서, 개인 키)를 담아요. 서버 쪽 소켓을 위한 SSL 세션 캐시도 관리해 같은 클라이언트의 반복 연결을 빠르게 해요.

class ssl.SSLContext(protocol=None) 새 SSL 컨텍스트를 만들어요. protocolPROTOCOL_* 상수 중 하나여야 해요. 미지정 시 기본은 PROTOCOL_TLS (가장 호환성 높음). 3.6에서 안전한 기본값으로 생성(OP_NO_COMPRESSION, OP_CIPHER_SERVER_PREFERENCE, OP_SINGLE_DH_USE, OP_SINGLE_ECDH_USE, OP_NO_SSLv2, OP_NO_SSLv3 설정, 기본 암호는 HIGH만). 3.10에서 protocol 인자 없는 SSLContext 폐기; 기본 암호는 전향적 비밀성과 보안 수준 2를 가진 AES·ChaCha20만. TLS 1.2를 최소 TLS 버전으로 사용.

참고: SSLContext는 한 번 연결에 사용된 뒤에는 제한된 변경만 지원해요. 여러 연결이 공유하도록 설계되었고, 연결에 사용된 뒤 재구성하지 않는 한 스레드 안전해요.

주요 메서드:

  • SSLContext.cert_store_stats() — 로드된 X.509 인증서 수, CA 표시 인증서 수, CRL 수를 dict로 반환:
>>> context.cert_store_stats()
{'crl': 0, 'x509_ca': 1, 'x509': 2}

3.4 추가.

  • SSLContext.load_cert_chain(certfile, keyfile=None, password=None) — 개인 키와 대응 인증서 로드. password는 암호화된 키를 복호화할 함수이거나 문자열/bytes/bytearray일 수 있어요. 키가 인증서와 안 맞으면 SSLError. 3.3에서 password 추가.
  • SSLContext.load_default_certs(purpose=Purpose.SERVER_AUTH) — 기본 위치에서 기본 "인증 기관"(CA) 인증서 로드. 3.4 추가.
  • SSLContext.load_verify_locations(cafile=None, capath=None, cadata=None)verify_modeCERT_NONE이 아닐 때 다른 피어 인증서를 검증하는 데 쓰는 CA 인증서 로드. cafile·capath 중 하나 이상은 지정해야 해요. PEM/DER 형식 CRL도 로드할 수 있어요. 3.4에서 cadata 추가.
  • SSLContext.get_ca_certs(binary_form=False) — 로드된 CA 인증서 목록. binary_form=FalseSSLSocket.getpeercert()처럼 각 항목이 dict, 아니면 DER 인코딩 목록. 3.4 추가.
  • SSLContext.get_ciphers() — 활성 암호 목록(우선순위 순):
>>> ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
>>> ctx.set_ciphers('ECDHE+AESGCM:!ECDSA')
>>> ctx.get_ciphers()
[{'aead': True,
  'alg_bits': 256,
  'auth': 'auth-rsa',
  'description': 'ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=RSA  '
                 'Enc=AESGCM(256) Mac=AEAD',
  'digest': None,
  'id': 50380848,
  'kea': 'kx-ecdhe',
  'name': 'ECDHE-RSA-AES256-GCM-SHA384',
  'protocol': 'TLSv1.2',
  'strength_bits': 256,
  'symmetric': 'aes-256-gcm'},
 {'aead': True,
  'alg_bits': 128,
  'auth': 'auth-rsa',
  'description': 'ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH     Au=RSA  '
                 'Enc=AESGCM(128) Mac=AEAD',
  'digest': None,
  'id': 50380847,
  'kea': 'kx-ecdhe',
  'name': 'ECDHE-RSA-AES128-GCM-SHA256',
  'protocol': 'TLSv1.2',
  'strength_bits': 128,
  'symmetric': 'aes-128-gcm'}]

3.6 추가.

  • SSLContext.set_default_verify_paths() — OpenSSL 라이브러리 빌드 시 정의된 파일시스템 경로에서 기본 CA 인증서 로드.
  • SSLContext.set_ciphers(ciphers, /) — 이 컨텍스트로 만든 소켓의 사용 가능한 암호 설정(OpenSSL 암호 목록 형식 문자열). 선택 가능한 암호가 없으면 SSLError. TLS 1.3 암호 스위트는 set_ciphers()로 비활성화할 수 없어요.
  • SSLContext.set_alpn_protocols(alpn_protocols) — 핸드셰이크 중 광고할 프로토콜 지정(선호 순의 ASCII 문자열 리스트). RFC 7301. HAS_ALPNFalseNotImplementedError. 3.5 추가.
  • SSLContext.set_npn_protocols(npn_protocols) — 핸드셰이크 중 광고할 프로토콜 지정. 3.3 추가; 3.10부터 폐기(ALPN으로 대체).
  • SSLContext.sni_callback — TLS 서버가 Client Hello 메시지를 받고 클라이언트가 SNI(서버 이름 표시)를 지정하면 호출될 콜백 등록. 콜백은 (ssl.SSLSocket, server_name, SSLContext) 세 인자로 호출돼요. None 반환 시 협상 계속, ALERT_DESCRIPTION_* 반환 시 TLS 실패. 3.7 추가.
  • SSLContext.set_servername_callback(server_name_callback) — 레거시 API. IDN이 U-label로 전달되는 점 외에 sni_callback과 유사. 가능하면 sni_callback 사용. 3.4 추가.
  • SSLContext.load_dh_params(dhfile, /) — Diffie-Hellman 키 교환의 키 생성 매개변수 로드(PEM 형식). 클라이언트 소켓엔 적용 안 됨. 3.3 추가.
  • SSLContext.set_ecdh_curve(curve_name, /) — ECDH 키 교환의 곡선 이름 설정(예: prime256v1). 클라이언트 소켓엔 적용 안 됨. HAS_ECDHFalse면 사용 불가. 3.3 추가.
  • SSLContext.wrap_socket(sock, server_side=False, do_handshake_on_connect=True, suppress_ragged_eofs=True, server_hostname=None, session=None) — 기존 Python sock을 감싸 SSLContext.sslsocket_class(기본 SSLSocket) 인스턴스 반환. server_hostname은 클라이언트 연결에서 서비스 호스트 이름 지정(단일 서버가 여러 인증서 호스팅 가능). server_side=Trueserver_hostname 지정 시 ValueError. 3.5에서 OpenSSL이 SNI가 없어도 server_hostname 전달 허용. 3.6에서 session 추가. 3.7에서 sslsocket_class 인스턴스 반환.
  • SSLContext.sslsocket_classwrap_socket()의 반환 타입, 기본 SSLSocket. 3.7 추가.
  • SSLContext.wrap_bio(incoming, outgoing, server_side=False, server_hostname=None, session=None) — BIO 객체 incoming/outgoing을 감싸 SSLContext.sslobject_class(기본 SSLObject) 인스턴스 반환. 3.6에서 session 추가. 3.7에서 sslobject_class 인스턴스 반환.
  • SSLContext.sslobject_classwrap_bio()의 반환 타입, 기본 SSLObject. 3.7 추가.
  • SSLContext.session_stats() — 이 컨텍스트가 만들거나 관리한 SSL 세션 통계 dict:
>>> stats = context.session_stats()
>>> stats['hits'], stats['misses']
(0, 0)

주요 속성:

  • SSLContext.check_hostnameSSLSocket.do_handshake()에서 피어 인증서 호스트 이름을 매칭할지. 활성화 시 verify_modeCERT_NONE에서 CERT_REQUIRED로 자동 변경(되돌릴 수 없음). PROTOCOL_TLS_CLIENT는 기본 활성. 3.4 추가. 3.7에서 verify_mode 자동 변경.
  • SSLContext.keylog_filename — 키 재료가 생성/수신될 때마다 TLS 키를 키로그 파일에 기록. NSS 형식, Wireshark가 사용. 3.8 추가.
  • SSLContext.maximum_version / SSLContext.minimum_version — 지원하는 최고/최저 TLS 버전(TLSVersion 멤버). 3.7 추가.
  • SSLContext.num_ticketsPROTOCOL_TLS_SERVER 컨텍스트의 TLS 1.3 세션 티켓 수 제어. 3.8 추가.
  • SSLContext.options — 이 컨텍스트에 활성화된 SSL 옵션 집합(정수). 기본 OP_ALL.
  • SSLContext.post_handshake_auth — TLS 1.3 핸드셰이크 후 클라이언트 인증 활성화. 3.8 추가.
  • SSLContext.protocol — 컨텍스트 생성 시 선택한 프로토콜. 읽기 전용.
  • SSLContext.hostname_checks_common_namecheck_hostname이 subject alternative name 확장이 없을 때 인증서 subject common name 검증으로 대체할지(기본 true). 3.7 추가.
  • SSLContext.security_level — 컨텍스트의 보안 수준(정수). 읽기 전용. 3.10 추가.
  • SSLContext.verify_flags — 인증서 검증 연산 플래그. 기본적으로 OpenSSL은 CRL을 요구/검증하지 않음.
  • SSLContext.verify_mode — 다른 피어 인증서 검증 여부/실패 시 동작. CERT_NONE/CERT_OPTIONAL/CERT_REQUIRED 중 하나.
  • SSLContext.set_psk_client_callback(callback) — 클라이언트 쪽 TLS-PSK 사전 공유 키 인증 활성화. 콜백 시그니처 def callback(hint: str | None) -> tuple[str | None, bytes]. TLS 1.3에선 hint가 항상 None, client-identity는 비어 있지 않은 문자열이어야 해요. 3.13 추가.
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
context.maximum_version = ssl.TLSVersion.TLSv1_2
context.set_ciphers('PSK')

# A simple lambda:
psk = bytes.fromhex('c0ffee')
context.set_psk_client_callback(lambda hint: (None, psk))

# A table using the hint from the server:
psk_table = { 'ServerId_1': bytes.fromhex('c0ffee'),
              'ServerId_2': bytes.fromhex('facade')
}
def callback(hint):
    return 'ClientId_1', psk_table.get(hint, b'')
context.set_psk_client_callback(callback)
  • SSLContext.set_psk_server_callback(callback, identity_hint=None) — 서버 쪽 TLS-PSK 인증 활성화. 콜백 시그니처 def callback(identity: str | None) -> bytes. 3.13 추가.

인증서

인증서는 공개키/개인키 시스템의 일부예요. 각 주체는 고유한 두 부분 키를 할당받고, 인증서에는 subject 이름과 그 공개키 외에 issuer의 진술(주체가 주장하는 사람임 + 이것이 공개키임)이 포함돼요. "notBefore"와 "notAfter" 필드로 유효 기간을 표현해요.

Python은 파일로 인증서를 담아요. "PEM"(RFC 1422) 형식이어야 해요 — base-64 인코딩에 헤더/푸터 줄을 감싼 형태:

-----BEGIN CERTIFICATE-----
... (certificate in base64 PEM encoding) ...
-----END CERTIFICATE-----

인증서 체인: 파일은 체인을 이룬 일련의 인증서를 담을 수 있어요 — 클라이언트/서버 주체의 특정 인증서로 시작해, 그 발급자의 인증서, 그 발급자의 발급자 인증서... 마지막에 self-signed(루트) 인증서까지. 파일에 그냥 이어 붙이면 돼요.

CA 인증서: 연결 상대의 인증서를 검증하려면 각 issuer에 대해 신뢰할 인증서 체인을 채운 "CA certs" 파일이 필요해요. 플랫폼 인증서 파일은 SSLContext.load_default_certs()로 쓸 수 있고, create_default_context()가 자동으로 해요.

결합 키와 인증서: 개인 키가 인증서와 같은 파일에 종종 저장돼요. 이 경우 SSLContext.load_cert_chain()certfile만 넘기면 되고, 키는 체인의 첫 인증서보다 앞에 와야 해요.

자체 서명 인증서: OpenSSL 패키지로 생성하는 일반적인 방법:

% openssl req -new -x509 -days 365 -nodes -out cert.pem -keyout cert.pem
Generating a 1024 bit RSA private key
.......++++++
.............................++++++
writing new private key to 'cert.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:MyState
Locality Name (eg, city) []:Some City
Organization Name (eg, company) [Internet Widgits Pty Ltd]:My Organization, Inc.
Organizational Unit Name (eg, section) []:My Group
Common Name (eg, YOUR name) []:myserver.mygroup.myorganization.com
Email Address []:[email protected]
%

자체 서명 인증서의 단점은 그 자신이 루트 인증서라서 다른 누구도 알려진(신뢰된) 루트 인증서 캐시에 갖고 있지 않다는 점이에요.

예제

SSL 지원 테스트:

try:
    import ssl
except ImportError:
    pass
else:
    ...  # do something that requires SSL support

클라이언트 쪽 동작:

>>> context = ssl.create_default_context()
>>> context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
>>> context.load_verify_locations("/etc/ssl/certs/ca-bundle.crt")

PROTOCOL_TLS_CLIENT는 인증서 검증과 호스트 이름 검증을 위해 verify_modeCERT_REQUIRED로, check_hostnameTrue로 설정해요. 연결 후:

>>> conn = context.wrap_socket(socket.socket(socket.AF_INET),
...                            server_hostname="www.python.org")
>>> conn.connect(("www.python.org", 443))

인증서 조회:

>>> cert = conn.getpeercert()

서버 쪽 동작:

import socket, ssl

context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain(certfile="mycertfile", keyfile="mykeyfile")

bindsocket = socket.socket()
bindsocket.bind(('myaddr.example.com', 10023))
bindsocket.listen(5)
while True:
    newsocket, fromaddr = bindsocket.accept()
    connstream = context.wrap_socket(newsocket, server_side=True)
    try:
        deal_with_client(connstream)
    finally:
        connstream.shutdown(socket.SHUT_RDWR)
        connstream.close()
def deal_with_client(connstream):
    data = connstream.recv(1024)
    # empty data means the client is finished with us
    while data:
        if not do_something(connstream, data):
            # we'll assume do_something returns False
            # when we're finished with client
            break
        data = connstream.recv(1024)
    # finished with client

논블로킹 소켓 참고

SSL 소켓은 논블로킹 모드에서 일반 소켓과 약간 다르게 동작해요:

  • 대부분의 SSLSocket 메서드는 I/O가 블록되면 BlockingIOError 대신 SSLWantWriteError/SSLWantReadError를 발생시켜요. SSL 소켓에 쓰려면 먼저 기본 소켓에서 읽어야 하고, 읽으려면 먼저 써야 할 수 있어요.
  • select()가 OS 레벨 소켓이 읽기(쓰기) 가능함을 알려줘도 상위 SSL 레벨에 충분한 데이터가 있음을 보장하지 않아요(SSL 프레임의 일부만 도착했을 수 있음). 따라서 recv()/send() 실패를 처리하고 select()를 다시 호출한 후 재시도해야 해요.
  • 반대로 SSL 레이어는 고유 프레이밍이 있으므로 select()가 모르는 사이에 읽을 데이터가 있을 수 있어요. 먼저 recv()로 가능한 데이터를 비우고 필요할 때만 select()에서 블록하세요.
  • SSL 핸드셰이크 자체는 논블로킹이에요. do_handshake()가 성공할 때까지 재시도해야 해요:
while True:
    try:
        sock.do_handshake()
        break
    except ssl.SSLWantReadError:
        select.select([sock], [], [])
    except ssl.SSLWantWriteError:
        select.select([], [sock], [])

asyncio 모듈이 논블로킹 SSL 소켓을 지원하고 더 높은 레벨의 Streams API를 제공해요.

메모리 BIO 지원

SSLSocket은 두 가지 기능 영역을 결합해요: SSL 프로토콜 처리와 네트워크 IO. "select/poll on a file descriptor" 모델이 효율적이지 않은 Windows 같은 플랫폼에서는 네트워크 IO 없는 축소 범위 변형인 SSLObject를 제공해요.

  • class ssl.SSLObject — 네트워크 IO 메서드를 담지 않은 SSLSocket의 축소 범위 변형. 비동기 IO를 메모리 버퍼로 구현하려는 프레임워크 작성자가 주로 사용. 공개 생성자가 없고 wrap_bio()로 만들어요. SSLSocket의 메서드와 속성 중 네트워크 IO 관련(recv()/send() 등)이 없는 것들을 지원해요.
  • class ssl.MemoryBIO — Python과 SSL 프로토콜 인스턴스 사이에 데이터를 전달하는 데 쓰는 메모리 버퍼. pending(현재 버퍼의 바이트 수), eof(EOF 위치 여부), read(n=-1, /), write(buf, /), write_eof() 메서드.

SSL 세션

  • class ssl.SSLSessionsession이 사용하는 세션 객체. 속성: id, time, timeout, ticket_lifetime_hint, has_ticket. 3.6 추가.

보안 고려사항

최상의 기본값: 클라이언트 사용에서 특별한 요구가 없다면 create_default_context()를 사용하는 걸 강력히 권장해요. 시스템의 신뢰된 CA 인증서를 로드하고, 인증서 검증과 호스트 이름 검사, 합리적으로 안전한 프로토콜/암호 설정을 선택해요. 반대로 SSLContext 생성자를 직접 호출하면 기본적으로 인증서 검증이나 호스트 이름 검사가 활성화되지 않아요.

수동 설정 — 인증서 검증: SSLContext 생성자를 직접 호출하면 기본이 CERT_NONE이에요. 클라이언트 모드에선 CERT_REQUIRED를 사용하는 걸 강력히 권장하고, check_hostname 활성화로 호스트 이름 검사를 자동 수행해요. 서버 모드에서 SSL 레이어로 클라이언트를 인증하려면 CERT_REQUIRED를 지정하고 클라이언트 인증서를 검사해야 해요.

프로토콜 버전: SSL 2/3은 안전하지 않은 것으로 간주돼요. PROTOCOL_TLS_CLIENT/PROTOCOL_TLS_SERVER 사용 권장. 예:

>>> client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
>>> client_context.minimum_version = ssl.TLSVersion.TLSv1_2
>>> client_context.maximum_version = ssl.TLSVersion.TLSv1_3

암호 선택: SSLContext.set_ciphers()로 세부 조정 가능. get_ciphers()openssl ciphers 명령으로 확인.

멀티 프로세싱: OpenSSL의 내부 난수 생성기는 fork된 프로세스를 제대로 처리하지 못해요. os.fork()와 SSL 기능을 함께 쓰면 부모 프로세스의 PRNG 상태를 바꿔야 해요. RAND_add()RAND_bytes() 호출로 충분해요.

TLS 1.3

TLS 1.3은 이전 버전과 약간 다르게 동작해요. 새 기능 일부는 아직 사용할 수 없어요:

  • TLS 1.3은 분리된 암호 스위트 세트를 사용해요. 모든 AES-GCM과 ChaCha20 암호가 기본 활성. set_ciphers()가 TLS 1.3 암호를 활성/비활성화하지 못하지만 get_ciphers()는 반환해요.
  • 세션 티켓이 초기 핸드셰이크의 일부로 더 이상 전송되지 않아요. SSLSocket.sessionSSLSession은 TLS 1.3과 호환되지 않아요.
  • 클라이언트 쪽 인증서가 초기 핸드셰이크 중에 더 이상 검증되지 않아요. 서버는 언제든 인증서를 요청할 수 있어요.
  • early data, 지연된 TLS 클라이언트 인증서 요청, 시그니처 알고리즘 구성, rekeying 같은 TLS 1.3 기능은 아직 지원되지 않아요.

더 알아보기

  • socket.socket 클래스 — 기본 socket 클래스 문서.
  • SSL/TLS Strong Encryption: An Introduction — Apache HTTP Server 문서의 소개.
  • RFC 1422, RFC 4086, RFC 5280, RFC 5246, RFC 6066, RFC 7525.
  • IANA TLS: Transport Layer Security (TLS) Parameters.
  • Mozilla's Server Side TLS recommendations.