公司项目用户注册部分需要用到邮箱进行帐号激活,在开通企业邮箱之前用的是新浪的免费邮箱,不使用SSL,在Windows和Linux下一直都能正常使用。最近将服务切换到腾讯企业邮箱,并使用SSL。切换后在Windows(开发环境)测试通过。在生产环境(Linux)中测试时发现无法发送邮件,日志中记录如下异常:
javax.mail.MessagingException: Could not connect to SMTP host: smtp.exmail.qq.com, port: 465;
nested exception is:
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at com.sun.mail.smtp.SMTPTransport.openServer(SMTPTransport.java:2102)
at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:699)
at javax.mail.Service.connect(Service.java:388)
at javax.mail.Service.connect(Service.java:246)
at javax.mail.Service.connect(Service.java:195)
at javax.mail.Transport.send0(Transport.java:254)
at javax.mail.Transport.send(Transport.java:124)
at com.genepoint.tool.EmailUtil$2.run(EmailUtil.java:78)
Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.Alerts.getSSLException(Alerts.java:154)
at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:2023)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1125)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1375)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1403)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1387)
at com.sun.mail.util.SocketFetcher.configureSSLSocket(SocketFetcher.java:598)
at com.sun.mail.util.SocketFetcher.createSocket(SocketFetcher.java:372)
at com.sun.mail.util.SocketFetcher.getSocket(SocketFetcher.java:238)
at com.sun.mail.smtp.SMTPTransport.openServer(SMTPTransport.java:2066)
... 7 more
看到异常后根据关键词“handshake_failure”进行google,并查看了stackoverflow中的高票结果如下:
然后一个个分析,开始没注意JDK版本的差异,死命地想第三点(证书问题),但都无从解决,后来又想开发环境也并没有用到任何证书,应该与证书无关。接着修改代码,在main方法中开启调试:
System.setProperty("javax.net.debug", "all");
开始一点点分析问题所在。发现在客户端发送了“ClientHello”之后,并没有打印出服务器返回的“ServerHello”消息,得到的数据包长度特别小:
%% No cached client session
*** ClientHello, TLSv1.2
RandomCookie: GMT: 1459055923 bytes = { 108, 96, 162, 161, 238, 177, 96, 27, 170, 185, 29, 84, 11, 49, 12, 69, 235, 224, 154, 50, 252, 150, 122, 129, 98, 100, 58, 159 }
Session ID: {}
Cipher Suites: [TLS_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_DES_CBC_SHA, SSL_DHE_RSA_WITH_DES_CBC_SHA, SSL_DHE_DSS_WITH_DES_CBC_SHA, SSL_RSA_EXPORT_WITH_DES40_CBC_SHA, SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
Compression Methods: { 0 }
Extension signature_algorithms, signature_algorithms: SHA512withECDSA, SHA512withRSA, SHA384withECDSA, SHA384withRSA, SHA256withECDSA, SHA256withRSA, SHA224withECDSA, SHA224withRSA, SHA1withECDSA, SHA1withRSA, SHA1withDSA, MD5withRSA
Extension server_name, server_name: [type=host_name (0), value=smtp.exmail.qq.com]
***
[write] MD5 and SHA1 hashes: len = 128
0000: 01 00 00 7C 03 03 57 F7 6D 33 6C 60 A2 A1 EE B1 ......W.m3l`....
0010: 60 1B AA B9 1D 54 0B 31 0C 45 EB E0 9A 32 FC 96 `....T.1.E...2..
0020: 7A 81 62 64 3A 9F 00 00 1A 00 2F 00 33 00 32 00 z.bd:...../.3.2.
0030: 0A 00 16 00 13 00 09 00 15 00 12 00 08 00 14 00 ................
0040: 11 00 FF 01 00 00 39 00 0D 00 1A 00 18 06 03 06 ......9.........
0050: 01 05 03 05 01 04 03 04 01 03 03 03 01 02 03 02 ................
0060: 01 02 02 01 01 00 00 00 17 00 15 00 00 12 73 6D ..............sm
0070: 74 70 2E 65 78 6D 61 69 6C 2E 71 71 2E 63 6F 6D tp.exmail.qq.com
Thread-1, WRITE: TLSv1.2 Handshake, length = 128
[Raw write]: length = 133
0000: 16 03 03 00 80 01 00 00 7C 03 03 57 F7 6D 33 6C ...........W.m3l
0010: 60 A2 A1 EE B1 60 1B AA B9 1D 54 0B 31 0C 45 EB `....`....T.1.E.
0020: E0 9A 32 FC 96 7A 81 62 64 3A 9F 00 00 1A 00 2F ..2..z.bd:...../
0030: 00 33 00 32 00 0A 00 16 00 13 00 09 00 15 00 12 .3.2............
0040: 00 08 00 14 00 11 00 FF 01 00 00 39 00 0D 00 1A ...........9....
0050: 00 18 06 03 06 01 05 03 05 01 04 03 04 01 03 03 ................
0060: 03 01 02 03 02 01 02 02 01 01 00 00 00 17 00 15 ................
0070: 00 00 12 73 6D 74 70 2E 65 78 6D 61 69 6C 2E 71 ...smtp.exmail.q
0080: 71 2E 63 6F 6D q.com
[Raw read]: length = 5
0000: 15 03 03 00 02 .....
[Raw read]: length = 2
0000: 02 28 .(
Thread-1, READ: TLSv1.2 Alert, length = 2
Thread-1, RECV TLSv1.2 ALERT: fatal, handshake_failure
Thread-1, called closeSocket()
从调试信息中可以看出,最后几行“[Raw read]: length=2”那里有问题,而正常情况下内容如下:
%% No cached client session
*** ClientHello, TLSv1.2
RandomCookie: GMT: 1459056565 bytes = { 82, 75, 109, 26, 109, 90, 228, 205, 228, 59, 241, 6, 23, 96, 141, 225, 119, 222, 82, 22, 211, 40, 131, 250, 176, 80, 168, 11 }
Session ID: {}
Cipher Suites: [SSL_RSA_WITH_RC4_128_SHA, SSL_RSA_WITH_RC4_128_MD5, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_DES_CBC_SHA, SSL_DHE_RSA_WITH_DES_CBC_SHA, SSL_DHE_DSS_WITH_DES_CBC_SHA, SSL_RSA_EXPORT_WITH_RC4_40_MD5, SSL_RSA_EXPORT_WITH_DES40_CBC_SHA, SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
Compression Methods: { 0 }
Extension signature_algorithms, signature_algorithms: SHA512withECDSA, SHA512withRSA, SHA384withECDSA, SHA384withRSA, SHA256withECDSA, SHA256withRSA, SHA224withECDSA, SHA224withRSA, SHA1withECDSA, SHA1withRSA, SHA1withDSA, MD5withRSA
Extension server_name, server_name: [type=host_name (0), value=smtp.exmail.qq.com]
***
[write] MD5 and SHA1 hashes: len = 134
0000: 01 00 00 82 03 03 57 F7 70 B5 52 4B 6D 1A 6D 5A ......W.p.RKm.mZ
0010: E4 CD E4 3B F1 06 17 60 8D E1 77 DE 52 16 D3 28 ...;...`..w.R..(
0020: 83 FA B0 50 A8 0B 00 00 20 00 05 00 04 00 2F 00 ...P.... ...../.
0030: 33 00 32 00 0A 00 16 00 13 00 09 00 15 00 12 00 3.2.............
0040: 03 00 08 00 14 00 11 00 FF 01 00 00 39 00 0D 00 ............9...
0050: 1A 00 18 06 03 06 01 05 03 05 01 04 03 04 01 03 ................
0060: 03 03 01 02 03 02 01 02 02 01 01 00 00 00 17 00 ................
0070: 15 00 00 12 73 6D 74 70 2E 65 78 6D 61 69 6C 2E ....smtp.exmail.
0080: 71 71 2E 63 6F 6D qq.com
Thread-1, WRITE: TLSv1.2 Handshake, length = 134
[Raw write]: length = 139
0000: 16 03 03 00 86 01 00 00 82 03 03 57 F7 70 B5 52 ...........W.p.R
0010: 4B 6D 1A 6D 5A E4 CD E4 3B F1 06 17 60 8D E1 77 Km.mZ...;...`..w
0020: DE 52 16 D3 28 83 FA B0 50 A8 0B 00 00 20 00 05 .R..(...P.... ..
0030: 00 04 00 2F 00 33 00 32 00 0A 00 16 00 13 00 09 .../.3.2........
0040: 00 15 00 12 00 03 00 08 00 14 00 11 00 FF 01 00 ................
0050: 00 39 00 0D 00 1A 00 18 06 03 06 01 05 03 05 01 .9..............
0060: 04 03 04 01 03 03 03 01 02 03 02 01 02 02 01 01 ................
0070: 00 00 00 17 00 15 00 00 12 73 6D 74 70 2E 65 78 .........smtp.ex
0080: 6D 61 69 6C 2E 71 71 2E 63 6F 6D mail.qq.com
[Raw read]: length = 5
0000: 16 03 03 00 51 ....Q
[Raw read]: length = 81
0000: 02 00 00 4D 03 03 DD C5 E3 95 00 A7 FC B5 34 17 ...M..........4.
0010: 0F AA 93 16 7F 48 3A FD 49 CF A5 41 F4 E2 C0 EE .....H:.I..A....
0020: F0 26 DD 55 EE 78 20 6E 7F B3 22 83 83 DF 1C 4A .&.U.x n.."....J
0030: 43 59 6E 7B 98 A1 50 1E B2 84 41 10 B2 A6 3C 84 CYn...P...A...<.
0040: 10 50 34 19 27 2F 55 00 05 00 00 05 FF 01 00 01 .P4.'/U.........
0050: 00 .
Thread-1, READ: TLSv1.2 Handshake, length = 81
*** ServerHello, TLSv1.2
RandomCookie: GMT: -591076715 bytes = { 0, 167, 252, 181, 52, 23, 15, 170, 147, 22, 127, 72, 58, 253, 73, 207, 165, 65, 244, 226, 192, 238, 240, 38, 221, 85, 238, 120 }
Session ID: {110, 127, 179, 34, 131, 131, 223, 28, 74, 67, 89, 110, 123, 152, 161, 80, 30, 178, 132, 65, 16, 178, 166, 60, 132, 16, 80, 52, 25, 39, 47, 85}
Cipher Suite: SSL_RSA_WITH_RC4_128_SHA
为了看懂这段信息,又恶补了SSL协议,发现该阶段主要是用于客户端发送协议版本信息、sessionid、客户端支持的加密算法、压缩算法等信息,同时附带一个随机数。至此,我们仔细对比了开发环境和生产环境下上述信息的不同,发现两者中的Cipher Suites内容存在较大差异,即开发环境中支持的加密算法种类数要明显多余生产环境。再接着分析调试信息,发现开发环境中得到“ServerHello”之后,出现了一个关键信息:
Cipher Suite: SSL_RSA_WITH_RC4_128_SHA
由此可以判断腾讯邮件服务器在接收“ClientHello”数据包后选用了“SSL_RSA_WITH_RC4_128_SHA”算法。然后再根据该关键信息搜索发现生产环境中提供的加密算法序列中并不包含该项。是时候怀疑JDK版本的问题了!
分别查看两个环境下JDK的版本,发现开发环境用的是JDK1.8_20,而生产环境安装的确是JDK1.8_60,由于平时并不关注JDK小版本升级时到底改了些啥,无法分析出具体差异,带着疑惑继续google,搜索到如下信息:
Java 8 Update 60 (8u60)
發行版本重點
IANA Data 2015e
JDK 8u60 包含 IANA 時區資料版本 2015e。如需詳細資訊,請參閱 JRE 軟體中的時區資料版本。
問題修正:dns_lookup_realm 預設應為 false
Kerberos 的 krb5.conf 檔案中,dns_lookup_realm 設定現已預設為 false。請參閱 8080637。
問題修正:停用 RC4 密碼套件
RC4 型的 TLS 密碼套件 (例如 TLS_RSA_WITH_RC4_128_SHA) 現已視為有缺陷且應不再使用 (請參閱 RFC 7465)。因此,已經透過將 "RC4" 加入 "jdk.tls.disabledAlgorithms" 安全特性中,並將它們從預設啟用的密碼套件清單中移除,使 Oracle JSSE 實行中的 RC4 型 TLS 密碼套件預設為停用。若要重新啟用這些密碼套件,您可以從 java.security 檔案的 "jdk.tls.disabledAlgorithms" 安全特性中移除 "RC4",或者以動態方式呼叫 Security.setProperty(),然後使用 SSLSocket/SSLEngine.setEnabledCipherSuites() 方法將它們重新加入已啟用的密碼套件清單中。您也可以使用 -Djava.security.properties 命令行選項來覆寫 jdk.tls.disabledAlgorithms 安全特性。例如:
java -Djava.security.properties=my.java.security ...
其中 my.java.security 是一個檔案,其所包含的該特性並無 RC4:
jdk.tls.disabledAlgorithms=SSLv3
即使從命令行設定此選項,仍必須使用 SSLSocket/SSLEngine.setEnabledCipherSuites() 方法將 RC4 型的密碼套件重新加入已啟用的密碼套件清單中。請參閱 8076221。
这时才发现刚好JDK1.8_60版本默认禁用了RC4算法,而刚好腾讯邮件服务器又使用了其中的SSL_RSA_WITH_RC4_128_SHA!
至此,问题的根源算是定位到了,接下来便是如何启用该算法。继续查看JDK更新说明,其中提到可以通过修改{jdk_home}/jre/lib/security目录下的java.security文件来启用该算法,具体位置为:
# Note: This property is currently used by Oracle's JSSE implementation.
# It is not guaranteed to be examined and used by other implementations.
#
# Example:
# jdk.tls.disabledAlgorithms=MD5, SSLv3, DSA, RSA keySize < 2048
#jdk.tls.disabledAlgorithms=SSLv3, RC4, MD5withRSA, DH keySize < 768
jdk.tls.disabledAlgorithms=SSLv3, RC4, MD5withRSA, DH keySize < 768
# Legacy algorithms for Secure Socket Layer/Transport Layer Security (SSL/TLS)
# processing in JSSE implementation.
去掉
jdk.tls.disabledAlgorithms=SSLv3, RC4, MD5withRSA, DH keySize < 768
中的RC4部分并保存,然后在代码中通过如下配置来启用相关算法:
Properties
props = new Properties();props.put("mail.smtp.ssl.enable", true);props.put("mail.smtp.host", Configs.EMAIL_HOST);props.put("mail.smtp.port", Configs.EMAIL_PORT);props.put("mail.smtp.auth", true);props.put("mail.smtps.ssl.protocols", "TSLv1 TSLv1.1 TLSv1.2");props.put("mail.transport.protocol", "smtp");props.put("mail.smtp.ssl.ciphersuites","SSL_RSA_WITH_RC4_128_SHA SSL_RSA_WITH_RC4_128_MD5 TLS_RSA_WITH_AES_128_CBC_SHA TLS_DHE_RSA_WITH_AES_128_CBC_SHA TLS_DHE_DSS_WITH_AES_128_CBC_SHA SSL_RSA_WITH_3DES_EDE_CBC_SHA SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA SSL_RSA_WITH_DES_CBC_SHA SSL_DHE_RSA_WITH_DES_CBC_SHA SSL_DHE_DSS_WITH_DES_CBC_SHA SSL_RSA_EXPORT_WITH_RC4_40_MD5 SSL_RSA_EXPORT_WITH_DES40_CBC_SHA SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA TLS_EMPTY_RENEGOTIATION_INFO_SCSV");
因为本项目能确定服务器使用的具体加密算法,可以只配置“SSL_RSA_WITH_RC4_128_SHA”这一项,不确定的话,应包含大部分加密算法,不同算法用空格分割。
保存代码,重新编译、部署,再次测试,问题解决!在google搜索答案时,检索到相同问题的解决方案“RC4被JDK8默认禁用导致腾讯QQ邮箱无法访问”,在此表示感谢。