QSslSocket 之 TLS initialization failed 问题
背景介绍:
Qt使用openssl进行客户端和服务端https通信,当部署程序到新主机时,出现上述QSslSocket的TLS初始化失败问题。该问题的根源在于发布程序中并没有包含openssl动态库(或者包含错误的版本),而且新机器上也没有安装openssl动态库。
以下形式均是同一问题:
qt.network.ssl: QSslSocket::connectToHostEncrypted: TLS initialization failed
QSslSocket::connectToHostEncrypted: TLS initialization failed
QSslSocket::startClientEncryption: TLS initialization failed
QSslSocket::startServerEncryption: TLS initialization failed
我们知道,openssl的动态库只有两个。目前最新的长期支持版1.1.1,64位是这两个,libssl-1_1-x64.dll和libcrypto-1_1-x64.dll。
libssl库实现了所有的TLS协议版本,包括最新的TLSv1.3。
libcrypto库包括一套特别完备的通用的密码学(cryptography)库。这个库是TLS的基础,但是又完全可以单独作为一个加密解密的库来使用。
注意,以前的版本使用的是动态库libeay32.bll和ssleay32.bll,但是官方强烈推荐不要再使用旧版,应该尽快使用新版1.1.1的库。
问题定位:
由报错信息定位到Qt源码文件qsslsocket.cpp。
void QSslSocket::connectToHostEncrypted();
同理,在 void QSslSocket::startClientEncryption(); void QSslSocket::startServerEncryption();
中均有同样的信息。
这个supportSsl()
函数正是检测当前系统环境是否支持openssl,也就是是否存在上述两个动态库。
void QSslSocket::connectToHostEncrypted();
用来客户端发起一个SSL握手的加密连接,包含调用startClientEncryption()。
void QSslSocket::startServerEncryption();
用来服务端发起一个SSL握手的加密连接。
void QSslSocket::startClientEncryption();
一般不直接使用,而使用connectToHostEncrypted()替代。
由上可知,服务端程序和客户端程序在检测到当前系统环境不支持openssl时,都可能出现上述初始化失败的错误。
问题解决:
-
发布使用了Openssl的程序时,记得要把编译时使用的Openssl库的动态库拷贝到程序运行目录中,libssl和libcrypto。
注意这种方式,要特别注意版本问题。因为主机上可能有很多程序都包含openssl动态库,也可能安装了多个版本的openssl库。当部署时如果稀里糊涂地拷贝了一个版本的库,而没有使用编译时链接的库,就会导致不同版本间有时会不兼容的问题。
比如,这次的情况是: 通过QSslSocket::sslLibraryBuildVersionString()得到程序构建时的openssl库版本是1.1.1g,而发布程序时在程序主目录里导入了安装的openssl库libssl-1_1-x64.dll和libcrypto-1_1-x64.dll,版本为1.1.1j,但是程序运行时就是检测不到openssl,也即QSslSocket::supportsSsl()为false。后来,下载并更换了最新的openssl库,版本为1.1.1k,程序成功检测到了openssl,并能正常使用。由上可得结论,似乎是1.1.1g不兼容1.1.1j,但是兼容1.1.1k。具体原因有待将来深入认识再下结论。查看openssl动态库的版本,只需查看相应文件属性即可,如下:
-
在部署机器上,预安装兼容程序版本的Openssl库。
推荐一个网站:
window平台预编译的openssl库的网站:
http://slproweb.com/products/Win32OpenSSL.html
如果仅仅是安装个openssl库环境而不做二次开发,选择Light版即可。另外,注意安装过程中,记得选择“拷贝库文件到系统目录中”,系统目录已经在环境变量中,这样该目录下的动态库可以被其他程序运行时加载。
经验积累:
代码中可以增加系统对openssl的支持情况的检测,使用 QSslSocket::supportsSsl()
,即可检测程序运行时平台是否具备openssl的环境,如检测不到,直接抛出更直接明了,可读性性更强的报错即可,而不使用默认的TLS initialization failed提示。
另外,可以增加qDebug() << QSslSocket::sslLibraryBuildVersionString();
日志,和 qDebug() << QSslSocket::sslLibraryVersionString()
日志,一个输出程序编译时指定的openssl库的版本,一个输出程序运行时检测到的openssl库的版本,这样有助于帮助分析一些奇怪问题。