TLS简介
介绍
TLS(Transport Layer Security)即安全传输层协议,在两个通信应用程序之间提供保密性和数据完整性。最典型的应用就是HTTPS。HTTPS,即HTTP over TLS,就是安全的HTTP,运行在HTTP层之下,TCP层之上,为HTTP层提供数据加解密服务。也可用于email,即时通信等。
TLS协议概论
TLS协议采用主从式架构模型,用于在两个应用程序间透过网络创建起安全的连接,防止在交换数据时受到窃听及篡改。
TLS协议的优势是与高层的应用层协议(如HTTP、FTP、Telnet等)无耦合。应用层协议能透明地运行在TLS协议之上,由TLS协议进行创建加密通道需要的协商和认证。应用层协议传送的数据在通过TLS协议时都会被加密,从而保证通信的私密性。
TLS协议是可选的,必须配置客户端和服务器才能使用。主要有两种方式实现这一目标:一个是使用统一的TLS协议通信端口(例如:用于HTTPS的端口443);另一个是客户端请求服务器连接到TLS时使用特定的协议机制(例如:邮件、新闻协议和STARTTLS)。一旦客户端和服务器都同意使用TLS协议,他们通过使用一个握手过程协商出一个有状态的连接以传输数据。通过握手,客户端和服务器协商各种参数用于创建安全连接:
- 当客户端连接到支持TLS协议的服务器要求创建安全连接并列出了受支持的密码组合(加密密码算法和散列算法),握手开始;
- 服务器从该列表中决定加密算法和散列算法,并通知客户端;
- 服务器发回其数字证书,此证书通常包含服务器的名称、受信任的证书颁发机构(CA)和服务器的公钥;
- 客户端验证其收到的服务器证书的有效性;
- 为了生成会话密钥用于安全连接,客户端使用服务器的公钥加密随机生成的密钥,并将其发送到服务器,只有服务器才能使用自己的私钥解密;
- 利用随机数,双方生成用于加密和解密的对称密钥。这就是TLS协议的握手,握手完毕后的连接是安全的,直到连接(被)关闭。如果上述任何一个步骤失败,TLS握手过程就会失败,并且断开所有的连接。
TLS报文交互过程
详细讲解:https://zhuanlan.zhihu.com/p/440612523
通过上图可知,我们这里说的TLS握手主要讲的是最基本的TLS握手,即只使用服务器的证书来进行加密,具体步骤如下:
1.客户端与服务器之间通过3次握手建立连接
2.协商阶段
a.client发送一个ClientHello消息给server,这个消息包含了client支持的最高的TLS协议版本,一个随机数,当前client支持的密码组列表和建议的压缩算法
b.server发送一个ServerHello消息给client,这个消息包含了TLS协议版本,一个服务器随机数,根据客户端支持的密码组列表最终选择的密码和最终选择的压缩算法。
c.server发送Certificate(证书)消息
d.server发送ServerKeyExchange消息
e.server发送ServerHelloDone消息,表明握手协商已经完成
f.client发送ClientKeyExchange消息,这个消息包含了PreMasterSecret(这个值是根据server发送的Certificate里面的public key通过加密生成的)和 public key。(注意在发送消息之前,client会对server发送的Certificate进行验证–可参考下面的数字证书,如果验证失败,则握手失败)
g.client和server根据client的随机数,server端的随机数和PreMasterSecret计算出一个共同的密码,这个共同的密码叫做master secret)
3.client发送ChangeCipherSpec记录,告诉server我后面发送的所有消息都会是加密的了
a.client发送一个已验证并且加密的Finished消息。包含了对之前消息的hash和mac
b.server对接收的Finished消息进行解密并验证hash和mac,如果验证失败,则握手失败
4.server发送ChangeCipherSpec消息,告诉client我后面发送的所有消息都会是加密的了
a.server发送一个已验证并且加密的Finished消息。包含了对之前消息的hash和mac
b.client 对接收的Finished消息进行解密并验证hash和mac,如果验证失败,则握手失败
5.应用阶段:此阶段说明TLS握手已经完成,后面所有发送的消息都会是加密的
openssl使用
服务器使用openssl生成证书
'
生成证书以及CA根证书
1.生成CA私钥
2.生成CA根证书
3.生成私钥
4.生成证书请求
5.根据证书请求、CA根证书、CA私钥生成证书(pem格式)。
'
openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.pem -subj "/C=US/ST=Utah/L=Lehi/O=Your Company, Inc./OU=IT/CN=rootca.com"
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr -subj "/C=US/ST=Utah/L=Lehi/O=Your Company, Inc./OU=IT/CN=test-cert"
openssl x509 -req -in server.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out server.pem -days 1000 -sha256
'
server.pem证书
server.key 证书私钥
rootCA.pem CA根证书
rootCA.key CA根证书私钥
'
Aes256加密的私钥:openssl genrsa -aes256 -passout pass:111111 -out CA.key 2048
使用私钥生成证书:openssl req -x509 -new -nodes -key CA.key -sha256 -days 1024 -out CA.pem -subj "/C=CN/ST=GD/L=SZ/O=CA/OU=dev/CN=CA.com/emailAddress=ca@ca.com" -passin pass:111111
CA签名证书:openssl x509 -req -in actor1.csr -CA CA.pem -CAkey CA.key -CAcreateserial -out actor1.pem -days 1000 -sha256 -passin pass:111111
#参考
#http://t.zoukankan.com/sxFu-p-13223332.html
#https://www.jianshu.com/p/4268191f762b
浏览器会提示证书无效,实际http报文已使用tls加密传输
简单的测试代码
#!/usr/bin/python3
import argparse
import socket
import ssl
import sys
import time
from http.server import HTTPServer, BaseHTTPRequestHandler
from http.client import HTTPSConnection
# 基于原生socket的tls客户端
def client(host, port, cafile=None):
#创建tcp socket套接字客户端
raw_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
raw_sock.connect((host, port))
print('Connected to host {!r} and port {}'.format(host, port))
# 创建ssl客户端上下文
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
#context.check_hostname = False #检查服务器主机名与服务器申请证书是主机名是否一致
#context.verify_mode = ssl.CERT_NONE #是否关闭CA校验,此设置只能在check_hostname关闭之后才能添加,关闭之后就不检查CA证书的真实性
context.check_hostname = False #此处设置不检查主机名
#if not cafile:
# print("not find cafile!")
# sys.exit()
context.load_verify_locations(cafile) # 导入CA证书,客户端使用CA证书验证服务器的证书
'''
# 如果服务器开启了验证客户端证书,则此处需要导入客户端的证书和私钥,一般WEB浏览器和服务端不启用。
context.load_cert_chain(certfile=client_certfile,keyfile=client_keyfile)
'''
# ssl上下文与原生socket绑定,生成ssl_socket,
# 作为客户端wrap_socket函数的server_side参数需要设置为False
ssl_sock = context.wrap_socket(raw_sock, server_side=False,server_hostname=host)
while True:
data = ssl_sock.recv(1024)
print(repr(data))
if not data:
break
# 基于原生socket的tls服务端
def server(host, port,certfile, keyfile,password=None,cafile=None,):
# 创建tcp socket套接字服务端
listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listener.bind((host, port))
listener.listen(1)
print('Listening at interface {!r} and port {}'.format(host, port))
raw_sock, address = listener.accept()
print('Connection from host {!r} and port {}'.format(*address))
#创建ssl服务端上下文
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
if cafile:
# 服务端是否验证客户端证书,如果需要则需要导入CA证书
context.load_verify_locations(cafile)
#服务端需要导入自己的证书和自己的私钥,用于客户端验证
context.load_cert_chain(certfile=certfile,keyfile=keyfile,password=password)
# ssl上下文与原生socket绑定,生成ssl_socket
# 作为服务wrap_socket函数的server_side参数需要设置为True
ssl_sock = context.wrap_socket(raw_sock, server_side=True)
while True:
try:
time.sleep(2)
ssl_sock.sendall('Simple test.'.encode('utf-8'))
except Exception:
break
ssl_sock.close()
'''
# https的例子
def client(host, port, cafile=None):
params = urllib.parse.urlencode({})
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.check_hostname = False
context.load_verify_locations('rootCA.pem')
conn = HTTPSConnection(host=host,
port=port,
context=context)
conn.request("GET", "/",params)
r1 = conn.getresponse()
print(r1.status)
#print(r1.read().decode('utf-8'))
# server
class requesthandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-type","text/html")
self.end_headers()
self.wfile.write("<html><body>get success</body></html>".encode('utf-8'))
def do_POST(self):
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
self.wfile.write("<html><body>post success</body></html>".encode('utf-8'))
def server(host, port, certfile, cafile=None):
httpd = HTTPServer((host, port), requesthandler)
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain('server.pem', 'server.key')
httpd.socket = context.wrap_socket(httpd.socket, server_side=True)
httpd.serve_forever()
'''
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Safe TLS client and server')
parser.add_argument('host', help='hostname or IP address')
parser.add_argument('port', type=int, help='TCP port number')
parser.add_argument('-a', dest='cafile', metavar='CAFILE',default=None,
help='authority: path to CA certificate PEM file')
parser.add_argument('-k', dest='keyfile', metavar='KEYFILE',default=None,
help='run as server : path to server keyfile file')
parser.add_argument('-s', dest='certfile',metavar='CERTFILE', default=None,
help='run as server: path to server certfile PEM file')
parser.add_argument('-p', dest='password',metavar='PASSWD', default=None,
help='run as server: cert password!')
args = parser.parse_args()
if args.certfile:
server(args.host, args.port, args.certfile, args.keyfile,args.password,args.cafile)
else:
client(args.host, args.port, args.cafile)