[color=rgba(0, 0, 0, 0.75)]python关于SSL/TLS认证的实现
纯技术贴,不喜勿踩。
本文链接:https://blog.csdn.net/vip97yigang/article/details/84721027
最近有个客户端的需求是和服务端建立安全的链路,需要用ssl双向认证的方式实现。刚开始的时候被各种证书认证搞得晕乎乎-_-,花了好长时间才理清思路实现需求,所以写下这篇文章记录分享。先先容下啥是SSL,后面给出demo源码。 参考https://blog.csdn.net/wuliganggang/article/details/78428866
https://blog.csdn.net/duanbokan/article/details/50847612
https://blog.csdn.net/zhangtaoym/article/details/55259889 SSL/TSL简要先容SSL是安全套接层(secure sockets layer),而TLS是SSL的继任者,叫传输层安全(transport layer security)。是为网络通信提供安全及数据完整性的一种安全协议。TLS与SSL在传输层对网络连接进行加密。
比如如HTTP协议是明文传输,加上SSL层之后,就有了雅称HTTPS。它的发展依次经历了下面几个时期
SSL1.0: 已废除
SSL2.0: RFC6176,已废除
SSL3.0: RFC6101,基本废除
TLS1.0: RFC2246,目前大都采用此种方式
TLS1.1: RFC4346
TLS1.2: RFC5246,没有广泛使用
TLS1.3: RFC 8446,于2018年8月发表 流程证书CA: 证书授权中心( certificate authority)。 它呢,类似于国家出入境管理处一样,给别人颁发护照;也类似于国家工商管理局一样,给企业企业颁发营业执照。
它有两大主要性质: - CA本身是受信任的 // 国际认可的
- 给他受信任的申请对象颁发证书 // 和办理护照一样,要确定你的合法身份,你不能是犯罪分子或造反派。当然,你需要被收保护费,同时,CA可以随时吊销你的证书。
证书长啥样?其实你的电脑中有一堆CA证书。你可以看一看嘛:
360浏览器: 选项/设置-> 高级设置 -> 隐私于安全 -> 管理 HTTPS/SSL 证书 -> 证书颁发机构
火狐浏览器: 首选项 -> 高级 -> 证书 -> 查看证书 -> 证书机构
chrome浏览器: 设置 -> 高级 -> 管理证书 -> 授权中心
ubuntu: /etc/ssl/certs
这些都是 CA 的证书!
CA 的证书 ca.crt 和 SSL Server的证书 server.crt 是什么关系呢? - SSL Server 自己生成一个 私钥/公钥对。server.key/server.pub // 私钥加密,公钥解密!
- server.pub 生成一个请求文件 server.req. 请求文件中包含有 server 的一些信息,如域名/申请者/公钥等。
- server 将请求文件 server.req 递交给 CA,CA验明正身后,将用 ca.key和请求文件加密生成 server.crt
- 由于 ca.key 和 ca.crt 是一对, 于是 ca.crt 可以解密 server.crt.
在实际应用中:如果 SSL Client 想要校验 SSL server.那么 SSL server 必须要将他的证书 server.crt 传给 client.然后 client 用 ca.crt 去校验 server.crt 的合法性。如果是一个钓鱼网站,那么CA是不会给他颁发合法server.crt证书的,这样client 用ca.crt去校验,就会失败。比如浏览器作为一个 client,你想访问合法的淘宝网站https://www.taobao.com, 结果不慎访问到 https://wwww.jiataobao.com ,那么浏览器将会检验到这个假淘宝钓鱼网站的非法性,提醒用户不要继续访问!这样就可以保证了client的所有https访问都是安全的。
SSL握手SSL/TLS协商的过程可以参考这篇文章SSL/TLS协商过程详解 证书生成makefile.sh # * Redistributions in binary form must reproduce the above copyright# notice, this list of conditions and the following disclaimer in the# documentation and/or other materials provided with the distribution.# * Neither the name of the axTLS project nor the names of its# contributors may be used to endorse or promote products derived# from this software without specific prior written permission.## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.### Generate the certificates and keys for testing.#PROJECT_NAME="TLS Project"# Generate the openssl configuration files.cat > ca_cert.conf << EOF [ req ]distinguished_name = req_distinguished_nameprompt = no[ req_distinguished_name ] O = $PROJECT_NAME Dodgy Certificate AuthorityEOFcat > server_cert.conf << EOF [ req ]distinguished_name = req_distinguished_nameprompt = no[ req_distinguished_name ] O = $PROJECT_NAME CN = 192.168.111.100EOFcat > client_cert.conf << EOF [ req ]distinguished_name = req_distinguished_nameprompt = no[ req_distinguished_name ] O = $PROJECT_NAME Device Certificate CN = 192.168.111.101EOFmkdir camkdir servermkdir clientmkdir certDER# private key generationopenssl genrsa -out ca.key 1024openssl genrsa -out server.key 1024openssl genrsa -out client.key 1024# cert requestsopenssl req -out ca.req -key ca.key -new \ -config ./ca_cert.confopenssl req -out server.req -key server.key -new \ -config ./server_cert.conf openssl req -out client.req -key client.key -new \ -config ./client_cert.conf # generate the actual certs.openssl x509 -req -in ca.req -out ca.crt \ -sha1 -days 5000 -signkey ca.keyopenssl x509 -req -in server.req -out server.crt \ -sha1 -CAcreateserial -days 5000 \ -CA ca.crt -CAkey ca.keyopenssl x509 -req -in client.req -out client.crt \ -sha1 -CAcreateserial -days 5000 \ -CA ca.crt -CAkey ca.keyopenssl x509 -in ca.crt -outform DER -out ca.deropenssl x509 -in server.crt -outform DER -out server.deropenssl x509 -in client.crt -outform DER -out client.dermv ca.crt ca.key ca/mv server.crt server.key server/mv client.crt client.key client/mv ca.der server.der client.der certDER/rm *.confrm *.reqrm *.srl - 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
做如下修改,终端实行。 - 修改 CN 域中 IP 地址为你主机/设备的 IP 地址
- [可选]加密位数 1024 修改为你需要的加密位数
ca目录:保存ca的私钥ca.key和证书ca.crt
certDER目录:将证书保存为二进制文件 ca.der client.der server.der
client目录: client.crt client.key
server目录:server.crt server.key
实现单向认证源码server from flask import Flaskapp = Flask(__name__)@app.route('/')def hello_world(): return 'Hello World!'if __name__ == '__main__': app.run(debug=True, ssl_context=('server.crt', 'server.key'))client import urllib.requestimport sslif __name__ == '__main__': CA_FILE = "ca.crt" context = ssl.SSLContext(ssl.PROTOCOL_TLS) context.check_hostname = False context.load_verify_locations(CA_FILE) context.verify_mode = ssl.CERT_REQUIRED try: request = urllib.request.Request('https://127.0.0.1:5000/') res = urllib.request.urlopen(request, context=context) print(res.code) print(res.read().decode("utf-8")) except Exception as ex: print("Found Error in auth phase:%s" % str(ex))- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
https的测试本来使用使用浏览器,只要将ca证书安装到本地就可能使用浏览器访问,但是因为认证过程需要检测验证hostname,测试使用的自签名证书信息是随意填写的,所以即使安装了证书浏览器也会提示链接不安全。所以客户端使用ssl模块的load_verify_locations()方法加载根证书,并且将check_hostname设置为False。 双向认证源码https版本
server from flask import Flaskimport sslapp = Flask(__name__)@app.route('/')def hello_world(): return 'Hello World!'if __name__ == '__main__': CA_FILE = "ca.crt" KEY_FILE = "server.key" CERT_FILE = "server.crt" context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) context.load_cert_chain(certfile=CERT_FILE, keyfile=KEY_FILE) context.load_verify_locations(CA_FILE) context.verify_mode = ssl.CERT_REQUIRED app.run(debug=True, ssl_context=context) - 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
client import urllib.requestimport sslif __name__ == '__main__': CA_FILE = "ca.crt" KEY_FILE = "client.key" CERT_FILE = "client.crt" context = ssl.SSLContext(ssl.PROTOCOL_TLS) context.check_hostname = False context.load_cert_chain(certfile=CERT_FILE, keyfile=KEY_FILE) context.load_verify_locations(CA_FILE) context.verify_mode = ssl.CERT_REQUIRED try: # 通过request()方法创建一个请求: request = urllib.request.Request('https://127.0.0.1:5000/') res = urllib.request.urlopen(request, context=context) print(res.code) print(res.read().decode("utf-8")) except Exception as ex: print("Found Error in auth phase:%s" % str(ex))- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
socket版本
server import socketimport sslclass server_ssl: def build_listen(self): CA_FILE = "ca.crt" KEY_FILE = "server.key" CERT_FILE = "server.crt" context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) context.load_cert_chain(certfile=CERT_FILE, keyfile=KEY_FILE) context.load_verify_locations(CA_FILE) context.verify_mode = ssl.CERT_REQUIRED # 监听端口 with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock: # 将socket打包成SSL socket with context.wrap_socket(sock, server_side=True) as ssock: ssock.bind(('127.0.0.1', 5678)) ssock.listen(5) while True: # 接收客户端连接 client_socket, addr = ssock.accept() # 接收客户端信息 msg = client_socket.recv(1024).decode("utf-8") print(f"receive msg from client {addr}:{msg}") # 向客户端发送信息 msg = f"yes , you have client_socketect with server.\r\n".encode("utf-8") client_socket.send(msg) client_socket.close()if __name__ == "__main__": server = server_ssl() server.build_listen()- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
client import socketimport sslclass client_ssl: def send_hello(self,): CA_FILE = "ca.crt" KEY_FILE = "client.key" CERT_FILE = "client.crt" context = ssl.SSLContext(ssl.PROTOCOL_TLS) context.check_hostname = False context.load_cert_chain(certfile=CERT_FILE, keyfile=KEY_FILE) context.load_verify_locations(CA_FILE) context.verify_mode = ssl.CERT_REQUIRED # 与服务端建立socket连接 with socket.socket() as sock: # 将socket打包成SSL socket with context.wrap_socket(sock, server_side=False) as ssock: ssock.connect(('127.0.0.1', 5678)) # 向服务端发送信息 msg = "do i connect with server ?".encode("utf-8") ssock.send(msg) # 接收服务端返回的信息 msg = ssock.recv(1024).decode("utf-8") print(f"receive msg from server : {msg}") ssock.close()if __name__ == "__main__": client = client_ssl() client.send_hello()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
ssl认证主要用到python的ssl模块,如果有不清楚的也可以自己阅读下官方文档。
|