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_mode를 CERT_REQUIRED로 설정하고 CA 인증서를 로드해요. 기본 설정에 VERIFY_X509_PARTIAL_CHAIN과 VERIFY_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.CertificateError—SSLCertVerificationError의 별칭. 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 tupleDefaultVerifyPaths반환. 필드:cafile,capath,openssl_cafile_env,openssl_cafile,openssl_capath_env,openssl_capath. 3.4 추가.ssl.enum_certificates(store_name)— Windows 시스템 인증서 저장소에서 인증서 조회.store_name은CA,ROOT,MY중 하나.(cert_bytes, encoding_type, trust)튜플 리스트 반환.encoding_type은x509_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_REQUIRED와check_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_REQUIRED와check_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_AUTH—create_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_reused— 3.6 추가.
SSL 컨텍스트
SSLContext는 단일 SSL 연결보다 오래 사는 각종 데이터(SSL 구성 옵션, 인증서, 개인 키)를 담아요. 서버 쪽 소켓을 위한 SSL 세션 캐시도 관리해 같은 클라이언트의 반복 연결을 빠르게 해요.
class ssl.SSLContext(protocol=None)
새 SSL 컨텍스트를 만들어요. protocol은 PROTOCOL_* 상수 중 하나여야 해요. 미지정 시 기본은 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_mode가CERT_NONE이 아닐 때 다른 피어 인증서를 검증하는 데 쓰는 CA 인증서 로드.cafile·capath중 하나 이상은 지정해야 해요. PEM/DER 형식 CRL도 로드할 수 있어요. 3.4에서cadata추가.SSLContext.get_ca_certs(binary_form=False)— 로드된 CA 인증서 목록.binary_form=False면SSLSocket.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_ALPN이False면NotImplementedError. 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_ECDH가False면 사용 불가. 3.3 추가.SSLContext.wrap_socket(sock, server_side=False, do_handshake_on_connect=True, suppress_ragged_eofs=True, server_hostname=None, session=None)— 기존 Pythonsock을 감싸SSLContext.sslsocket_class(기본SSLSocket) 인스턴스 반환.server_hostname은 클라이언트 연결에서 서비스 호스트 이름 지정(단일 서버가 여러 인증서 호스팅 가능).server_side=True면server_hostname지정 시ValueError. 3.5에서 OpenSSL이 SNI가 없어도 server_hostname 전달 허용. 3.6에서session추가. 3.7에서sslsocket_class인스턴스 반환.SSLContext.sslsocket_class—wrap_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_class—wrap_bio()의 반환 타입, 기본SSLObject. 3.7 추가.SSLContext.session_stats()— 이 컨텍스트가 만들거나 관리한 SSL 세션 통계 dict:
>>> stats = context.session_stats()
>>> stats['hits'], stats['misses']
(0, 0)
주요 속성:
SSLContext.check_hostname—SSLSocket.do_handshake()에서 피어 인증서 호스트 이름을 매칭할지. 활성화 시verify_mode가CERT_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_tickets—PROTOCOL_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_name—check_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_mode를 CERT_REQUIRED로, check_hostname을 True로 설정해요. 연결 후:
>>> 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.SSLSession—session이 사용하는 세션 객체. 속성: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.session과SSLSession은 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.