aiohttp 指定 tls 版本 (ssl 错误)

某些网站对 ssl 1.0 以上的版本支持并不友好,但是 python ssl lib 默认会指定当前OpenSSL支持的最高版本,并且当服务器返回的ssl版本号低于或者等于1.0时, 会出现如下错误

aiohttp.client_exceptions.ClientConnectorError: Cannot connect to host www.mdnkids.com:443 ssl: [None]

看一下错误原因的推断过程,我的操作系统是 Mac OS, 首先用 curl 命令进行尝试

  curl 'https://www.mdnkids.com'
  curl: (35) LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to www.mdnkids.com:443

 发现 curl 也一样有 ssl error, 那么尝试直接用 openssl 对该域名进行连接

openssl s_client  -connect www.mdnkids.com:443 -msg
# if fail, try openssl s_client -CApath /usr/local/etc/openssl/certs  -connect www.mdnkids.com:443 -msg

==> TLS 1.2 Handshake [length 0139], ClientHello
==> ...
==>  TLS 1.0 Handshake [length 004a], ServerHello
==>  TLS 1.0 Handshake [length 0c0a], Certificate
==> ...
==> TLS 1.0 Handshake [length 0010], Finished

发现,ClientHello 指定的是 1.2 的版本,但是 ServerHello 返回的却是 1.0, 表示当前服务器只支持 1.0, 并不识别客户端的 1.2 的版本,之后客户端用 1.0 版本的协议完成了 session key 的加密,传输,并完成了握手

那么,既然 openssl 能成功,为什么 curl 和 aiohttp 又失败呢,查阅 curl 资料 发现

curl is designed to use a "safe version" of SSL/TLS by default. It means that it will not negotiate SSLv2 or SSLv3 unless specifically told to, and in fact several TLS libraries no longer provide support for those protocols so in many cases curl is not even able to speak those protocol versions unless you make a serious effort.

上面说,curl 默认会使用安全的 ssl/tls 版本,比如 SSLv2/SSLv3 就会拒绝访问,只有你指定了这个版本的时候才会尝试使用这些不安全的版本

好了,那我们指定 tls1.0 试试看

curl --tlsv1.0 'https://www.mdnkids.com/'

==> html .....

果然,看来 tlsv1.0 也被 curl 认为是不安全的版本默认禁用了,那我们来看看 aiohttp 是不是也是这个原因

import ssl
import aiohttp
import asyncio


async def test():
    FORCED_CIPHERS = (
        'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:'
        'DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+HIGH:RSA+3DES'
    )
    sslcontext = ssl.create_default_context()
    sslcontext.options |= ssl.OP_NO_SSLv3
    sslcontext.options |= ssl.OP_NO_SSLv2
    sslcontext.options |= ssl.OP_NO_TLSv1_1
    sslcontext.options |= ssl.OP_NO_TLSv1_2
    sslcontext.options |= ssl.OP_NO_TLSv1_3
    sslcontext.set_ciphers(FORCED_CIPHERS)

    async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(limit=50, loop=loop)) as session:
        r = await session.get('https://www.mdnkids.com/news/?Serial_NO=108552', ssl=sslcontext)
        print(await r.text())

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(test())

好了,这一次能成功的获取到报文了

参考资料:

关于 ssl/tls 的通信过程参考资料

 

 

你可能感兴趣的:(network,python)