python关于SSL/TLS认证的实现

最近有个客户端的需求是和服务端建立安全的链路,需要用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月发表

流程

SSL/TLS 工作流

证书

CA: 证书授权中心( certificate authority)。 它呢,类似于国家出入境管理处一样,给别人颁发护照;也类似于国家工商管理局一样,给公司企业颁发营业执照。
它有两大主要性质:

  1. CA本身是受信任的 // 国际认可的
  2. 给他受信任的申请对象颁发证书 // 和办理护照一样,要确定你的合法身份,你不能是犯罪分子或造反派。当然,你需要被收保护费,同时,CA可以随时吊销你的证书。
    证书长啥样?其实你的电脑中有一堆CA证书。你可以看一看嘛:
    360浏览器: 选项/设置-> 高级设置 -> 隐私于安全 -> 管理 HTTPS/SSL 证书 -> 证书颁发机构
    火狐浏览器: 首选项 -> 高级 -> 证书 -> 查看证书 -> 证书机构
    chrome浏览器: 设置 -> 高级 -> 管理证书 -> 授权中心
    ubuntu: /etc/ssl/certs
    这些都是 CA 的证书!
    CA 的证书 ca.crt 和 SSL Server的证书 server.crt 是什么关系呢?
  3. SSL Server 自己生成一个 私钥/公钥对。server.key/server.pub // 私钥加密,公钥解密!
  4. server.pub 生成一个请求文件 server.req. 请求文件中包含有 server 的一些信息,如域名/申请者/公钥等。
  5. server 将请求文件 server.req 递交给 CA,CA验明正身后,将用 ca.key和请求文件加密生成 server.crt
  6. 由于 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_name
prompt                 = no

[ req_distinguished_name ]
 O                      = $PROJECT_NAME Dodgy Certificate Authority
EOF

cat > server_cert.conf << EOF  
[ req ]
distinguished_name     = req_distinguished_name
prompt                 = no

[ req_distinguished_name ]
 O                      = $PROJECT_NAME
 CN                     = 192.168.111.100
EOF

cat > client_cert.conf << EOF  
[ req ]
distinguished_name     = req_distinguished_name
prompt                 = no

[ req_distinguished_name ]
 O                      = $PROJECT_NAME Device Certificate
 CN                     = 192.168.111.101
EOF

mkdir ca
mkdir server
mkdir client
mkdir certDER

# private key generation
openssl genrsa -out ca.key 1024
openssl genrsa -out server.key 1024
openssl genrsa -out client.key 1024

# cert requests
openssl req -out ca.req -key ca.key -new \
            -config ./ca_cert.conf
openssl 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.key
openssl x509 -req -in server.req -out server.crt \
            -sha1 -CAcreateserial -days 5000 \
            -CA ca.crt -CAkey ca.key
openssl x509 -req -in client.req -out client.crt \
            -sha1 -CAcreateserial -days 5000 \
            -CA ca.crt -CAkey ca.key

openssl x509 -in ca.crt -outform DER -out ca.der
openssl x509 -in server.crt -outform DER -out server.der
openssl x509 -in client.crt -outform DER -out client.der

mv 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 *.conf
rm *.req
rm *.srl 

做如下修改,终端执行。

  • 修改 CN 域中 IP 地址为你主机/设备的 IP 地址
  • [可选]加密位数 1024 修改为你需要的加密位数
    python关于SSL/TLS认证的实现_第1张图片
    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 Flask

app = 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.request

import ssl

if __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))

https的测试本来使用使用浏览器,只要将ca证书安装到本地就可能使用浏览器访问,但是因为认证过程需要检测验证hostname,测试使用的自签名证书信息是随意填写的,所以即使安装了证书浏览器也会提示链接不安全。所以客户端使用ssl模块的load_verify_locations()方法加载根证书,并且将check_hostname设置为False。

双向认证

双向认证

源码

https版本
server

from flask import Flask
import ssl

app = 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)
    

client

import urllib.request

import ssl

if __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))

socket版本
server

import socket
import ssl

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

client

import socket
import ssl

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

ssl认证主要用到python的ssl模块,如果有不清楚的也可以自己阅读下官方文档。

你可能感兴趣的:(python,python,ssl,https,ca证书,双向认证)