coding--SSL理解总结

SSL协议简介

SSL(Secure Socket Layer)netscape公司设计的主要用于web的安全传输协议。这种协议在WEB上获得了广泛的应用。SL协议分为两部分:Handshake ProtocolRecord Protocol,。其中Handshake Protocol用来协商密钥,协议的大部分内容就是通信双方如何利用它来安全的协商出一份密钥;Record Protocol则定义了传输的格式。SSL协议可以理解为工作在传输层之上、应用层之下。

SSL握手过程

SSL协议的握手过程完成身份认证、密码协商两个功能,单向认证场景下不需要验证客户端身份,交互过程如下:

1) SSL客户端(也是TCP的客户端)在TCP链接建立之后,发出一个“Client Hello”来发起握手,这个消息里面包含了自己可实现的算法列表和其它一些需要的消息;

2) SSL的服务端会回应一个“Server Hello”,这里面确定了这次通信所需要的算法;服务端发过去证书certificate(里面包含了身份和自己的公钥);

3) Client在收到这个消息后会生成一个通信密码,用SSL服务端的公钥加密后发到服务端;

4) SSL服务器端用自己的私钥解密后,会话密钥协商成功,双方可以用同一份会话密钥来通信。

SSL双向认证过程要复杂得多,分为互相验证证书、协商加密方案、协商密钥三大步骤:

1)  客户端发送一个连接请求给SSL服务端。

2)  服务端将自己的证书,以及同证书相关的信息发送给客户端。

3)   客户端验证服务端证书,验证通过则发送客户端证书到服务端,此时客户端获得服务端公钥。

4)  服务器验证客户的证书,如果没有通过验证,拒绝连接;如果通过验证,服务端获得用户的公钥。

5)  客户端生成一个通信密码方案列表,用服务端的公钥加密后传过去。

6)  服务器从客户发送过来的密码方案中,选择一种加密程度最高的密码方案,用客户的公钥加过密后通知客户端。

8)  客户端针对这个密码方案,选择一个通话密钥,接着用服务端的公钥加过密后发送给服务器。

9)  服务器接收到端送过来的消息,用自己的私钥解密,获得通话密钥。

10)  服务器、端接下来的通讯都是用对称密码方案,对称密钥是加过密的。

证书文件说明

1)  证书文件类型

.cer/.crt是用于存放证书,它是2进制形式存放的,不含私钥。

.pemcrt/cer的区别是它以AsciiBASE64)来表示。

pfx/p12用于存放个人证书/私钥,通常包含保护密码,2进制方式。

p10是证书请求。

p7rCA对证书请求的回复,只用于导入。

p7b以树状展示证书链(certificate chain),同时也支持单个证书,不含私钥。

2)        X509证书链

X509证书是最通用一种证书个数,用到三类文件:keycsrcrt

Key文件是私钥文件,openssl格式,通常使用rsa算法,密钥长度要求20481024已经被破解。

csr文件是证书请求文件,用于申请证书,可以设定读取该文件的密码。

crt文件是经过根证书CA认证过的证书文件,(windowscsr就是crt文件),csr文件使用自己的key来签署得到crt文件。

3)        证书转换:

X509P12的转换:

openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12

PKCS#12 PEM的转换:

openssl pkcs12 [-nocerts] -nodes -in cert.p12 –out cert.pem

x509pfx

pkcs12 -export –in keys/client1.crt -inkey keys/client1.key -out keys/client1.pfx

PFX格式文件中提取私钥格式文件(.key):

openssl pkcs12 -in mycert.pfx -nocerts -nodes -out mycert.key

 证书制作过程

SUSE11环境的OPENSSL工具为例制作证书,先创建一个专门生成SSL证书的目录,下面所有操作均在该目录下执行。

1)        制作根证书私钥

openssl genrsa –des3 –out ca.key 2048(有密码)

openssl genrsa -out ca.key 2048  (无密码)

2)        制作自签名根证书

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

其中要提示输入一些证书的基本信息,如红框部分。包括国家代码、省份、城市、公司名称、项目/部门名称、证书名称等信息。这些内容,最好根据项目的实际情况进行填写。当然,也可以不设置,直接回车即可。

3)        制作服务端私钥

openssl genrsa -out zte.key 2048

4)        制作服务端证书

openssl req -new -key zte.key -out srv.csr

其中的需要填写部分可以对比第二步中的方法。

5)        签发证书

执行openss命令可以制作制作简单的证书:

openssl x509 -req -in srv.csr -out srv.crt -signkey zte.key -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650

6)        制作客户端私钥证书

openssl genrsa -out client.key  2048

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

openssl x509 -req -in clt.csr -out clt.crt -signkey zte.key -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650

如果要产生一个包含私钥、证书的p12文件需要执行:

openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12

说明:

SSL单向认证时,不需要制作客户端证书,服务端需要部署根证书ca.crt、服务端私钥srv.key、服务端证书srv.crt三个文件,客户端可以部署根证书ca.crt,客户端也可以不使用根证书。SSL双向认证时,则需要制作客户端证书、私钥,客户端需要部署ca.crtclient.keyclient.key三个文件。

SSL编程总结

网上很多文章推荐SSL握手过程使用阻塞方式,握手过程结束后再设置非阻塞方式,这种方法有个非常危险的场景,向一个SSL监听端口建TCP链接后不发起SSL握手请求,这个SSL监听进程会一直阻塞住。(不知道是否有超时函数可用)下面提到的SSL操作都是非阻塞的。

SSL_connect():一次调用发送client hello,返回值-1errno=2,需要继续读;此时SSL_pending()返回长度为0,继续读、写均返回-1,但抓包确认SSL握手可以完成。

SSL_accept():一次调用立即发送服务端证书,返回值-1errno=2,需要继续读;继续SSL_read()仍然返回-1errno=2,抓包确认SSL握手可以正常完成。

根据函数说明SSL_write/SSL_read可以完成ssl握手过程,应用只需要根据select()结果执行后续读写操作,就可以完成握手过程;测试验证确认这样可以完成握手过程,但是握手过程中SSL_readSSL_write都返回-1errno=2,无法得知何时完成握手过程。

非阻塞SSL客户端握手过程编码:

(1)  SSL_connect()(返回值-1),发送client hello

(2)  select检测句柄可读,即为收到服务端发送的server hello,执行SSL_connect()(返回值-1),则发送经过公钥加密的密码;

(3)  再次select检测句柄可读,即为收到服务端发送的密码确认,执行SSL_connect()(返回值1),握手成功;

(4)  修改链路为正常establish状态,执行后续操作。

非阻塞SSL服务端握手过程编码:

(1)  tcp层完成3次握手后,select检测到句柄可读,即为收到客户端的client hello,执行SSL_accept()(返回值-1),发送server hello

(2)  select再次检测句柄可读,收到客户端发送的密文密码,执行SSL_accept()(返回值1),发送经过密码确认,握手成功结束;

(3)  修改链路为正常establish状态,执行后续操作。

  • 客户端校验服务端证书:

默认为SSL_VERIFY_NONE模式,客户端可以不加载ca.crt信任根证书;

如果初始化为SSL_VERIFY_PEER,则客户端会加载信任根证书并认证服务端发过来的证书,认证失败则会执行回调函数;

握手失败时,应用可以调用SSL_get_verify_result()接口获取失败原因。

附:SSL会话初始化时,执行SSL_CTX_set_verify()设置证书认证模式,第二个参数为SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT|SSL_VERIFY_CLIENT_ONCE,第三个参数指定校验回调函数,NULL表示使用SSL自带的校验函数。

  • 服务端校验客户端端证书:

单向验证时,服务端不要求客户端发送证书、公钥,也就不对客户端证书进行认证;

服务端初始化为SSL_VERIFY_PEER,则服务端要求双向认证,客户端必须发送证书、公钥,服务端进行认证校验;

还有一种,双向不认证,两层都配置SSL_VERIFY_NONE,客户端验证服务端证书不通过,但仍然可以继续传输数据。

小结:

证书是用来验证对端身份是否合法的,根证书又是用来验证对端证书合法性的,如果不验证对端证书,本地也就不需要根证书了。

服务端不验证客户端证书:服务端不需要CA证书、客户端不需要配置证书私钥;
服务端验证客户端证书:服务端需要CA证书、客户端需要配置证书私钥;
客户端验证服务端证书:客户端需要CA证书、服务端需要证书私钥;
客户端不验证服务端证书:客户端不需要CA证书、服务端仍然需要证书私钥。

参考:http://blog.youkuaiyun.com/dog250/article/details/5303388

 

<think>嗯,用户遇到了一个错误,说`__new__() got an unexpected keyword argument 'key_ciphers'`。这个问题出现在他们使用自定义的GMSSLAdapter的时候。我需要先理解代码的结构和可能的错误原因。 首先,用户定义了一个继承自HTTPAdapter的GMSSLAdapter类,并重写了init_poolmanager方法,设置了ciphers参数为国密套件。然后他们用这个适配器挂载到session,并尝试发送请求,但失败了。 错误信息是关于`__new__()`方法接收到了意外的关键字参数'key_ciphers'。这通常意味着在某个类的构造函数中传递了不存在的参数。我需要检查urllib3或requests的源代码,看看PoolManager或者相关的类是否接受ciphers参数。用户可能使用的是旧版本的urllib3,其中PoolManager的参数名不是ciphers,而是其他名称,比如cipher_suite或者ssl_ciphers?或者可能他们的国密套件名称不正确,导致底层库尝试传递不支持的参数? 另外,用户可能没有正确配置SSL上下文,或者使用的requests或urllib3版本不兼容自定义的适配器设置。也有可能,他们使用的国密套件在openssl中不被支持,导致在初始化时出错,但错误信息可能不太直接。 我需要查阅urllib3的文档,看看PoolManager的参数。根据urllib3的版本,例如在1.x版本中,PoolManager的初始化参数中的ssl相关的参数可能有所不同。比如,是否应该用ssl_ciphers而不是ciphers?或者是否需要用ssl_context来代替直接传递ciphers? 假设用户使用的是较新的urllib3版本,可能需要通过ssl_context来设置密码套件,而不是直接在PoolManager的参数中设置。例如,创建一个SSL上下文,配置密码套件,然后将这个上下文传递给PoolManager。 另外,用户可能没有正确安装国密SSL的支持,比如他们可能没有编译openssl时启用国密套件,或者Python的ssl模块不支持这些套件。需要检查openssl的版本和编译选项,确认是否支持ECDHE-SM2-WITH-SM4-GCM-SM3这个密码套件。 还有可能,urllib3或requests在初始化时,传递了额外的参数,导致冲突。例如,用户可能在适配器中设置的参数和requests的其他配置冲突,从而引发了意外的关键字参数错误。 现在,我应该建议用户检查urllib3的版本,并查看PoolManager的__init__方法接受的参数。如果ciphers参数在旧版本中不存在,那么用户需要更新urllib3,或者调整参数名称。或者改用ssl_context来配置密码套件。 另外,用户可能需要自定义SSL上下文,而不是直接在PoolManager里设置ciphers。例如,使用ssl.create_default_context(),然后配置密码套件,再将这个context传递给PoolManager。 总结可能的解决方案:1. 检查urllib3版本,确认ciphers参数是否存在;2. 使用ssl_context代替直接设置ciphers;3. 确保国密套件名称正确且被OpenSSL支持;4. 更新相关库到最新版本。 另外,错误信息中的'key_ciphers'可能是一个拼写错误或者参数传递错误,需要确认代码中是否有拼写错误,比如是否应该是ciphers而不是key_ciphers?不过用户代码中写的是ciphers,所以可能是库内部的问题。 可能的解决步骤:让用户尝试使用ssl_context来设置密码套件,而不是在PoolManager中直接设置ciphers参数。例如,在自定义适配器中创建SSL上下文,配置密码套件,然后将该上下文传递给PoolManager。</think>--- ### **错误分析** 你遇到的错误 `__new__() got an unexpected keyword argument 'key_ciphers'` 是由于 **`urllib3` 版本兼容性问题**或 **参数名称不匹配** 导致的。具体来说: - `urllib3` 的 `PoolManager` 类在较新版本中可能不再直接通过 `ciphers` 参数配置密码套件。 - 某些环境下(如旧版本 `urllib3` 或 `requests`),参数传递方式需通过 `ssl_context` 实现。 --- ### **分步解决方案** #### **1. 检查库版本兼容性** 首先确保以下库为较新版本: ```bash pip install --upgrade urllib3 requests ``` #### **2. 改用 `ssl_context` 配置国密套件** 直接通过 `ssl` 模块创建上下文,避免依赖 `PoolManager` 的参数兼容性: ```python # -*- coding: utf-8 -*- import requests import ssl from urllib3.poolmanager import PoolManager from urllib3.util.ssl_ import create_urllib3_context from requests.adapters import HTTPAdapter # 自定义SSL上下文:配置国密密码套件 def create_gmssl_context(): context = create_urllib3_context() # 使用urllib3的默认上下文 context.set_ciphers("ECDHE-SM2-WITH-SM4-GCM-SM3") # 设置国密套件 return context # 自定义适配器 class GMSSLAdapter(HTTPAdapter): def init_poolmanager(self, *args, **kwargs): kwargs["ssl_context"] = create_gmssl_context() # 注入自定义SSL上下文 return super().init_poolmanager(*args, **kwargs) # 配置会话 session = requests.Session() session.mount("https://", GMSSLAdapter()) # 发送请求 try: response = session.get( "https://your-gmssl-server.com", verify="/path/to/sm2_ca.crt" # SM2证书路径 ) print("状态码:", response.status_code) print("响应内容:", response.text) except Exception as e: print("请求失败:", e) ``` --- ### **关键修改说明** 1. **使用 `ssl_context` 替代直接参数** - 通过 `create_urllib3_context()` 创建基础SSL上下文,避免直接操作 `PoolManager` 的敏感参数。 - 使用 `set_ciphers()` 方法配置国密套件,兼容性更好。 2. **规避版本冲突** - 新版本的 `urllib3` 更推荐通过上下文 (`ssl_context`) 管理SSL配置,而非直接传递参数。 --- ### **验证国密套件支持** 1. **确认OpenSSL支持国密算法** 在终端运行以下命令,检查是否包含 `SM2`、`SM3`、`SM4` 算法: ```bash openssl ciphers -v | grep SM ``` 2. **检查服务端配置** 确保目标服务器的TLS服务已启用国密套件(如基于`GMSSL`或`Tongsuo`的国密服务)。 --- ### **扩展:常见问题排查** 1. **证书路径错误** - 若 `verify` 参数指定的证书路径无效,会引发 `SSLError`。建议使用绝对路径。 2. **国密套件名称不匹配** - 不同国密实现可能对套件名称有差异。例如: - `ECDHE-SM2-WITH-SM4-SM3` - `TLS_SM4_GCM_SM3` - 需根据实际服务端支持的套件调整名称。 --- ### **总结** 通过升级库版本、改用 `ssl_context` 注入国密配置,可解决 `key_ciphers` 参数错误。若问题仍未解决,请提供以下信息进一步排查: 1. `urllib3` 和 `requests` 的版本 (`pip show urllib3 requests`)。 2. 服务端国密TLS的具体实现(如`GMSSL`、`Tongsuo`)。 3. OpenSSL的版本 (`openssl version`)。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值