OpenSSL实现单向认证

OpenSSL的安装

OpenSSL采用C语言作为开发语言,这使得它具有优秀的跨平台性能,OpenSSL支持:

win和mac需要单独去下OpenSSL客户端,网上也有很多教程,可以自己去找找看

Linux系统自带OpenSSL库,一般不需要下载安装,我会使用KALI Linux进行本次实验

查看下openssl的版本:
avatar

简单通信过程

下面以最常见的单向认证为例,简述一下通信过程:

  1. 客户端向服务器发送请求

  2. 服务器将包含公钥的证书通过明文发送给客户端

  3. 客户端通过根证书验证服务器证书是否有效

  4. 如果有效,客户端生成一个随机密钥,也叫对称加密密钥,使用服务器的公钥进行加密传输

  5. 服务器通过私钥解密客户端传输的对称加密密钥

  6. 这时只有服务器和客户端知道这个对称加密密钥,双方可以进行对称加密传输

avatar

颁发证书

通过vim /usr/lib/ssl/openssl.cnf修改配置文件,这里修改了默认文件夹、私钥名、证书名。
avatar
avatar

在默认文件夹创建所需的目录和文件

1
2
3
4
mkdir -pv ssl
cd ssl
mkdir -pv {certs,csr,newcerts,private}
touch {serial,index.txt,index.txt.attr}

avatar

指明证书开始的编号

1
echo 01 >> serial

生成根证书私钥

1
openssl genrsa -out private/ca.key 2048

  • genrsa: 产生rsa密钥命令

  • -out: 输出路径

这里的参数2048,指的是密钥的长度位数,默认长度为512位,密钥越长,安全性越高,但是生成速度也越慢。

生成自签名证书

1
openssl req -new -x509 -key private/ca.key -out ca.crt -days 365
  • -new:表示生成一个新证书签署请求

  • -x509:专用于CA生成自签证书,如果不是自签证书则不需要此项

  • -key:用到的私钥文件

  • -out:证书的保存路径

  • -days:证书的有效期限,单位是day(天),默认是openssl.cnf的default_days

avatar

生成服务器密钥

1
openssl genrsa -out private/server.key 2048

生成请求文件,图中划线的这几项需和根证书一致,后几项最好也保持一致。

1
openssl req -new -key private/server.key -out csr/server.csr

avatar

颁发证书

1
openssl ca -in csr/server.csr -out certs/server.crt

avatar

验证

下面通过一个例程来模拟单向SSL认证,服务器需要准备好刚刚签发的服务器证书,和一定要好好保管的密钥,客户端只需准备好根证书就行,用来验证服务器的证书。

服务端代码

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
import socket
import ssl

class server_ssl:
def build_listen(self):
# 生成SSL上下文
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
# 加载服务器所用证书和私钥
context.load_cert_chain('server.crt', 'server.key')

# 监听端口
with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
sock.bind(('0.0.0.0', 8888))
sock.listen(5)
# 将socket打包成SSL socket
with context.wrap_socket(sock, server_side=True) as ssock:
while True:
# 接收客户端连接
client_socket, addr = ssock.accept()
# 接收客户端信息
msg = client_socket.recv(1024).decode("utf-8")
print("receive msg from client {}:{}".format(addr, msg))
# 向客户端发送信息
msg = "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
import socket
import ssl

class client_ssl:
def send_hello(self,):
# 生成SSL上下文
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
# 加载信任根证书
context.load_verify_locations('ca.crt')

# 与服务端建立socket连接
with socket.create_connection(('0.0.0.0', 8888)) as sock:
# 将socket打包成SSL socket
# 一定要注意的是这里的server_hostname不是指服务端IP,而是指服务端证书中的CN。
with context.wrap_socket(sock, server_hostname='USER1') as ssock:
# 向服务端发送信息
msg = "do i connect with server ?".encode("utf-8")
ssock.send(msg)
# 接收服务端返回的信息
msg = ssock.recv(1024).decode("utf-8")
print("receive msg from server : {}".format(msg))
ssock.close()

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

结果

服务端
avatar

客户端
avatar