如何使用Python建立TLS连接?

11

我希望创建与服务器的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))

我正在使用上述脚本中的私钥。它可以正常运行没有错误。然而,当我尝试使用bind()而不是connect()时,即

bindsocket.bind((server_addr, server_port))

我遇到了以下错误:
OSError: [Errno 99] 无法分配请求的地址
我阅读了许多关于上述错误的相关问题,但仍然不明白为什么会发生这种情况。由于我有主机名、端口、证书和密钥,我期望能够成功创建TLS连接。
第二个问题是如何建立TLS连接?我的脚本是否正确?
非常感谢您提供任何改进脚本的意见。
1个回答

13

首先,绝不能拥有私钥!正如其名称所示,它是私有的,不必要用于建立连接。
你可以拥有公钥,但只要使用标准 SSL 并信任签署服务器证书的 CA,甚至公钥也不是必需的。
你确定它是私钥吗?文件是否以 -----BEGIN PRIVATE KEY----- 开头?请使用openssl rsa -noout -text -in server.key 进行检查。
有关非对称加密的更多信息,请参阅维基百科文章此帖子

下一步:
通过 socket.bind(),你将套接字绑定到本地机器上的某个端口。这是不可能的,因为你没有地址(你提供了服务器地址)。
从你的代码中看,你似乎正在尝试以服务器身份打开套接字。你将需要私钥才能这样做,但随后你将接受连接而不是自己连接其他机器。我有一种感觉,你在把两件事情搞混了。
有关 socket.bind() 的详细信息,请参阅Python 文档,有关此问题的信息,请参阅此问题,因为这似乎与之密切相关。
还请查看Python SSL 文档。我从该文档中选取了一个符合你要求的示例。

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

另外请参考如何在服务器模式下打开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()

运行它:

% python3 ssltest.py
b'hello server!\n'
b'this is data\n'

客户端:

% 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

如果您使用像HTTP这样的常用协议,建议使用库来实现。以下是使用Flask的示例:

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

非常感谢您的回答。我想我误解了“服务器端”和“客户端”操作。由于我拥有私钥,我应该执行服务器操作。我仍然在身份验证和握手程序方面遇到问题。它们是否是wrap_socket()函数的一部分? - Ali
1
认证并不一定是SSL的一部分,除非您让客户端使用自己的证书进行身份验证。握手是在客户端连接到服务器时完成的,但这是在ssl库中实现的,所以您不需要真正担心它。wrap socket函数只是将SSL层放在普通网络层之上。之后,您应该能够像使用普通网络套接字一样使用套接字。这有意义吗? - toydarian
1
@Ali 我添加了一个示例,展示如何打开一个 SSL 套接字。 - toydarian
感谢您的详细回答。当我按照您建议的使用openssl s_client命令时,一切都很顺利,我可以向服务器发送数据。然而,当我尝试使用Python代码(客户端)发送一些数据时,它失败了。这是发送数据到服务器的Python代码:https://stackoverflow.com/q/63326571/9696715 - Ali

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接