python ssl_Python网络编程中的TLS/SSL。

传输层安全协议(TLS)算是如今互联网上应用最广泛的加密方法。

TLS的前身是安全套接层(SSL),现代互联网的许多协议基础协议都是使用TLS来验证服务器身份,并保护传输过程中的数据。

TLS能保护的信息包括:与请求URL之间的HTTPS链接以及以及返回内容、密码或cookie等可能在套接字双向传递的认证信息。

下面的信息无法使用TLS保护:本机与远程主机都是可见的,地址信息在每个数据包的IP头信息中以纯文本的形式表示。

客户端与服务器的端口号同样在每个TCP头信息中可见。

客户端为了获取服务器的IP地址,可能会先进行DNS查询。该查询在通过网络发送时也是可见的。

通过TLS加密的套接字向任何一方传递数据块的时候,观察者都可以看到数据块的大小。尽管TLS会试图隐藏确切的字节数,但是观察者仍然能看到传输数据块的大致规模。同样,也可以看到请求和响应的整体模式。

关于TLS怎么被设计出来的,那些问题这里就不说,下面说一下生成证书。

Python标准库中并没有提供私钥生成或者证书签名的相关操作。如果需要进行与这两项相关的操作,那么必须使用其他工具。openssl命令行工具就很流行而且很好用。

自己创建证书,通常要先生成两部分信息:第一部分是人工生成的,另一部分是由机器生成。人工生成的信息。人工生成的信息对证书中的描述的实体进行了文本说明,而机器会使用操作系统提供的真正的随机算法精心生成一个秘钥。

你也可以把手写的实体描述保存在一个版本控制文件中,以便今后查看。当然,你也可以直接在弹出的openssl命令提示符中输入实体描述的相关字段。

然后我们说一下TLS负载移除。

这里面先说另外一个点,为什么要直接在Python应用程序中直接进行加密操作,而不是直接使用工具。如果在另外一个端口运行这些工具的话,就可以通过它们对客户端的连接作出响应。

因此,在Python应用程序提供TLS支持的时候有两种选择:方案一是使用一个单独的守护进程或者服务提供TLS支持。方案二则是直接在Python编写的服务器代码中使用提供TLS功能的OpenSSL库。相比较于方案二,方案一更易于升级或者维护。

下面说下Python3.4之后的默认上下文,Python标准库是对OpenSSL库进行封装。当然,Python社区也在研究其他密码学的项目,包括pyOpenSSL。

Python3.4引入了ssl.create_default_context()函数,这样我们就可以轻松在Python应用程序中安全使用TLS。

这是一个简单的客户端和服务器,通过TLS套接字进行安全通信的方法。

import argparse, socket, ssl

def client(host, port, cafile=None):

purpose = ssl.Purpose.SERVER_AUTH

context = ssl.create_default_context(purpose, cafile=cafile)

raw_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

raw_sock.connect((host, port))

print('Connected to host{!r}and port{}'.format(host, port))

ssl_sock = context.wrap_socket(raw_sock, server_hostname=host)

while True:

data = ssl_sock.recv(1024)

if not data:

break

print(repr(data))

def server(host, port, certfile, cafile=None):

purpose = ssl.Purpose.CLIENT_AUTH

context = ssl.create_default_context(purpose, cafile=cafile)

context.load_cert_chain(certfile)

listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

listener.bind((host, port))

listener.listen(1)

print('Listening at interface{!r}and port{}'.format(host, port))

raw_sock, address = listener.accept()

print('Connection from host{!r}and port{}'.format(*address))

ssl_sock = context.wrap_socket(raw_sock, server_side=True)

ssl_sock.sendall('Simple is better than complex.'.encode('ascii'))

ssl_sock.close()

if __name__ == '__main__':

parser = argparse.ArgumentParser(description='Safe TLS client and server')

parser.add_argument('host', help='hostname or IP address')

parser.add_argument('port', type=int, help='TCP port number')

parser.add_argument('-a', metavar='cafile', default=None,

help='authority: path to CA certificate PEM file')

parser.add_argument('-s', metavar='certfile', default=None,

help='run as server: path to server PEM file')

args = parser.parse_args()

if args.s:

server(args.host, args.port, args.s, args.a)

else:

client(args.host, args.port, args.a)

从上面看出,为一个套接字提供安全通信只需要三个步骤。第一步是TLS上下文对象。对象中保存了我们对证书的认证与加密算法选择的偏好设置。

第二步是调用上下文对象的wrap_socket()方法,表示让OpenSLL库负责控制我们的TCP链接。然后与通信对方交换必要的握手信息,并建立加密链接。

最后一步是使用wrap_socket()调用返回的ssl_sock对象,进行所有的后续通信。

另外,套接字包装的变体有很多,这里就不再说了。另外不再详细说的就是,如果对数据安全性要求很高的话,可能需要自己指定OpenSLL确切使用的加密算法,而不使用create_default_context()函数提供的默认值。

有一个问题就是,如何配置TLS加密算法及选项,以防止通信对方使用这些协议,以防止通信对方使用较弱的协议版本、加密算法或者是像压缩 这种可能降低协议安全性的选项。

这个配置可以通过下面的方式来解决:第一种是特定于库的API调用

第二种是直接传递一个包含了配置选项的SSLContext对象

然后再看支持TLS的协议:http.client

smtplib

poplib

imaplib

ftplib

nntplib

下面看一个脚本,这个脚本创建了一个加密链接,然后打印出这个链接的特性。

先看一下如何获取配置信息:getpeercert()

cipher()

compression()

为了尽可能打印出这些特性,所以使用了ctypes来获取正在使用的TLS协议的信息。这段代码是让我们连接到一个自己构建的客户端或服务器,并了解它们支持的或不支持的加密算法与协议。

import argparse, socket, ssl, sys, textwrap

import ctypes

from pprint import pprint

def open_tls(context, address, server=False):

raw_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

if server:

raw_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

raw_sock.bind(address)

raw_sock.listen(1)

say('Interface where we are listening', address)

raw_client_sock, address = raw_sock.accept()

say('Client has connected from address', address)

return context.wrap_socket(raw_client_sock, server_side=True)

else:

say('Address we want to talk to', address)

raw_sock.connect(address)

return context.wrap_socket(raw_sock)

def describe(ssl_sock, hostname, server=False, debug=False):

cert = ssl_sock.getpeercert()

if cert is None:

say('Peer certificate', 'none')

else:

say('Peer certificate', 'provided')

subject = cert.get('subject', [])

names = [name for names in subject for (key, name) in names

if key == 'commonName']

if 'subjectAltName' in cert:

names.extend(name for (key, name) in cert['subjectAltName']

if key == 'DNS')

say('Name(s) on peer certificate', *names or ['none'])

if (not server) and names:

try:

ssl.match_hostname(cert, hostname)

except ssl.CertificateError as e:

message = str(e)

else:

message = 'Yes'

say('Whether name(s) match the hostname', message)

for category, count in sorted(context.cert_store_stats().items()):

say('Certificates loaded of type{}'.format(category), count)

try:

protocol_version = SSL_get_version(ssl_sock)

except Exception:

if debug:

raise

else:

say('Protocol version negotiated', protocol_version)

cipher, version, bits = ssl_sock.cipher()

compression = ssl_sock.compression()

say('Cipher chosen for this connection', cipher)

say('Cipher defined in TLS version', version)

say('Cipher key has this many bits', bits)

say('Compression algorithm in use', compression or 'none')

return cert

class PySSLSocket(ctypes.Structure):

"""The first few fields of a PySSLSocket (see Python's Modules/_ssl.c)."""

_fields_ = [('ob_refcnt', ctypes.c_ulong), ('ob_type', ctypes.c_void_p),

('Socket', ctypes.c_void_p), ('ssl', ctypes.c_void_p)]

def SSL_get_version(ssl_sock):

"""Reach behind the scenes for a socket's TLS protocol version."""

lib = ctypes.CDLL(ssl._ssl.__file__)

lib.SSL_get_version.restype = ctypes.c_char_p

address = id(ssl_sock._sslobj)

struct = ctypes.cast(address, ctypes.POINTER(PySSLSocket)).contents

version_bytestring = lib.SSL_get_version(struct.ssl)

return version_bytestring.decode('ascii')

def lookup(prefix, name):

if not name.startswith(prefix):

name = prefix + name

try:

return getattr(ssl, name)

except AttributeError:

matching_names = (s for s in dir(ssl) if s.startswith(prefix))

message = 'Error:{!r}is not one of the available names:\n{}'.format(

name, ' '.join(sorted(matching_names)))

print(fill(message), file=sys.stderr)

sys.exit(2)

def say(title, *words):

print(fill(title.ljust(36, '.') + ' ' + ' '.join(str(w) for w in words)))

def fill(text):

return textwrap.fill(text, subsequent_indent=' ',

break_long_words=False, break_on_hyphens=False)

if __name__ == '__main__':

parser = argparse.ArgumentParser(description='Protect a socket with TLS')

parser.add_argument('host', help='hostname or IP address')

parser.add_argument('port', type=int, help='TCP port number')

parser.add_argument('-a', metavar='cafile', default=None,

help='authority: path to CA certificate PEM file')

parser.add_argument('-c', metavar='certfile', default=None,

help='path to PEM file with client certificate')

parser.add_argument('-C', metavar='ciphers', default='ALL',

help='list of ciphers, formatted per OpenSSL')

parser.add_argument('-p', metavar='PROTOCOL', default='SSLv23',

help='protocol version (default: "SSLv23")')

parser.add_argument('-s', metavar='certfile', default=None,

help='run as server: path to certificate PEM file')

parser.add_argument('-d', action='store_true', default=False,

help='debug mode: do not hide "ctypes" exceptions')

parser.add_argument('-v', action='store_true', default=False,

help='verbose: print out remote certificate')

args = parser.parse_args()

address = (args.host, args.port)

protocol = lookup('PROTOCOL_', args.p)

context = ssl.SSLContext(protocol)

context.set_ciphers(args.C)

context.check_hostname = False

if (args.s is not None) and (args.c is not None):

parser.error('you cannot specify both -c and -s')

elif args.s is not None:

context.verify_mode = ssl.CERT_OPTIONAL

purpose = ssl.Purpose.CLIENT_AUTH

context.load_cert_chain(args.s)

else:

context.verify_mode = ssl.CERT_REQUIRED

purpose = ssl.Purpose.SERVER_AUTH

if args.c is not None:

context.load_cert_chain(args.c)

if args.a is None:

context.load_default_certs(purpose)

else:

context.load_verify_locations(args.a)

print()

ssl_sock = open_tls(context, address, args.s)

cert = describe(ssl_sock, args.host, args.s, args.d)

print()

if args.v:

pprint(cert)

到了这里,这篇文章就结束了。

要注意的是,一旦我们在自己的应用程序中实现了TLS,就应该始终使用工具对那些具有不同参数集的链接进行测试。

最后的最后,寒假快结束了。

祝大家天天开心,新的一年更加的万事胜意。

你可能感兴趣的:(python,ssl)