通信人家园

 找回密码
 注册

只需一步,快速开始

短信验证,便捷登录

搜索

军衔等级:

  新兵

注册:2013-12-13
跳转到指定楼层
1#
发表于 2019-10-11 00:10:58 |只看该作者 |倒序浏览
[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'))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

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模块,如果有不清楚的也可以自己阅读下官方文档




举报本楼

您需要登录后才可以回帖 登录 | 注册 |

Archiver|手机版|C114 ( 沪ICP备12002291号-1 )|联系我们 |网站地图  

GMT+8, 2024-4-26 11:52 , Processed in 0.272385 second(s), 15 queries , Gzip On.

Copyright © 1999-2023 C114 All Rights Reserved

Discuz Licensed

回顶部