在python使用SSL(HTTPS)

在python上使用SSL有许多场景,我主要关注的是使用python访问HTTPS资源,以及使用python提供HTTPS服务。(HTTPS是SSL在WEB上的应用之一)

一、使用python访问HTTPS网站

这应该算是最简单也是最常见的场景了。我们使用python做为客户端去访问公网上的网站,而这个网站为了传输安全(避免被劫持或者窃听)使用了HTTPS服务,传输过程内容都经过了SSL加密。下面来看下具体的python代码,这里使用的是python2.7.11,用的是python自带的urllib2。当然你也可以使用requests或者pycurl等第三方库,都可以,原理都是一样的,只是实现的手段有些差异而已。

import urllib2

import ssl

if __name__ == ‘__main__‘:

myurl="https://www.baidu.com"

req = urllib2.Request(myurl)

try:

response = urllib2.urlopen(req)

print "HTTP return code:%d" % response.getcode()

strResult= response.read()

print strResult

except Exception ,ex:

print "Found Error :%s" % str(ex)

运行它,我们就可以得到这个网站上的内容了。并且传输过程都经过了加密。是不是很简单。整个的传输过程大概是这样的,客户端请求SSL连接握手(其中会跟服务器有些SSL协议之间的交互,这个我们不用详细去研究)服务器把自己的证书传给客户端,客户端对这个证书进行认证(每台客户端电脑上都会默认安装一些权威CA(证书签发机构)的证书,这个认证就是使用这些证书进行的,python的ssl模块会自动加载这些权威CA证书,当然你也可以自己指定,这个后面会谈到),确保这个证书是由可信的CA颁布的(客户端对服务端证书进行认证这个操作,是从python2.7.9开始才有的,以前版本的是不进行认证的),之后就是利用证书交换密钥后,使用密钥对传输内容进行加密传输。

互联网上大部分的正规网站都是会有自己的证书的,这些证书都是经过权威CA认证的,可以被认为是可信的(其实这个也不一定,前段时间就有过谷歌封杀赛门铁克CA签发的证书的事件,有兴趣的可以去网上搜索看看)。但是同样也有不少网站,由于各种各样的原因没有这些经过权威CA认证的证书,毕竟申请这些证书流程比较麻烦,并且要支付一定的费用。这些网站也可以提供安全的HTTPS服务,但由于他们的证书是自签名的或者是由非权威的CA颁发的(这两种证书后面会谈到),所以会被认为是不可信的。当使用浏览器打开这些网站时,浏览器会提醒你这是不安全的连接,当然这个不安全是指网站未经权威认证,所以网站本身可能不安全,但在网络传输层面上来看,传输是安全的。那我们访问这些网站时,使用上面的代码会有错误提示,下面代码就是访问一个这是我自己建立的一个HTTPS网站https://127.0.0.1:8443,该网站使用的是自签名的证书。如果你使用谷歌浏览器访问会提示你:您的连接不是私密连接,如果你强制连接则会提示不安全

import urllib2

import ssl

if __name__ == ‘__main__‘:

myurl="https://127.0.0.1:8443"

req = urllib2.Request(myurl)

try:

response = urllib2.urlopen(req)

print "HTTP return code:%d" % response.getcode()

strResult= response.read()

print strResult

except Exception ,ex:

print "Found Error :%s" % str(ex)

运行这个就会出现错误:

提示证书校验错误。这时如果我们想要访问这样的网站就要把客户端的证书校验关闭。在前面加上一句:ssl._create_default_https_context = ssl._create_unverified_context即可

import urllib2

import ssl

ssl._create_default_https_context = ssl._create_unverified_context

if __name__ == ‘__main__‘:

myurl="https://127.0.0.1:8443"

req = urllib2.Request(myurl)

try:

response = urllib2.urlopen(req)

print "HTTP return code:%d" % response.getcode()

strResult= response.read()

print strResult

except Exception ,ex:

print "Found Error :%s" % str(ex)

当然也可以自己创建一个不校验的SSL上下文,然后引用这个上下文来打开url

ctx = ssl._create_unverified_context()

然后

response = urllib2.urlopen(req,context=ctx)

 

二、使用OPENSSL生成证书

1、生成自签名的证书

在使用python提供HTTPS服务之前,我们需要先生成证书,这里请参考https://www.cnblogs.com/wucao/archive/2017/02/27/6474711.html 这篇文章讲解了如何使用openssl生成证书。在WINDOWS平台记得要设置环境变量,参考我的一篇博文http://blog.sina.com.cn/s/blog_5d18f85f0102xdm7.html。

使用命令:openssl req -x509 -newkey rsa:2048 -nodes -days 365 -keyout private.pem -out cert.crt

之后会要求我们输入一些组织信息,你可以根据你的实际情况填写。之后就生成了自签名的证书cert.crt,私钥是private.pem

2、自己建立一个CA(证书签发机构),然后用自己的CA来颁发证书。这个我们后面讨论双向认证的时候会用到。详细内容请参考http://blog.csdn.net/gx_1983/article/details/47866537

这里我只是简单的写一下我自己的步骤

1)      保证openssl的bin目录在path环境变量里面。创建一个工作目录,这里我使用ca这个目录。然后在ca下面再创建目录demoCA。之后在demoCA下创建空白文本文件index.txt和serial,并且打开serial写入字符01

2)      先造成CA的KEY和证书

openssl req -new -x509 -days 36500 -key ca.key -out ca.crt

执行后会出现提示,主要是要求输入一些组织方面的信息,请按要求填写即可

执行成功后会造成ca.key和ca.crt 两个文件,ca.key为私钥需要妥善保管,不要轻易给别人。ca.crt为证书可以随意传播。

3)      生成服务器的私钥和证书,其中证书使用了CA的私钥进行签名:

l  生成server私钥

openssl genrsa -out server.key 2048

l  使用server私钥生成server端证书请求文件

openssl req -new -key server.key -out server.csr

一样需要回答一些组织方面的问题

l  使用server证书请求文件通过CA生成由CA签名的证书

openssl ca -in server.csr -out server.crt -cert ca.crt -keyfile ca.key

l  验证server证书

openssl verify -CAfile ca.crt server.crt

这样就得到两个文件:一个是私钥server.key;一个是证书server.crt

4)      用跟3)同样的方法生成客户端的client.key和client.crt这两个文件在后面的双向认证里面会用到

三、使用python提供HTTPS服务

我们有了证书和私钥了,下面就可以正式使用python建立一个HTTPS网站了。这里我使用框架实现,用的是twisted。使用的证书是之前用OPENSSL生成的自签名证书。

#-* -coding: utf-8 -* -

from twisted.web import server, resource

from twisted.internet import reactor,ssl

class MainResource(resource.Resource):

isLeaf = True

# 用于处理GET类型请求

def render_GET(self, request):

# name参数

name = ‘World‘

if request.args.has_key(‘name‘):

name = request.args[‘name‘][0]

# 设置响应编码

request.responseHeaders.addRawHeader("Content-Type", "text/html; charset=utf-8")

# 响应的内容直接返回

return "

Hello, " + name + ""

if __name__ == ‘__main__‘:

sslContext = ssl.DefaultOpenSSLContextFactory(

‘C:/ca/private.pem, # 私钥

‘C:/ca/cert.crt‘ # 证书

)

site = server.Site(MainResource())

reactor.listenSSL(8080, site, sslContext)

print "监听端口:8080"

reactor.run()

使用之前的客户端访问,记得关闭证书校验。这时可以看到可以顺利访问。如果使用浏览器访问,也可以正常访问,但此时会出现安全提示,忽略这个安全提示后也可以正常访问。

四、SSL双向校验

上面的示例都是客户端对服务器端证书的校验。这也是最常见的单向校验。这里面由客户端来校验服务器端的证书(当然也可以选择不校验)。通常的互联网服务都是这种方式。在这种方式下,客户端不需要有证书。服务器端也不会校验客户端的证书。此外还有一种双向校验模式。在这种模式下,客户端也要有证书,并且在协商过程中提供给服务器端。服务器也会对客户端的证书进行校验。这种模式一般应用于对安全要求比较高的环境,服务器和用户端都需要证书,并且双方都要能被权威CA验证通过。看看下面的服务器端代码:

#-* -coding: utf-8 -* -

from twisted.web import server, resource

from twisted.internet import reactor,ssl

from OpenSSL import SSL

def verifyCallback(connection, x509, errnum, errdepth, ok):

if not ok:

print ‘invalid cert from subject:‘, x509.get_subject()

return False

else:

print "Certs are fine", x509.get_subject()

return True

class MainResource(resource.Resource):

isLeaf = True

# 用于处理GET类型请求

def render_GET(self, request):

# name参数

name = ‘World‘

if request.args.has_key(‘name‘):

name = request.args[‘name‘][0]

# 设置响应编码

request.responseHeaders.addRawHeader("Content-Type", "text/html; charset=utf-8")

# 响应的内容直接返回

return "

Hello, " + name + ""

if __name__ == ‘__main__‘:

sslContext = ssl.DefaultOpenSSLContextFactory(

‘C:/ca/private.pem, # 私钥

‘C:/ca/cert.crt‘ # 证书

)

ctx = sslContext.getContext()

#这里改为了双向校验模式

ctx.set_verify(

SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT,

verifyCallback

)

site = server.Site(MainResource())

reactor.listenSSL(8080, site, sslContext)

print "监听端口:8080"

reactor.run()

然后再用客户端来访问一下。这时会发现有错误出现,错误提示为:

提示sslv3协议握手失败。原因就是服务器端要求双向校验,但客户端没有提供自己的证书给服务器,所以握手协商失败。

好的,我们再改一下客户端代码,适配这种双向校验模式。这里客户端使用使用的证书是,是之前使用OPENSSL生成的客户端证书,并且使用了我们自己建立的CA进行签名。

import urllib2

import ssl

KEY_FILE="C:/ca/client.key"

CERT_FILE="C:/ca/client.crt"

context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)

context.check_hostname = False

context.load_cert_chain(certfile=CERT_FILE,keyfile=KEY_FILE)

context.verify_mode=ssl.CERT_REQUIRED

if __name__ == ‘__main__‘:

myurl="https://127.0.0.1:8080/?name=qh"

req = urllib2.Request(myurl)

try:

response = urllib2.urlopen(req,context=context)

#response = urllib2.urlopen(req)

print "HTTP return code:%d" % response.getcode()

strResult= response.read()

print strResult

except Exception ,ex:

print "Found Error in auth phase:%s" % str(ex)

这里出现错误提示

证书校验失败。这是因为服务器提供的证书没有通过客户端的校验。

这是因为服务器端的证书是使用自签名的证书,当然通不过客户端校验了。这里我们要把服务器端的证书改成我们使用CA签名的证书。修改一下服务器端代码:

sslContext = ssl.DefaultOpenSSLContextFactory(

‘C:/ca/server.key‘, # 私钥

‘C:/ca/server.crt‘ # 证书

)

改过之后再试,可以还出现同样的错误,这是怎么回事呢?原来服务器端的证书是使用我们自建的CA进行签名的,不是权威CA,所以校验失败了。这怎么办呢,我们可以指定CA的证书,把我们的CA证书设置为可信。修改一下客户端,加上一句:

CA_FILE="c:/ca/ca.crt"

context.load_verify_locations(CA_FILE)

注意ca.crt是之前我们使用OPENSSL建立的CA证书(请参考前面第二部分的内容)。修改之后重新运行,发现还是出现错误:

这个错误提示变了,提示CA不知名。同时在服务器端的回调函数也有错误输出:invalid cert from subject:

这说明服务器端校验客户端证书失败,原因当然是服务器端我们没有把我们自己的CA设置为可信。所以同样的在服务器端也要修改一下,加上以下两句:

cafile="c:/ca/ca.crt"

ctx.load_verify_locations( cafile)

重新运行,这下成功了

下面把完整的代码发一下:

服务器端:

#-* -coding: utf-8 -* -

‘‘‘

Created on 2018-1-16

@author: qh

‘‘‘

from twisted.internet import iocpreactor as iocpreactor

try:

iocpreactor.install()

except Exception, e:

print "iocp install failed:%s" % str(e)

from twisted.web import server, resource

from twisted.internet import reactor,ssl

from OpenSSL import SSL

cafile="c:/ca/ca.crt"

class MainResource(resource.Resource):

isLeaf = True

# 用于处理GET类型请求

def render_GET(self, request):

# name参数

name = ‘World‘

if request.args.has_key(‘name‘):

name = request.args[‘name‘][0]

# 设置响应编码

request.responseHeaders.addRawHeader("Content-Type", "text/html; charset=utf-8")

# 响应的内容直接返回

return "

Hello, " + name + ""

if __name__ == ‘__main__‘:

sslContext = ssl.DefaultOpenSSLContextFactory(

‘C:/ca/server.key‘, # 私钥

‘C:/ca/server.crt‘ # 证书

)

ctx = sslContext.getContext()

ctx.set_verify(

SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT,

verifyCallback

)

ctx.load_verify_locations( cafile)

site = server.Site(MainResource())

reactor.listenSSL(8080, site, sslContext)

print "监听端口:8080"

reactor.run()

客户端:

‘‘‘

Created on 2018-3-2

@author: qh

‘‘‘

import urllib2

import ssl

KEY_FILE="C:/ca/client.key"

CERT_FILE="C:/ca/client.crt"

CA_FILE="c:/ca/ca.crt"

context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)

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

if __name__ == ‘__main__‘:

req = urllib2.Request(myurl)

try:

response = urllib2.urlopen(req,context=context)

#response = urllib2.urlopen(req)

print "HTTP return code:%d" % response.getcode()

strResult= response.read()

print strResult

except Exception ,ex:

print "Found Error in auth phase:%s" % str(ex)

五、其它

1、context.load_cert_chain(certfile=CERT_FILE,keyfile=KEY_FILE)

这个函数可以只提供一个certfile这一个文件,这时要求CERT_FILE里面包括私钥。其实证书文件和私钥文件都是简单的文本文件,你可以把私钥文件的内容复制到证书文件后面,这样你就可以在这个函数里面只提供一个文件了。

2、如何在request对象里面读出客户端信息呢?

所有的客户端信息都可以request对象里面读出,包括客户端的证书信息,看看下面,你所需要的基本都有了

def render_GET(self, request):

print request.content.read()

print request.getAllHeaders()

print request.getClientIP()

print request.getHost()

print request.transport.getPeer()

cl=request.transport.getPeerCertificate()

certificate = ssl.Certificate(cl)

print certificate.getIssuer()

print certificate.getSubject()

3、使用POST方法发送JSON数据

def urllib2_open(myurl):

headers = {‘Content-Type‘: ‘application/json‘}

data={‘test‘:‘hello‘}

req = urllib2.Request(myurl,headers=headers, data=json.dumps(data))

try:

response = urllib2.urlopen(req,context=context)

print "HTTP return code:%d" % response.getcode()

strResult= response.read()

print strResult

except Exception ,ex:

print "Found Error in auth phase:%s" % str(ex)

4、有一个地方我研究的不是特别清楚:默认情况下python是到哪里找权威CA证书的,是在某个文件夹还在通过注册表,还是直接通过操作系统提供的API。

写这么多总算把该写的写完了。之前我只是想用python访问一个HTTPS站点的。但当时很不顺利,不管怎么搞都出现SSLV3_ALERT_HANDSHAKE_FAILURE,当时在网上找了很多类似的解决办法,什么启用SSLV3啊、把加密方式改为ALL啊,另外也用了requests和pycurl等等第三方组件。但所有尝试无一成功。花了我好几天时间。最后终于发现原来这个站点需要双向认证。从这里面感觉自己对SSL了解太少了,走了太多弯路。于是就对SSL好好研究了一下,这才有了上面的内容。这些内容还是比较肤浅,都只限于应用方面,但对我来说足够了。分享给大家,让大家少走我走过的弯路。

原文地址:https://www.cnblogs.com/jiangzhaowei/p/9123467.html

时间: 2024-10-13 14:52:35

在python使用SSL(HTTPS)的相关文章

TLS,SSL,HTTPS with Python(转)

需要了解的背景知识: 术语 HTTPS,SSL,TLS 长连接与短连接的关系 了解 CA 证书 基本流程 一.术语扫盲 1.什么是SSL? SSL(Secure Sockets Layer, 安全套接字),因为原先互联网上使用的 HTTP 协议是明文的,存在很多缺点——比如传输内容会被偷窥(嗅探)和篡改.发明 SSL 协议,就是为了解决这些问题. 2.那么什么是TLS呢? 到了1999年,SSL 因为应用广泛,已经成为互联网上的事实标准.IETF 就在那年把 SSL 标准化.标准化之后的名称改为

使用python检查SSL证书到期情况

使用python检查SSL证书到期情况 结合邮件告警和页面展示,再多的域名证书到期情况即可立马知道 代码示例: # coding: utf-8 # 查询域名证书到期情况 import re import time import subprocess from datetime import datetime from io import StringIO def main(domain): f = StringIO() comm = f"curl -Ivs https://{domain} --

Java调用使用SSL/HTTPS协议来传输的axis webservice服务

使用SSL/HTTPS协议来传输 Web服务也可以使用SSL作为传输协议.虽然JAX-RPC并没有强制规定是否使用SSL协议,但在tomcat 下使用HTTPS协议. 1.使用JDK自带的工具创建密匙库和信任库. 1)通过使用以下的命令来创建服务器端的密匙库: keytool -genkey -alias Server -keystore server.keystore -keyalg RSA 输入keystore密码: changeit 您的名字与姓氏是什么? [Unknown]: Serve

嵩天老师的零基础Python笔记:https://www.bilibili.com/video/av15123607/?from=search&seid=10211084839195730432#page=25 中的38-41讲

# -*- coding: utf-8 -*-#嵩天老师的零基础Python笔记:https://www.bilibili.com/video/av15123607/?from=search&seid=10211084839195730432#page=25 中的38-41讲# 文件# 文件是存储在外部介质上的数据或信息的集合# 文件是有序的数据序列# 常用的编码# ASCII码是标准化字符集# 7个二进制位编码# 表示128个字符 # ord() 将字符转化为ASCII码, chr() 将AS

嵩天老师的零基础Python笔记:https://www.bilibili.com/video/av13570243/?from=search&seid=15873837810484552531 中的15-22讲

#coding=gbk#嵩天老师的零基础Python笔记:https://www.bilibili.com/video/av13570243/?from=search&seid=15873837810484552531 中的15-22讲#数字类型的关系#三种类型存在一种逐渐扩展的关系:#整数 ->浮点数 ->复数(整数是浮点数的特殊,浮点数是复数的特殊)#不同数字类型之间可以进行混合运算,运算后生成的结果为最宽类型.如整数+浮点数=浮点数#三种类型可以相互转换#函数:int(), fl

SSL HTTPS 生成证书

SSL HTTPS 一.生成服务器私钥.公钥 $ openssl genrsa -out server.key 2048 $ openssl rsa -in server.key -pubout -out server.pem 二.生成CA证书,步骤是 (1)生成私钥 (2)生成X.509 Certificate Signing Request(CSR), 此步骤需要输入国家.省份.城市. 组织公司名(注意不要CA的名称不要与后面签发给服务器和客户端名字相同). 部门(可空). Common N

nginx普通配置/负载均衡配置/ssl/https配置

1.nginx普通配置 server { listen 80; server_name jqlin.lynch.com; access_log /var/log/nginx/main.log main; error_log /var/log/nginx/pay_local.error; #log_format access '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent $re

关于windows下 使用Anaconda3安装的python无法使用ssl模块问题.关联pip无法下载https

关联错误: pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available. Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError("Can't connect to HTTP

python requests SSL证书问题

错误信息如下: requests.exceptions.SSLError: ("bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)",) python做爬虫,对于有的网站,需要验证证书,比如:12306,https://inv-veri.chinatax.gov.cn/等网站 那么我参考这个作者的简书:http://www.jia