Python requests 库请求 HTTPS 的问题

无脑的解决方案

经测试,Python 3.9 是最合适的一个版本了,直接静态编译一个 Python,搞定 TLS 的难题

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
mkdir -p /local/root/opt/{lib,source}
export LIBDIR=/local/root/opt/lib
export WORKDIR=/local/root/opt/source
cd $WORKDIR && \
wget --no-check-certificate https://www.openssl.org/source/openssl-1.1.0l.tar.gz && \
tar -xzvf openssl-1.1.0l.tar.gz && \
cd openssl-1.1.0l && \
./config shared \
         --openssldir=$LIBDIR/openssl \
         --prefix=$LIBDIR/openssl \
         -DOPENSSL_TLS_SECURITY_LEVEL=0 && \
make -j16 && \
make install

# complie python 3.9.0
cd $WORKDIR && \
wget --no-check-certificate http://npm.taobao.org/mirrors/python/3.9.0/Python-3.9.0.tar.xz && \
tar -Jxvf Python-3.9.0.tar.xz && \
cd Python-3.9.0 && \
sed -i "s|OPENSSL_LIBS=\"-lssl -lcrypto\"|OPENSSL_LIBS=\"\$ssldir/lib/libssl.a \$ssldir/lib/libcrypto.a\"|g" ./configure && \
sed -i "s|libraries=openssl_libs|extra_objects=openssl_libs|g" ./setup.py && \
sed -i "s|split_var('OPENSSL_LIBS', '-l')|split_var('OPENSSL_LIBS', '')|g" ./setup.py && \
./configure --prefix=$(pyenv root)/versions/openssl \
            --with-openssl=$LIBDIR/openssl && \
make -j16 && \
make install

繁琐的解决方案

UNSAFE_LEGACY_RENEGOTIATION_DISABLED

新建一个 openssl.cnf 文件,内容如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
openssl_conf = openssl_init

[openssl_init]
ssl_conf = ssl_sect

[ssl_sect]
system_default = system_default_sect

[system_default_sect]
Options = UnsafeLegacyRenegotiation

之后更换 OPENSSL_CONF 环境变量运行 Python 脚本

1
OPENSSL_CONF=openssl.cnf python3 demo.py

CERTIFICATE_VERIFY_FAILED

将 Python 脚本中所有的 requests 库的请求函数加上 verify=False 参数

1
print(requests.get(url=url, verify=False).content)

UNSUPPORTED_PROTOCOL

本机的 openssl 编译时未曾添加所需 TLS 支持

若是碰到低版本的 TLS 建议用 Ubuntu 16.04 这些老版本的系统进行命令操作

不然很可能得自己编译一个低版本的 openssl 来添加系统对低版本 TLS 的支持

首先要查看本地的 openssl 库对该网站的协议是否支持,命令及结果如下所示

1
2
3
4
5
# openssl s_client -help 2>&1 | awk '/-ssl[0-9]|-tls[0-9]/{print $1}'
-tls1
-tls1_1
-tls1_2
-tls1_3

接着要查看本地的 openssl 是否支持对应的加密方式

1
2
3
4
5
# openssl ciphers -s -v
TLS_AES_256_GCM_SHA384         TLSv1.3 Kx=any      Au=any   Enc=AESGCM(256)            Mac=AEAD
TLS_CHACHA20_POLY1305_SHA256   TLSv1.3 Kx=any      Au=any   Enc=CHACHA20/POLY1305(256) Mac=AEAD
TLS_AES_128_GCM_SHA256         TLSv1.3 Kx=any      Au=any   Enc=AESGCM(128)            Mac=AEAD
...

但是如上两行命令也可能不能帮你获晓 openssl 对所需 TLS 提供支持的具体情况

可以选择用浏览器查看网站,再点击证书,或用 curl 命令来访问网址

这两种方式是都可以看到网站所用 TLS 版本信息及加密方式的

本机的 openssl 支持该 TLS 版本

可用 curl 先证明一下能否支持,如下是能够支持 TLS 的样例

1
2
3
4
5
6
7
8
# curl --insecure -vi https://example.com
* Rebuilt URL to: https://example.com/
*   Trying xxx.xxx.xxx.xxx...
* Connected to example.com (xxx.xxx.xxx.xxx) port 443 (#0)
* found 129 certificates in /etc/ssl/certs/ca-certificates.crt
* found 516 certificates in /etc/ssl/certs
* ALPN, offering http/1.1
* SSL connection using TLS1.0 / RSA_AES_256_CBC_SHA1

如下是不能支持该 TLS 的样例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# curl --insecure -vi https://example.com
* Rebuilt URL to: https://example.com/
*   Trying xxx.xxx.xxx.xxx...
* Connected to example.com (xxx.xxx.xxx.xxx) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.0 (IN), TLS header, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (OUT), TLS header, Unknown (21):
* TLSv1.3 (OUT), TLS alert, protocol version (582):
* error:0A000102:SSL routines::unsupported protocol
* Closing connection 0
curl: (35) error:0A000102:SSL routines::unsupported protocol

新建一个 openssl.cnf 文件,内容如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
openssl_conf = openssl_init

[openssl_init]
ssl_conf = ssl_sect

[ssl_sect]
system_default = system_default_sect

[system_default_sect]
MinProtocol = TLSv1.0
CipherString = DEFAULT@SECLEVEL=1

之后更换 OPENSSL_CONF 环境变量运行 Python 脚本

1
OPENSSL_CONF=openssl.cnf python3 demo.py