如何使用 python 建立 TLS 连接?

Ali*_*Ali 11 ssl python-3.x tls1.2

我想创建到服务器的 TLS 连接。然后,我想发送一些加密的数据到服务器。我知道主机名和端口并且有证书。令人惊讶的是,我还收到了服务器的私钥。但是,我认为我收到私钥是不正常的。

第一个问题是,我真的需要私钥来建立 TLS 连接吗?

顺便说一句,我正在使用这个 python 脚本

import socket
import ssl

server_addr = '**.**.**.**'
server_port = ****
server_cert = 'server.crt'
server_key  = 'server.key'        # I use the private key

context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.verify_mode = ssl.CERT_REQUIRED
context.load_cert_chain(certfile=server_cert, keyfile=server_key)

bindsocket = socket.socket()
bindsocket.connect((server_addr, server_port))
Run Code Online (Sandbox Code Playgroud)

我在上面的脚本中使用私钥。它可以正常工作,没有任何错误。但是,当我尝试使用 bind() 而不是 connect() 时,即

bindsocket.bind((server_addr, server_port))
Run Code Online (Sandbox Code Playgroud)

我收到以下错误:

OSError: [Errno 99] 无法分配请求的地址

我已经阅读了许多有关上述错误的相关问题,但是,我仍然不明白为什么会发生这种情况。因为我有主机名、端口、证书和密钥,所以我希望能够成功创建 TLS 连接。

第二个问题是如何建立TLS连接?我的脚本正确吗?

我非常感谢任何改进脚本的评论。

toy*_*ian 14

所以,首先,你绝对不应该拥有私钥!顾名思义,它是私​​有的,不需要建立连接。
您可以拥有公钥,但即使如此,只要您使用标准 SSL 并且信任签署服务器证书的 CA,也没有必要。
你确定这是私钥吗?文件以 开头吗-----BEGIN PRIVATE KEY-----?检查与openssl rsa -noout -text -in server.key. 有关非对称加密的更多信息,
请参阅维基百科文章这篇文章。

进一步:
socket.bind()套接字绑定到本地计算机上的端口。这是不可能的,因为您的计算机没有地址(您提供服务器地址)。
从您的代码来看,您似乎正在尝试将套接字作为服务器打开。为此,您将需要私钥,但随后您将接受连接,而不会自己连接到其他计算机。我有一种感觉,你在这里混淆了两件事。
请参阅此问题python 文档,因为这似乎密切相关。 另请查看有关 ssl 的 python 文档。我举了一个例子,它满足了您从所述文档中要求的内容:socket.bind()

import socket, ssl, pprint

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# require a certificate from the server
ssl_sock = ssl.wrap_socket(s,
                           ca_certs="/etc/ca_certs_file",
                           cert_reqs=ssl.CERT_REQUIRED)
ssl_sock.connect(('www.verisign.com', 443))

pprint.pprint(ssl_sock.getpeercert())
# note that closing the SSLSocket will also close the underlying socket
ssl_sock.close()
Run Code Online (Sandbox Code Playgroud)

另请查看有关如何在服务器模式下打开 SSL 套接字的示例

进一步的想法:
您真的需要自己完成所有 TLS 工作吗?例如,如果服务器使用 HTTPS(SSL 加密的 HTTP),则可以仅使用http.client库。

如果您需要我澄清一些事情,请随时询问。我会相应地更新我的答案。

编辑:
正如您所指出的,您想在服务器模式下打开一个端口,我为您做了一个示例(它很大程度上依赖于 python 文档示例):

import socket, ssl

context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
context.load_cert_chain(certfile="cert.pem", keyfile="key.pem")

bindsocket = socket.socket()
bindsocket.bind(('127.0.0.1', 10023))
bindsocket.listen(5)

def deal_with_client(connstream):
    data = connstream.recv(1024)
    # empty data means the client is finished with us
    while data:
        print(data)
        data = connstream.recv(1024)

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()
Run Code Online (Sandbox Code Playgroud)

运行它:

% python3 ssltest.py
b'hello server!\n'
b'this is data\n'
Run Code Online (Sandbox Code Playgroud)

客户端:

% openssl s_client -connect 127.0.0.1:10023
CONNECTED(00000005)
depth=0 C = SE, ST = Some-State, O = Internet Widgits Pty Ltd
verify error:num=18:self signed certificate
verify return:1
depth=0 C = SE, ST = Some-State, O = Internet Widgits Pty Ltd
verify return:1
---
Certificate chain
 0 s:C = SE, ST = Some-State, O = Internet Widgits Pty Ltd
   i:C = SE, ST = Some-State, O = Internet Widgits Pty Ltd
---
Server certificate
-----BEGIN CERTIFICATE-----
 ... certificate ...
-----END CERTIFICATE-----
subject=C = SE, ST = Some-State, O = Internet Widgits Pty Ltd

issuer=C = SE, ST = Some-State, O = Internet Widgits Pty Ltd

---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: RSA-PSS
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 2272 bytes and written 404 bytes
Verification error: self signed certificate
---
New, TLSv1.2, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 4096 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES256-GCM-SHA384
    ... session stuff ...
    Extended master secret: yes
---
hello server!
this is data
^C
Run Code Online (Sandbox Code Playgroud)

如果您使用一些常用协议(例如 HTTP),则应该使用库。这是一个使用烧瓶的示例:

>>> from flask import Flask
>>> app = Flask(__name__)
>>> 
>>> @app.route("/")
... def hello():
...     return "Hello World!"
... 
>>> if __name__ == "__main__":
...     app.run(ssl_context=('cert.pem', 'key.pem'))
... 
 * Serving Flask app "__main__" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on https://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [06/Aug/2020 11:45:50] "GET / HTTP/1.1" 200 -
Run Code Online (Sandbox Code Playgroud)