1、Python应用:SSL自签证书生成及双向认证验证(python)

目录

🍅点击这里查看所有博文

  随着自己工作的进行,接触到的技术栈也越来越多。给我一个很直观的感受就是,某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了,只有经常会用到的东西才有可能真正记下来。存在很多在特殊情况下有一点用处的技巧,用的不多的技巧可能一个星期就忘了。

  想了很久想通过一些手段把这些事情记录下来。也尝试过在书上记笔记,这也只是一时的,书不在手边的时候那些笔记就和没记一样,不是很方便。

  很多时候我们遇到了问题,一般情况下都是选择在搜索引擎检索相关内容,这样来的也更快一点,除非真的找不到才会去选择翻书。后来就想到了写博客,博客作为自己的一个笔记平台倒是挺合适的。随时可以查阅,不用随身携带。

  同时由于写博客是对外的,既然是对外的就不能随便写,任何人都可以看到。经验对于我来说那就只是经验而已,公布出来说不一定我的一些经验可以帮助到其他的人。遇到和我相同问题时可以少走一些弯路。

  既然决定了要写博客,那就只能认真去写。不管写的好不好,尽力就行。千里之行始于足下,一步一个脚印,慢慢来 ,写的多了慢慢也会变好的。权当是记录自己的成长的一个过程,等到以后再往回看时,就会发现自己以前原来这么菜😂。

  本系列博客所述资料均来自互联网,并不是本人原创(只有博客是自己写的)。出于热心,本人将自己的所学笔记整理并推出相对应的使用教程,方面其他人学习。为国内的物联网事业发展尽自己的一份绵薄之力,没有为自己谋取私利的想法。若出现侵权现象,请告知本人,本人会立即停止更新,并删除相应的文章和代码。

linux下安装openssl

查看

openssl version

该指令查看openssl的版本号,返回错误就说明没有openssl嘛,进行下面的安装步骤

获取

wget --use_no_certificate https://www.openssl.org/source/openssl-1.1.1b.tar.gz

解压

tar -xvf openssl-1.1.1f.tar.gz

编译

cd openssl-1.1.1f
make & make install 

配置

vim /etc/ld.so.conf
#添加配置
/usr/local/bin
/usr/local/lib
/usr/local/ssl

创建自签证书

需要注意几点:

  • openssl库后的字符代表的安全等级不一样,如openssl1.1.1f中的f
  • 启动服务器的时候提示秘钥太短,通过修改生成key的长度 openssl genrsa -des3 -out ca.key 2048 中的2048
  • 提示md太弱,修改下面脚本中的:default_md = sha256
    开始为md5,后面我修改为sha256才可以
  • 运行脚本之后会一步一步让你输入消息,注意到Common Name的时候CA的name和其他证书的name不能一样
  • 服务器搭建的时候绑定的ip要为0.0.0.0,本地测试用127.0.0.1没关系

签名脚本

openssl ca命令有一些奇怪的要求,并且默认的openssl配置不允许直接轻松地使用openssl ca。因此,我们创建这个sign.sh程序来替换它。创建sign.sh程序文件,点击/usr/bin/sign.sh并添加到这个文件:

#!/bin/sh
##
##  sign.sh -- Sign a SSL Certificate Request (CSR)
##  Copyright (c) 1998-1999 Ralf S. Engelschall, All Rights Reserved. 
##

#   argument line handling
CSR=$1
if [ $# -ne 1 ]; then
echo "Usage: sign.sign <whatever>.csr"; exit 1
fi
if [ ! -f $CSR ]; then
echo "CSR not found: $CSR"; exit 1
fi
case $CSR in
*.csr ) CERT="`echo $CSR | sed -e 's/\.csr/.crt/'`" ;;
* ) CERT="$CSR.crt" ;;
esac

#   make sure environment exists
if [ ! -d ca.db.certs ]; then
mkdir ca.db.certs
fi
if [ ! -f ca.db.serial ]; then
echo '01' >ca.db.serial
fi
if [ ! -f ca.db.index ]; then
cp /dev/null ca.db.index
fi

#   create an own SSLeay config
cat >ca.config <<EOT
[ ca ]
default_ca	= CA_own
[ CA_own ]
dir	= /etc/ssl
certs	= /etc/ssl/certs
new_certs_dir	= /etc/ssl/ca.db.certs
database	= /etc/ssl/ca.db.index
serial	= /etc/ssl/ca.db.serial
RANDFILE	= /etc/ssl/ca.db.rand
certificate	= /etc/ssl/certs/ca.crt
private_key	= /etc/ssl/private/ca.key
default_days	= 365
default_crl_days	= 30
default_md	= md5
preserve	= no
policy	= policy_anything
[ policy_anything ]
countryName	= optional
stateOrProvinceName	= optional
localityName	= optional
organizationName	= optional
organizationalUnitName	= optional
commonName	= supplied
emailAddress	= optional
EOT

#  sign the certificate
echo "CA signing: $CSR -> $CERT:"
openssl ca -config ca.config -out $CERT -infiles $CSR
echo "CA verifying: $CERT <-> CA cert"
openssl verify -CAfile /etc/ssl/certs/ca.crt $CERT

#  cleanup after SSLeay 
rm -f ca.config
rm -f ca.db.serial.old
rm -f ca.db.index.old

#  die gracefully
exit 0

自动生成自签证书脚本

#!/bin/sh
#
# ssl 证书输出的根目录。
sslOutputRoot="./cert_ssl"
if [ $# -eq 1 ]; then
	 sslOutputRoot=$1
 fi
 if [ ! -d ${sslOutputRoot} ]; then
	  mkdir -p ${sslOutputRoot}
  fi

  cd ${sslOutputRoot}

  echo "开始创建CA根证书..."
  #
  # 创建CA根证书,稍后用来签署用于服务器的证书。如果是通过商业性CA如
  # Verisign 或 Thawte 签署证书,则不需要自己来创建根证书,而是应该
  # 把后面生成的服务器 csr 文件内容贴入一个web表格,支付签署费用并
  # 等待签署的证书。关于商业性CA的更多信息请参见:
  # Verisign - http://digitalid.verisign.com/server/apacheNotice.htm
  # Thawte Consulting - http://www.thawte.com/certs/server/request.html
  # CertiSign Certificadora Digital Ltda. - http://www.certisign.com.br
  # IKS GmbH - http://www.iks-jena.de/produkte/ca /
  # Uptime Commerce Ltd. - http://www.uptimecommerce.com
  # BelSign NV/SA - http://www.belsign.be
  # 生成CA根证书私钥
  openssl genrsa -des3 -out ca.key 2048

  # 生成CA根证书
  # 根据提示填写各个字段, 但注意 Common Name 最好是有效根域名(如 zeali.net ),
  # 并且不能和后来服务器证书签署请求文件中填写的 Common Name 完全一样,否则会
  # 导致证书生成的时候出现
  # error 18 at 0 depth lookup:self signed certificate 错误
  openssl req -new -x509 -days 365 -key ca.key -out ca.crt
  echo "CA根证书创建完毕。"

  echo "开始生成服务器证书签署文件及私钥 ..."
  #
  # 生成服务器私钥
  openssl genrsa -des3 -out server.key 2048
  # 生成服务器证书签署请求文件, Common Name 最好填写使用该证书的完整域名
  # (比如: security.zeali.net )
  openssl req -new -key server.key -out server.csr 
  ls -altrh  ${sslOutputRoot}/server.*
  echo "服务器证书签署文件及私钥生成完毕。"

  echo "开始使用CA根证书签署服务器证书签署文件 ..."
  #
  # 签署服务器证书,生成server.crt文件
  # 参见 http://www.faqs.org/docs/securing/chap24sec195.html
  #  sign.sh START
  #
  #  Sign a SSL Certificate Request (CSR)
  #  Copyright (c) 1998-1999 Ralf S. Engelschall, All Rights Reserved.
  #

  CSR=server.csr

  case $CSR in
	  *.csr ) CERT="`echo $CSR | sed -e 's/\.csr/.crt/'`" ;;
	  * ) CERT="$CSR.crt" ;;
  esac

  #   make sure environment exists
  if [ ! -d ca.db.certs ]; then
	   mkdir ca.db.certs
   fi
   if [ ! -f ca.db.serial ]; then
	    echo '01' >ca.db.serial
    fi
    if [ ! -f ca.db.index ]; then
	     cp /dev/null ca.db.index
     fi

     #   create an own SSLeay config
     # 如果需要修改证书的有效期限,请修改下面的 default_days 参数.
     # 当前设置为10年.
     cat >ca.config <<EOT
[ ca ]
default_ca = CA_own
[ CA_own ]
dir = .
certs = ./certs
new_certs_dir = ./ca.db.certs
database = ./ca.db.index
serial = ./ca.db.serial
RANDFILE = ./ca.db.rand
certificate = ./ca.crt
private_key = ./ca.key
default_days = 3650
default_crl_days = 30
default_md = sha256
preserve = no
policy = policy_anything
[ policy_anything ]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
EOT

#  sign the certificate
echo "CA signing: $CSR -> $CERT:"
openssl ca -config ca.config -out $CERT -infiles $CSR
echo "CA verifying: $CERT <-> CA cert"
openssl verify -CAfile /cert_ssl/ca.crt $CERT

#  cleanup after SSLeay
rm -f ca.config
rm -f ca.db.serial.old
rm -f ca.db.index.old
#  sign.sh END
echo "使用CA根证书签署服务器证书签署文件完毕。"


#创建客户端证书
cp -f server.crt client.crt
cp -f server.key client.key



# 使用了 ssl 之后,每次启动 apache 都要求输入 server.key 的口令,
# 你可以通过下面的方法去掉口令输入(如果不希望去掉请注释以下几行代码):
echo "去除客户端的key限制:"

cp -f client.key client.key.org
openssl rsa -in client.key.org -out client.key
echo "去除完毕。"

exit 0

知识点:实际上服务器证书和客户端都是属于同一级,都是通过CA根证书衍生的

服务器测试代码

import socket
import ssl
import threading
import time
from time import sleep
#端口范围:2900-3000


class ssl_client:
    def __init__(self,ssl_client,ssl_client_address,test_file):
        self.client = ssl_client
        self.addr = ssl_client_address
        self.test_file = test_file

    def build(self):
        # 循环接收和发送数据
        ssl_client = self.client

        while True:
            recv_data = ssl_client.recv(1024)
            
            # 有消息就回复数据,消息长度为0就是说明客户端下线了
            if recv_data:
                #print("客户端是:", tcp_client_address)
                #print("客户端发来的消息是:", recv_data.decode())
                if recv_data.decode() == "recv_big_file":
                    with open(self.test_file,mode="rb") as fd:
                        while True:
                            readbuf = fd.read(1024)
                            if len(readbuf.decode()) <= 0:
                                break                                    
                            ssl_client.send(readbuf)
                            sleep(0.01)
                else:
                    ssl_client.send(recv_data)
            else:
                print("%s 客户端下线了..." % ssl_client)
                ssl_client.close()
                break

class server_ssl:

    def __init__(self,port = 9443,client_num = 100,test_file_name = 'socket_test_file.txt'):
        self.port = port
        self.client_num = client_num
        self.test_file_name = test_file_name

    def build_server(self):
        # 生成SSL上下文
        context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER,)
        context.verify_mode = ssl.CERT_REQUIRED
        # 加载服务器所用证书和私钥
        context.load_cert_chain('cert/server.crt', 'cert/server.key','123456')
        context.load_verify_locations('cert/ca.crt')
        # 监听端口
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
            sock.bind(('0.0.0.0', self.port))
            sock.listen(self.client_num)
            # 将socket打包成SSL socket
            with context.wrap_socket(sock, server_side=True,) as ssock:
                while True:
                    # 接收客户端连接
                    try:
                        client_socket, addr = ssock.accept()
                    except:
                        print("客户端连接失败")
                        continue

                    client = ssl_client(client_socket,addr,self.test_file_name)
                    # 创建多线程对象
                    thd = threading.Thread(target = client.build, args = ())
        
                    # 设置守护主线程  即如果主线程结束了 那子线程中也都销毁了  防止主线程无法退出
                    thd.setDaemon(True)
        
                    # 启动子线程对象
                    thd.start()

                    '''
                    # 接收客户端信息
                    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(port = 2903)
    server.build_server()

客户端测试代码

import socket
import ssl
from time import sleep

class client_ssl:
    def send_hello(self,):
        # 生成SSL上下文
        context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
        # 加载信任根证书
        context.load_verify_locations('cert/ca.crt')
        context.load_cert_chain('cert/client.crt','cert/client.key')
        #这里将check_hostname,否则本地验证的时候域名会校验不过
        context.check_hostname = False
        # 与服务端建立socket连接
        with socket.create_connection(('127.0.0.1', 2903)) as sock:
            # 将socket打包成SSL socket
            # 一定要注意的是这里的server_hostname不是指服务端IP,而是指服务端证书中设置的CN,我这里正好设置成127.0.1而已
            with context.wrap_socket(sock, server_hostname='127.0.0.1') as ssock:
                # 向服务端发送信息
                send_msg = "test is ok".encode("utf-8")
                # 接收服务端返回的信息
                while True:
                    ssock.send(send_msg)
                    msg = ssock.recv(1024).decode("utf-8")
                    if len(msg) >= 0:
                        print(f"receive msg from server : {msg}")
                    sleep(2)

                ssock.close()

if __name__ == "__main__":
    client = client_ssl()
    client.send_hello()

资料

  • python的ssl库

    https://docs.python.org/3/library/ssl.html

  • 自签证书生成

    https://www.jb51.net/article/60371.htm

  那么本篇博客就到此结束了,这里只是记录了一些我个人的学习笔记,其中存在大量我自己的理解。文中所述不一定是完全正确的,可能有的地方我自己也理解错了。如果有些错的地方,欢迎大家批评指正。如有问题直接在对应的博客评论区指出即可,不需要私聊我。我们交流的内容留下来也有助于其他人查看,说不一定也有其他人遇到了同样的问题呢😂。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Python中实现双向认证,需要用到SSL库和socket库。以下是一个简单的示例代码: ```python import socket, ssl # 服务器端代码 context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) context.load_cert_chain(certfile="server.crt", keyfile="server.key") bindsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) bindsocket.bind(('localhost', 10023)) bindsocket.listen(5) while True: newsocket, fromaddr = bindsocket.accept() conn = context.wrap_socket(newsocket, server_side=True) data = conn.recv(4096) conn.send(b'Hello, client!') conn.close() # 客户端代码 context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) context.load_cert_chain(certfile="client.crt", keyfile="client.key") sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) with context.wrap_socket(sock, server_hostname='localhost') as ssock: ssock.connect(('localhost', 10023)) ssock.send(b'Hello, server!') data = ssock.recv(4096) print(data.decode()) ``` 在以上代码中,服务器端先创建一个SSLContext对象,用于设置SSL相关参数,然后创建一个bindsocket,并开始监听端口。当有连接请求时,服务器端会接受连接,并通过wrap_socket方法创建一个安全套接字对象,从而开始SSL通信。客户端同样也需要创建一个SSLContext对象,用于设置SSL相关参数,并通过wrap_socket方法创建一个安全套接字对象,然后连接服务器。 需要注意的是,在SSL通信过程中需要使用到证书和私钥,这里的certfile和keyfile参数需要设置为相应的证书文件和私钥文件的路径。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值