python2.7建立https C/S交互 && 实现openssl enc加解密
前言
https是基于SSL/TLS的http协议,能够保证数据传输的安全性,避免如http报文传输过程中数据被劫持篡改的风险。
本文基于python2的ssl库以及httplib库模拟https客户端以及服务端进行通信。
SSL/TLS认证模式
- 双向认证:客户端与服务端互相认证,两者之间将会交换证书;
- 单向认证:客户端会认证服务器身份,而服务器不会对客户端身份进行验证
SSL/TLS握手过程
Client | Server |
1.Client Hello | |
2.Server Hello 3.Certificate 4.(Server_Key_Exchange) 5.(Certificate_Request) 6.Server_Hello_Done |
|
7.(Certificate) 8.Client_Key_Exchange 9.(Certificate_Verify) 10.Change_Cypher_Spec ----finished---- |
|
11.Change_Cypher_Spec ----finished---- |
1.Client Hello:
(1)支持的协议版本,比如TLS 1.0
(2)支持的加密算法(Cipher Specs)
(3)客户端生成的随机数1(Challenge),稍后用于生成"对话密钥"。
2.Server Hello
(1) 确认使用的协议版本
(2) 服务器生成的随机数2,稍后用于生成"对话密钥"
(3) session id
(4) 确认使用的加密算法
3.Certificate
服务器证书
4.(Server_Key_Exchange)
如果是DH算法,这里发送服务器使用的DH参数。RSA算法不需要这一步。
5.(Certificate_Request)
要求客户端提供证书,包括
(1) 客户端可以提供的证书类型
(2)服务器接受的证书distinguished name列表,可以是root CA或者subordinate CA。如果服务器配置了trust keystore, 这里会列出所有在trust keystore中的证书的distinguished name。
6.Server_Hello_Done
server hello结束
7.(Certificate)
客户端证书
8.Client_Key_Exchange
包含pre-master secret。客户端生成第三个随机数。如果是采用RSA算法,会生成一个48字节随机数,然后用server的公钥加密之后再放入报文中;如果是DH算法,这里发送的就是客户端的DH参数,之后服务器和客户端根据DH算法,各自计算出相同的pre-master secret。
9.(Certificate_Verify)
发送使用客户端证书给到这一步为止收到和发送的所有握手消息签名结果。
10.Change_Cypher_Spec
客户端通知服务器开始使用加密方式发送报文。客户端使用上面的3个随机数client random, server random, pre-master secret, 计算出48字节的master secret, 这个就是对称加密算法的密钥。
finished
客户端发送第一个加密报文。使用HMAC算法计算收到和发送的所有握手消息的摘要,然后通过RFC5246中定义的一个伪函数PRF计算出结果,加密后发送。
11.Change_Cypher_Spec
----finished----
服务器端发送change_cipher_spec和finished消息。到这里握手结束
HTTPS客户端
1 import httplib 2 import ssl 3 import socket 4 5 def start_client(ip_addr): 6 print("启动https客户端......") 7 ssl._create_default_https_context = ssl._create_unverified_context;#重要,忽略证书认证 8 conn = httplib.HTTPSConnection(ip_addr) 9 print(str(conn.host) + str(conn.port)) 10 sock = socket.create_connection((conn.host, conn.port)) 11 conn.sock = ssl.wrap_socket(sock, ssl_version=ssl.PROTOCOL_SSLv23)#忽略服务器证书认证 12 print '\033[0;31;40mIX success to connect to ' + ip_addr + '\033[0m' 13 #conn.sock = ssl.wrap_socket(sock, ca_certs='ca/rsa_ca.pem', cert_reqs=ssl.CERT_REQUIRED, ssl_version=ssl.PROTOCOL_SSLv23)#验证模式 14 return conn
HTTPS服务端
1 import ssl 2 import hashlib 3 from BaseHTTPServer import HTTPServer,BaseHTTPRequestHandler 4 5 def start_server(): 6 print("启动https服务端......") 7 ip_addr = get_server_role().split(':') 8 ip = ip_addr[0] 9 port = int(ip_addr[1]) 10 addr = (ip, port) 11 context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) 12 context.load_cert_chain("ca/rsa_local.pem","ca/rsa_local.key") 13 server = HTTPServer(addr,RequestHandler) 14 server.socket = context.wrap_socket(server.socket, server_side = True) 15 sa = server.socket.getsockname() 16 print("Serving HTTPS on %s:%d" % (sa[0],sa[1])) 17 server.serve_forever()
加密算法(openssl enc加密)
使用的加密算法为aes-256-cbc对称加密算法
对应openssl对称加密算法命令:
- 加密:openssl enc -e -aes-256-cbc -base64 -pass password
- 解密:openssl enc -d -aes-256-cbc -base64 -pass password
- openssl enc相关参数:-iv IV、-K key、-nopad、-pass arg
实现难点:如果需要实现的openssl enc命令指定的参数是-K、-iv,则可以生成需要的key或iv值直接用于加解密;由于需要实现的是指定-pass,所以需要根据pass生成key以及iv值;
在C语言中openssl函数库提供函数EVP_BytesToKey(),通过pass生成对应的key以及iv值;python没有库(或者我不知道)提供该函数的功能,需要自己去实现;EVP_BytesToKey()实现
原理是通过md5算法生成key、iv,另外通过阅读openssl源码得知,指定-salt时存在魔术字"Salted__";
salt相关信息:https://blog.csdn.net/kkxgx/article/details/12879367(转)
1 # -*- coding: utf-8 -*- 2 """使用md5算法生成key和iv""" 3 import base64 4 from hashlib import md5 5 from Crypto import Random 6 from Crypto.Cipher import AES 7 from binascii import b2a_hex, a2b_hex 8 #相关源码文件enc.c 9 #EVP_BytesToKey()函数的实现原理 10 #如果未指定salt,下面改为d_i = md5(d_i + password).digest(),有关salt的都删除,根据实验,进行一次md5加密,将生成16位结果 11 #md5加密原理 https://blog.csdn.net/weixin_34362790/article/details/87039959 12 #hash1_128 = MD5(Passphrase+salt) 13 #hash2_128 = MD5(hash1_128 + Passphrase + salt) 14 #hash3_128 = MD5(hash2_128 + Passphrase + salt) 15 #Key = hash1_128 + hash2_128 16 #IV = hash3_128 17 18 #根据pass参数生成key,iv 19 def encode(s): 20 return ' '.join([bin(ord(c)).replace('0b', '') for c in s]) 21 22 def derive_key_and_iv(password, salt, key_length, iv_length): 23 d = d_i = '' 24 while len(d) < key_length + iv_length: 25 d_i = md5(d_i + password + salt).digest() 26 d += d_i 27 return d[:key_length], d[key_length:key_length+iv_length] 28 29 def encrypt(data, password): 30 bs = AES.block_size 31 salt = Random.new().read(bs - len('Salted__')) 32 key, iv = derive_key_and_iv(password, salt, 32, 16) 33 #填充算法如下:重点chr(bs - len(s) % bs) 34 pad = lambda s: s + (bs - len(s) % bs) * chr(bs - len(s) % bs) 35 cipher = AES.new(key, AES.MODE_CBC, iv) 36 data = cipher.encrypt(pad(data)) 37 print('encrypt:len') 38 print(len(data)) 39 #important:'Salted__' + salt不能有任何修改 40 #在解密时-S指定的salt是无效的 41 return base64.b64encode('Salted__' + salt + data) 42 43 def decrypt(data, password): 44 data = base64.b64decode(data) 45 print(len(data)) 46 salt = data[8:16] 47 data = data[16:] 48 bs = AES.block_size 49 key, iv = derive_key_and_iv(password, salt, 32, 16) 50 print(key, iv) 51 unpad = lambda s : s[0:-ord(s[-1])] 52 cipher = AES.new(key, AES.MODE_CBC, iv) 53 data = unpad(cipher.decrypt(data)) 54 return data