35

我正在开发一个应用程序,用户必须为其设置自己的服务器(即nginx)来运行后端应用程序。需要在应用程序中配置相应的域以便连接。我一直在我自己的手机上进行测试(索尼z3c),并开始为5.1开发。后来我收到了6.0版的更新,但仍在仿真器中保持工作状态5.1。不久之前,我开始使用7.0版本的AVD工作,并且惊讶地发现它不会连接到我的服务器,告诉我ssl握手失败。我的nginx配置非常严格,但它适用于5.1和6.0,所以....?!SSLHandshakeException:握手在Android N/7.0上失败

这是我所知道的:

  • 我使用V24支持库,即我的compileSdkVersion为24
  • 我使用排v1.0.0
  • 我试过TLSSocketFactory,但它没有改变任何东西。这似乎在大多数情况下被用于防止老版本的SDK版本使用SSL3。
  • 我试过增加timeout,但它没有改变任何东西。
  • 我试过直接使用HttpURLConnection,但它并没有改变任何东西除了堆栈跟踪(它没有排除引用,但其他相同)。

如果没有TLSSocketFactory,请求将通过一个裸请求队列进行,该请求队列使用Volley.newRequestQueue(context)实例化。

这是我在Android Studio中看到:

W/System.err: com.android.volley.NoConnectionError: javax.net.ssl.SSLHandshakeException: Connection closed by peer 
W/System.err:  at com.android.volley.toolbox.BasicNetwork.performRequest(BasicNetwork.java:151) 
W/System.err:  at com.android.volley.NetworkDispatcher.run(NetworkDispatcher.java:112) 
W/System.err: Caused by: javax.net.ssl.SSLHandshakeException: Connection closed by peer 
W/System.err:  at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method) 
W/System.err:  at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:357) 
W/System.err:  at com.android.okhttp.Connection.connectTls(Connection.java:235) 
W/System.err:  at com.android.okhttp.Connection.connectSocket(Connection.java:199) 
W/System.err:  at com.android.okhttp.Connection.connect(Connection.java:172) 
W/System.err:  at com.android.okhttp.Connection.connectAndSetOwner(Connection.java:367) 
W/System.err:  at com.android.okhttp.OkHttpClient$1.connectAndSetOwner(OkHttpClient.java:130) 
W/System.err:  at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:329) 
W/System.err:  at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:246) 
W/System.err:  at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:457) 
W/System.err:  at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:126) 
W/System.err:  at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getOutputStream(HttpURLConnectionImpl.java:257) 
W/System.err:  at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getOutputStream(DelegatingHttpsURLConnection.java:218) 
W/System.err:  at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java) 
W/System.err:  at com.android.volley.toolbox.HurlStack.addBodyIfExists(HurlStack.java:264) 
W/System.err:  at com.android.volley.toolbox.HurlStack.setConnectionParametersForRequest(HurlStack.java:234) 
W/System.err:  at com.android.volley.toolbox.HurlStack.performRequest(HurlStack.java:107) 
W/System.err:  at com.android.volley.toolbox.BasicNetwork.performRequest(BasicNetwork.java:96) 
W/System.err: ... 1 more 
W/System.err: Suppressed: javax.net.ssl.SSLHandshakeException: Handshake failed 
W/System.err:  at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:429) 
W/System.err:  ... 17 more 
W/System.err: Caused by: javax.net.ssl.SSLProtocolException: SSL handshake terminated: ssl=0x7ffef3748040: Failure in SSL library, usually a protocol error 
W/System.err: error:10000410:SSL routines:OPENSSL_internal:SSLV3_ALERT_HANDSHAKE_FAILURE (external/boringssl/src/ssl/s3_pkt.c:610 0x7ffeda1d2240:0x00000001) 
W/System.err: error:1000009a:SSL routines:OPENSSL_internal:HANDSHAKE_FAILURE_ON_CLIENT_HELLO (external/boringssl/src/ssl/s3_clnt.c:764 0x7ffee9d2b70a:0x00000000) 
W/System.err:  at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method) 
W/System.err:  at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:357) 
W/System.err:  ... 17 more 

因为它说SSLV3_ALERT_HANDSHAKE_FAILURE由于某种原因,尝试使用SSLv3的连接和失败我只能假设,但这样做没有任何意义,我任何。这可能是一个密码问题,但我怎么知道它试图使用什么?我宁愿不启用服务器上的密码,进行连接尝试并重复。

我nginx的网站使用的是咱们加密证书,并具有以下配置:

ssl_stapling on; 
ssl_stapling_verify on; 
ssl_trusted_certificate /etc/ssl/certs/lets-encrypt-x1-cross-signed.pem; 
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:!aNULL; 
ssl_dhparam /etc/ssl/certs/dhparam.pem; 
ssl_ecdh_curve secp384r1; 
ssl_prefer_server_ciphers on; 
ssl_protocols TLSv1.2; 

为了测试这些密码我有一个script,它证实了这些密码(在喘息VPS服务器的网络外运行) :

 
Testing ECDHE-RSA-AES256-GCM-SHA384...YES 
Testing ECDHE-ECDSA-AES256-GCM-SHA384...NO (sslv3 alert handshake failure) 
Testing ECDHE-RSA-AES256-SHA384...NO (sslv3 alert handshake failure) 
Testing ECDHE-ECDSA-AES256-SHA384...NO (sslv3 alert handshake failure) 
Testing ECDHE-RSA-AES256-SHA...NO (sslv3 alert handshake failure) 
Testing ECDHE-ECDSA-AES256-SHA...NO (sslv3 alert handshake failure) 
Testing SRP-DSS-AES-256-CBC-SHA...NO (sslv3 alert handshake failure) 
Testing SRP-RSA-AES-256-CBC-SHA...NO (sslv3 alert handshake failure) 
Testing DHE-DSS-AES256-GCM-SHA384...NO (sslv3 alert handshake failure) 
Testing DHE-RSA-AES256-GCM-SHA384...NO (sslv3 alert handshake failure) 
Testing DHE-RSA-AES256-SHA256...NO (sslv3 alert handshake failure) 
Testing DHE-DSS-AES256-SHA256...NO (sslv3 alert handshake failure) 
Testing DHE-RSA-AES256-SHA...NO (sslv3 alert handshake failure) 
Testing DHE-DSS-AES256-SHA...NO (sslv3 alert handshake failure) 
Testing DHE-RSA-CAMELLIA256-SHA...NO (sslv3 alert handshake failure) 
Testing DHE-DSS-CAMELLIA256-SHA...NO (sslv3 alert handshake failure) 
Testing AECDH-AES256-SHA...NO (sslv3 alert handshake failure) 
Testing SRP-AES-256-CBC-SHA...NO (sslv3 alert handshake failure) 
Testing ADH-AES256-GCM-SHA384...NO (sslv3 alert handshake failure) 
Testing ADH-AES256-SHA256...NO (sslv3 alert handshake failure) 
Testing ADH-AES256-SHA...NO (sslv3 alert handshake failure) 
Testing ADH-CAMELLIA256-SHA...NO (sslv3 alert handshake failure) 
Testing ECDH-RSA-AES256-GCM-SHA384...NO (sslv3 alert handshake failure) 
Testing ECDH-ECDSA-AES256-GCM-SHA384...NO (sslv3 alert handshake failure) 
Testing ECDH-RSA-AES256-SHA384...NO (sslv3 alert handshake failure) 
Testing ECDH-ECDSA-AES256-SHA384...NO (sslv3 alert handshake failure) 
Testing ECDH-RSA-AES256-SHA...NO (sslv3 alert handshake failure) 
Testing ECDH-ECDSA-AES256-SHA...NO (sslv3 alert handshake failure) 
Testing AES256-GCM-SHA384...NO (sslv3 alert handshake failure) 
Testing AES256-SHA256...NO (sslv3 alert handshake failure) 
Testing AES256-SHA...NO (sslv3 alert handshake failure) 
Testing CAMELLIA256-SHA...NO (sslv3 alert handshake failure) 
Testing PSK-AES256-CBC-SHA...NO (no ciphers available) 
Testing ECDHE-RSA-DES-CBC3-SHA...NO (sslv3 alert handshake failure) 
Testing ECDHE-ECDSA-DES-CBC3-SHA...NO (sslv3 alert handshake failure) 
Testing SRP-DSS-3DES-EDE-CBC-SHA...NO (sslv3 alert handshake failure) 
Testing SRP-RSA-3DES-EDE-CBC-SHA...NO (sslv3 alert handshake failure) 
Testing EDH-RSA-DES-CBC3-SHA...NO (sslv3 alert handshake failure) 
Testing EDH-DSS-DES-CBC3-SHA...NO (sslv3 alert handshake failure) 
Testing AECDH-DES-CBC3-SHA...NO (sslv3 alert handshake failure) 
Testing SRP-3DES-EDE-CBC-SHA...NO (sslv3 alert handshake failure) 
Testing ADH-DES-CBC3-SHA...NO (sslv3 alert handshake failure) 
Testing ECDH-RSA-DES-CBC3-SHA...NO (sslv3 alert handshake failure) 
Testing ECDH-ECDSA-DES-CBC3-SHA...NO (sslv3 alert handshake failure) 
Testing DES-CBC3-SHA...NO (sslv3 alert handshake failure) 
Testing PSK-3DES-EDE-CBC-SHA...NO (no ciphers available) 
Testing ECDHE-RSA-AES128-GCM-SHA256...YES 
Testing ECDHE-ECDSA-AES128-GCM-SHA256...NO (sslv3 alert handshake failure) 
Testing ECDHE-RSA-AES128-SHA256...NO (sslv3 alert handshake failure) 
Testing ECDHE-ECDSA-AES128-SHA256...NO (sslv3 alert handshake failure) 
Testing ECDHE-RSA-AES128-SHA...NO (sslv3 alert handshake failure) 
Testing ECDHE-ECDSA-AES128-SHA...NO (sslv3 alert handshake failure) 
Testing SRP-DSS-AES-128-CBC-SHA...NO (sslv3 alert handshake failure) 
Testing SRP-RSA-AES-128-CBC-SHA...NO (sslv3 alert handshake failure) 
Testing DHE-DSS-AES128-GCM-SHA256...NO (sslv3 alert handshake failure) 
Testing DHE-RSA-AES128-GCM-SHA256...NO (sslv3 alert handshake failure) 
Testing DHE-RSA-AES128-SHA256...NO (sslv3 alert handshake failure) 
Testing DHE-DSS-AES128-SHA256...NO (sslv3 alert handshake failure) 
Testing DHE-RSA-AES128-SHA...NO (sslv3 alert handshake failure) 
Testing DHE-DSS-AES128-SHA...NO (sslv3 alert handshake failure) 
Testing DHE-RSA-SEED-SHA...NO (sslv3 alert handshake failure) 
Testing DHE-DSS-SEED-SHA...NO (sslv3 alert handshake failure) 
Testing DHE-RSA-CAMELLIA128-SHA...NO (sslv3 alert handshake failure) 
Testing DHE-DSS-CAMELLIA128-SHA...NO (sslv3 alert handshake failure) 
Testing AECDH-AES128-SHA...NO (sslv3 alert handshake failure) 
Testing SRP-AES-128-CBC-SHA...NO (sslv3 alert handshake failure) 
Testing ADH-AES128-GCM-SHA256...NO (sslv3 alert handshake failure) 
Testing ADH-AES128-SHA256...NO (sslv3 alert handshake failure) 
Testing ADH-AES128-SHA...NO (sslv3 alert handshake failure) 
Testing ADH-SEED-SHA...NO (sslv3 alert handshake failure) 
Testing ADH-CAMELLIA128-SHA...NO (sslv3 alert handshake failure) 
Testing ECDH-RSA-AES128-GCM-SHA256...NO (sslv3 alert handshake failure) 
Testing ECDH-ECDSA-AES128-GCM-SHA256...NO (sslv3 alert handshake failure) 
Testing ECDH-RSA-AES128-SHA256...NO (sslv3 alert handshake failure) 
Testing ECDH-ECDSA-AES128-SHA256...NO (sslv3 alert handshake failure) 
Testing ECDH-RSA-AES128-SHA...NO (sslv3 alert handshake failure) 
Testing ECDH-ECDSA-AES128-SHA...NO (sslv3 alert handshake failure) 
Testing AES128-GCM-SHA256...NO (sslv3 alert handshake failure) 
Testing AES128-SHA256...NO (sslv3 alert handshake failure) 
Testing AES128-SHA...NO (sslv3 alert handshake failure) 
Testing SEED-SHA...NO (sslv3 alert handshake failure) 
Testing CAMELLIA128-SHA...NO (sslv3 alert handshake failure) 
Testing PSK-AES128-CBC-SHA...NO (no ciphers available) 
Testing ECDHE-RSA-RC4-SHA...NO (sslv3 alert handshake failure) 
Testing ECDHE-ECDSA-RC4-SHA...NO (sslv3 alert handshake failure) 
Testing AECDH-RC4-SHA...NO (sslv3 alert handshake failure) 
Testing ADH-RC4-MD5...NO (sslv3 alert handshake failure) 
Testing ECDH-RSA-RC4-SHA...NO (sslv3 alert handshake failure) 
Testing ECDH-ECDSA-RC4-SHA...NO (sslv3 alert handshake failure) 
Testing RC4-SHA...NO (sslv3 alert handshake failure) 
Testing RC4-MD5...NO (sslv3 alert handshake failure) 
Testing PSK-RC4-SHA...NO (no ciphers available) 
Testing EDH-RSA-DES-CBC-SHA...NO (sslv3 alert handshake failure) 
Testing EDH-DSS-DES-CBC-SHA...NO (sslv3 alert handshake failure) 
Testing ADH-DES-CBC-SHA...NO (sslv3 alert handshake failure) 
Testing DES-CBC-SHA...NO (sslv3 alert handshake failure) 
Testing EXP-EDH-RSA-DES-CBC-SHA...NO (sslv3 alert handshake failure) 
Testing EXP-EDH-DSS-DES-CBC-SHA...NO (sslv3 alert handshake failure) 
Testing EXP-ADH-DES-CBC-SHA...NO (sslv3 alert handshake failure) 
Testing EXP-DES-CBC-SHA...NO (sslv3 alert handshake failure) 
Testing EXP-RC2-CBC-MD5...NO (sslv3 alert handshake failure) 
Testing EXP-ADH-RC4-MD5...NO (sslv3 alert handshake failure) 
Testing EXP-RC4-MD5...NO (sslv3 alert handshake failure) 
Testing ECDHE-RSA-NULL-SHA...NO (sslv3 alert handshake failure) 
Testing ECDHE-ECDSA-NULL-SHA...NO (sslv3 alert handshake failure) 
Testing AECDH-NULL-SHA...NO (sslv3 alert handshake failure) 
Testing ECDH-RSA-NULL-SHA...NO (sslv3 alert handshake failure) 
Testing ECDH-ECDSA-NULL-SHA...NO (sslv3 alert handshake failure) 
Testing NULL-SHA256...NO (sslv3 alert handshake failure) 
Testing NULL-SHA...NO (sslv3 alert handshake failure) 
Testing NULL-MD5...NO (sslv3 alert handshake failure 

可以打开模拟器的浏览器服务器的URL并获得一个完美的JSON响应,所以我知道系统本身能。

所以问题是,为什么我不能在Android 7上连接?

更新

我已经看了使用tcpdump和Wireshark的捕获的数据包,并启用密码是在客户问候,所以这不应该是一个问题。

 
Cipher Suites (18 suites) 

Cipher Suite: Unknown (0xcca9) 
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b) 
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (0xc02c) 
Cipher Suite: Unknown (0xcca8) 
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f) 
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030) 
Cipher Suite: TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 (0x009e) 
Cipher Suite: TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 (0x009f) 
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (0xc009) 
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA (0xc00a) 
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (0xc013) 
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (0xc014) 
Cipher Suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA (0x0033) 
Cipher Suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA (0x0039) 
Cipher Suite: TLS_RSA_WITH_AES_128_GCM_SHA256 (0x009c) 
Cipher Suite: TLS_RSA_WITH_AES_256_GCM_SHA384 (0x009d) 
Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA (0x002f) 
Cipher Suite: TLS_RSA_WITH_AES_256_CBC_SHA (0x0035) 

正如你可以看到0xc02f0xc030比赛,但接下来的TLSv1.2工作包说:Alert (21), Handshake Failure (40)

更新2

这些是从5.1的Android曲线中的ClientHello:

 
Elliptic curves (25 curves) 

Elliptic curve: sect571r1 (0x000e) 
Elliptic curve: sect571k1 (0x000d) 
Elliptic curve: secp521r1 (0x0019) 
Elliptic curve: sect409k1 (0x000b) 
Elliptic curve: sect409r1 (0x000c) 
Elliptic curve: secp384r1 (0x0018) 
Elliptic curve: sect283k1 (0x0009) 
Elliptic curve: sect283r1 (0x000a) 
Elliptic curve: secp256k1 (0x0016) 
Elliptic curve: secp256r1 (0x0017) 
Elliptic curve: sect239k1 (0x0008) 
Elliptic curve: sect233k1 (0x0006) 
Elliptic curve: sect233r1 (0x0007) 
Elliptic curve: secp224k1 (0x0014) 
Elliptic curve: secp224r1 (0x0015) 
Elliptic curve: sect193r1 (0x0004) 
Elliptic curve: sect193r2 (0x0005) 
Elliptic curve: secp192k1 (0x0012) 
Elliptic curve: secp192r1 (0x0013) 
Elliptic curve: sect163k1 (0x0001) 
Elliptic curve: sect163r1 (0x0002) 
Elliptic curve: sect163r2 (0x0003) 
Elliptic curve: secp160k1 (0x000f) 
Elliptic curve: secp160r1 (0x0010) 
Elliptic curve: secp160r2 (0x0011) 

在服务器问候secp384r1 (0x0018)被返回。

这是从Android的7:

 
Elliptic curves (1 curve) 

Elliptic curve: secp256r1 (0x0017) 

在握手故障而导致的。

通过删除secp384r1或将其替换为默认值(prime256v1)来更改nginx配置会使其工作。所以我想这个问题仍然存在:我能够添加椭圆曲线吗?

使用模拟器时的捕获数据与使用Android 7.0设备(通用移动4G)时相同。

更新3

小更新,但值得一提的:我把它用在模拟器上工作的Android 7.1.1(!)。它示出了如下的数据(再次使用tcpdump的抓住和使用Wireshark的观察):

 
Elliptic curves (3 curves) 

Elliptic curve: secp256r1 (0x0017) 
Elliptic curve: secp384r1 (0x0018) 
Elliptic curve: secp512r1 (0x0019) 

它显示了相同的18个密码套件。

+1

“我可以在模拟器中的浏览器中打开服务器的URL” - 最有可能的,该浏览器不使用HTTP连接Java代码,所以这只是一个局部测试。网络,特殊的SSL处理,在Android 7.0中进行了相当大的改革,以支持[网络安全配置](https://developer.android.com/training/articles/security-config.html)。您是否依赖用户证书(即用户通过设置添加的证书)? – CommonsWare

+0

我认为你的服务器配置可能有点太严格。 tt只允许一条曲线和两条密码。 Wireshark中的连接和分析的数据包捕获将显示ClientHello中客户端提供哪些密码和曲线,以便您可以检查客户端是否也支持您的服务器支持。不必太在意“** sslv3 **握手失败” - 当使用TLS 1.2时,您也会得到这个,因为协议在这个区域基本相同,因此它从sslv3重用了使用奇怪错误消息的功能。 –

+0

@CommonsWare:我不依赖于用户证书,不。 – Cornelis

回答

2

默认情况下,安全的连接(使用诸如TLS协议和HTTPS)从所有的应用程序信任的预先安装的系统的CA和应用定位的Android 6.0(API级23),并且还降低信任用户添加的CA存储默认。

这意味着在Android Nougat(7.0)上,CA的游戏完全改变。如果你有你的密钥证书,你可以添加一个网络安全配置文件(如果你有你的证书),如下所述: https://developer.android.com/training/articles/security-config.html

或者你可以创建自己的TrustManager,如下所述: https://developer.android.com/training/articles/security-ssl.html#SelfSigned

或者你可以启用的密码套件您的服务器的需求但默认不启用在Android N.例如,这里有我需要在自己的应用程序添加同一家旧的Windows CE服务器的两个密码:

SSLSocket sslsock = (SSLSocket) createSocket(); 
    List<String> cipherSuitesToEnable = new ArrayList<>(); 
    cipherSuitesToEnable.add("SSL_RSA_WITH_RC4_128_SHA"); 
    cipherSuitesToEnable.add("SSL_RSA_WITH_3DES_EDE_CBC_SHA"); 
    sslsock.setEnabledCipherSuites(cipherSuitesToEnable.toArray(new String[cipherSuitesToEnable.size()])); 
+0

您链接到的文档中的示例都非常清楚与CA /证书信任问题有关。我可以通过将'ssl_ecdh_curve'的服务器nginx配置从'secp384r1'改为'prime256v1'(或将其删除,因为这是我的nginx版本的默认值),从而防止出现错误。因此SSL证书不是问题。 – Cornelis

+0

那么你的问题就解决了吗? –

+0

问题的症结在于:如何让Android 7在其ClientHello中提供'secp384r1'。它适用于旧版本的Android操作系统,洙..为什么它不能在7上工作? – Cornelis

5

在这里你工作的解决方案V OLLEY:

创建在单码队列前:

public class VolleyServiceSingleton { 

    private RequestQueue mRequestQueue; 
    private HurlStack mStack; 

    private VolleyServiceSingleton(){ 

     SSLSocketFactoryExtended factory = null; 

     try { 
      factory = new SSLSocketFactoryExtended(); 
     } catch (NoSuchAlgorithmException e) { 
      e.printStackTrace(); 
     } catch (KeyManagementException e) { 
      e.printStackTrace(); 
     } 


     final SSLSocketFactoryExtended finalFactory = factory; 
     mStack = new HurlStack() { 
      @Override 
      protected HttpURLConnection createConnection(URL url) throws IOException { 
       HttpsURLConnection httpsURLConnection = (HttpsURLConnection) super.createConnection(url); 
       try { 
        httpsURLConnection.setSSLSocketFactory(finalFactory); 
        httpsURLConnection.setRequestProperty("charset", "utf-8"); 

       } catch (Exception e) { 
        e.printStackTrace(); 
       } 
       return httpsURLConnection; 
      } 

     }; 



     mRequestQueue = Volley.newRequestQueue(YourApplication.getContext(), mStack, -1);  

    } 


} 

这里是SSLSocketFactoryExtended:

public class SSLSocketFactoryExtended extends SSLSocketFactory 
{ 
    private SSLContext mSSLContext; 
    private String[] mCiphers; 
    private String[] mProtocols; 


    public SSLSocketFactoryExtended() throws NoSuchAlgorithmException, KeyManagementException 
    { 
     initSSLSocketFactoryEx(null,null,null); 
    } 

    public String[] getDefaultCipherSuites() 
    { 
     return mCiphers; 
    } 

    public String[] getSupportedCipherSuites() 
    { 
     return mCiphers; 
    } 

    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException 
    { 
     SSLSocketFactory factory = mSSLContext.getSocketFactory(); 
     SSLSocket ss = (SSLSocket)factory.createSocket(s, host, port, autoClose); 

     ss.setEnabledProtocols(mProtocols); 
     ss.setEnabledCipherSuites(mCiphers); 

     return ss; 
    } 

    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException 
    { 
     SSLSocketFactory factory = mSSLContext.getSocketFactory(); 
     SSLSocket ss = (SSLSocket)factory.createSocket(address, port, localAddress, localPort); 

     ss.setEnabledProtocols(mProtocols); 
     ss.setEnabledCipherSuites(mCiphers); 

     return ss; 
    } 

    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException 
    { 
     SSLSocketFactory factory = mSSLContext.getSocketFactory(); 
     SSLSocket ss = (SSLSocket)factory.createSocket(host, port, localHost, localPort); 

     ss.setEnabledProtocols(mProtocols); 
     ss.setEnabledCipherSuites(mCiphers); 

     return ss; 
    } 

    public Socket createSocket(InetAddress host, int port) throws IOException 
    { 
     SSLSocketFactory factory = mSSLContext.getSocketFactory(); 
     SSLSocket ss = (SSLSocket)factory.createSocket(host, port); 

     ss.setEnabledProtocols(mProtocols); 
     ss.setEnabledCipherSuites(mCiphers); 

     return ss; 
    } 

    public Socket createSocket(String host, int port) throws IOException 
    { 
     SSLSocketFactory factory = mSSLContext.getSocketFactory(); 
     SSLSocket ss = (SSLSocket)factory.createSocket(host, port); 

     ss.setEnabledProtocols(mProtocols); 
     ss.setEnabledCipherSuites(mCiphers); 

     return ss; 
    } 

    private void initSSLSocketFactoryEx(KeyManager[] km, TrustManager[] tm, SecureRandom random) 
      throws NoSuchAlgorithmException, KeyManagementException 
    { 
     mSSLContext = SSLContext.getInstance("TLS"); 
     mSSLContext.init(km, tm, random); 

     mProtocols = GetProtocolList(); 
     mCiphers = GetCipherList(); 
    } 

    protected String[] GetProtocolList() 
    { 
     String[] protocols = { "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"}; 
     String[] availableProtocols = null; 

     SSLSocket socket = null; 

     try 
     { 
      SSLSocketFactory factory = mSSLContext.getSocketFactory(); 
      socket = (SSLSocket)factory.createSocket(); 

      availableProtocols = socket.getSupportedProtocols(); 
     } 
     catch(Exception e) 
     { 
      return new String[]{ "TLSv1" }; 
     } 
     finally 
     { 
      if(socket != null) 
       try { 
        socket.close(); 
       } catch (IOException e) { 
       } 
     } 

     List<String> resultList = new ArrayList<String>(); 
     for(int i = 0; i < protocols.length; i++) 
     { 
      int idx = Arrays.binarySearch(availableProtocols, protocols[i]); 
      if(idx >= 0) 
       resultList.add(protocols[i]); 
     } 

     return resultList.toArray(new String[0]); 
    } 

    protected String[] GetCipherList() 
    { 
     List<String> resultList = new ArrayList<String>(); 
     SSLSocketFactory factory = mSSLContext.getSocketFactory(); 
     for(String s : factory.getSupportedCipherSuites()){ 
      Log.e("CipherSuite type = ",s); 
      resultList.add(s); 
     } 
     return resultList.toArray(new String[resultList.size()]); 
    } 

} 

在这个代码我简单补充一点,由设备支持的所有加密算法,对我来说这作品),可能会帮助某人)欢呼声)

ps无需在清单中添加安全网络配置参数。

10

我有一个自签名证书的问题,问题是这是不是由Android 7.0

接受了密码,我跑:openssl s_client -showcerts -connect <domain>:<port>

的结果,我发现:

Protocol : TLSv1 
Cipher : DHE-RSA-AES256-SHA 

我搜索了密码的Android的等效,并把它添加到我的改造Restadapter:

ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) 
      .tlsVersions(TlsVersion.TLS_1_2) 
      .cipherSuites(

CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA) 
      .build(); 

clientBuilder.connectionSpecs(Collections.singletonList(spec)); 

从这里,我们接受了根据Android 7.0的每个连接都带有正确的证书固定或正确的证书,但带有“错误”的密码。

+1

这个确切的解决方案为我们工作 - AT&T的一群Galaxy S7用户最近得到了7.0更新,并在重大活动期间彻底打破了我们的应用程序。糟糕的谷歌,不好。 – DiscDev

+0

@DiskDev我是否理解你的问题,在AT&T 7.0版更新之后,问题仍然得到解决或再次崩溃? –

+1

您的解决方法解决了我们的问题。 AT&T Galaxy S7用户在一周或两周前获得了7.0版本的更新,并且由于我们不知道这个问题,它导致我们的应用在用户使用该设备时非常不合时宜地崩溃。我们在一周内有另一个事件,并且有了解决方法(因为我们正在使用改进功能,并且不想对服务器端的密码进行更改),我们正在备份并运行。谢谢! – DiscDev

29

这是Android 7.0中的一个已知回归,得到了Google的承认,并在Android 7.1.1发布之前有所修复。这是错误报告:https://code.google.com/p/android/issues/detail?id=224438

为了清楚起见,这里的错误是7.0只支持一个椭圆曲线:prime256v1又名secp256r1又名NIST P-256,正如Cornelis在问题中指出的那样。所以,如果你的用户都面临这个问题,这些都可以给你解决方法(忽略了一个事实,你的用户应该非常只是升级至Android 7.1.1):

  • 配置您的服务器使用椭圆曲线prime256v1 。例如,在Nginx 1.10中,您可以通过设置ssl_ecdh_curve prime256v1;来完成此操作。

  • 如果不起作用,使用不依赖于椭圆曲线加密算法旧的密码套件(如DHE-RSA-AES256-GCM-SHA384)(确保你理解你在这里做什么在数据安全性方面的)

注:在椭圆曲线加密方面的专家,确保做自己的研究,对我的建议的安全隐患。下面是我提到在写这个答案的一些其他链接:

+0

有人有解决上述问题吗? – Kels

+0

@Kels这个Android 7.0错误并没有真正的解决方案,只有我上面列出的解决方法。 –

+0

感谢您的回复。但是当我在7.1.1中测试并仍然有相同的错误。 – Kels

0

同样在这里。我的Nginx服务器使用sll_ecdh_curve prime384v1设置。不幸的是,由于客户的secirity策略,后端人员不允许我按照Vicky Chijwani的指示配置Nginx服务器。我曾尝试使用Valley和最新版本的OkHttp库,但它没有帮助。 要绕过该错误,我必须使用WebView与Adroid 7.0设备上的API服务进行通信。这是我的Adapter类。我希望别人会觉得它有用。

/** 
* Connection to API service using WebView (for Android 7.0 devices) 
* 
* Created by fishbone on 09.08.17. 
*/ 
@RequiresApi(api = Build.VERSION_CODES.N) 
class WebViewHttpsConnection extends ApiConnection { 

    private static final long TIMEOUT = 30000; 
    private static final String POST_DATA_SCRIPT = "javascript:(function(){" + 
      "var xhr = new XMLHttpRequest();\n" + 
      "xhr.open(\"POST\", \"%1$s\", true);\n" + 
      "xhr.setRequestHeader(\"Content-type\", \"application/json\");\n" + 
      "xhr.onreadystatechange = function() {\n" + 
      " if (xhr.readyState === 4) {\n" + 
      "  listener.onResult(xhr.status, xhr.responseText);\n" + 
      " }\n" + 
      "};\n" + 
      "xhr.send('%2$s');\n" + 
      "})();"; 

    WebViewHttpsConnection(Context context) { 
     super(context); 
    } 
    /** 
    * Send data to API Service. 
    * 
    * @param url URL of API Service 
    * @param request JSON Object serialized into String 
    * @return API response 
    * @throws IOException errors 
    */ 
    @Override 
    public String sendData(final URL url, final String request) throws IOException { 
     // We should escape backslashes in JSON because JS unescape it back 
     final String javaScript = String.format(POST_DATA_SCRIPT, url.toString(), 
       request.replace("\\", "\\\\")); 
     final RequestResultListener listener = new RequestResultListener(); 
     // We must use WebView only from 'main' Thread, therefore I using Handler with Application context 
     Handler handler = new Handler(getContext().getApplicationContext().getMainLooper()); 
     handler.post(new Runnable() { 

      @SuppressLint({"SetJavaScriptEnabled", "AddJavascriptInterface"}) // JavaScript is only for me and I'll use it only on Android 7.0 devices, so not scary 
      @Override 
      public void run() { 
       // WebView must be created, configured and called from the same Thread 
       final WebView webView = new WebView(getContext(), null); 
       webView.getSettings().setJavaScriptEnabled(true); 
       webView.addJavascriptInterface(listener, "listener"); 
       webView.setWebViewClient(new WebViewClient() { 

        @Override 
        public void onPageFinished(WebView view, String url) { 
         // As soon as loaded any page from target domain, we call JS-script that will make POST request 
         webView.loadUrl(javaScript); 
        } 
       }); 
       // I cant use postUrl() method because WebView doesn't allow to define 'Content-type' header, but my API service accepts only 'application/json' content type 
       // To complete CORS requirements we make any GET request to lets WebView navigate to the target domain, otherwise it will send 'null' as 'Origin' in headers 
       webView.loadUrl(url.toString()); 
      } 
     }); 
     // And further we waiting for response of API service 
     try { 
      if (!listener.latch.await(TIMEOUT, TimeUnit.MILLISECONDS)) { 
       throw new IOException("Timeout connection to server"); 
      } 
     } catch (InterruptedException e) { 
      throw new IOException("Connection to server was interrupted"); 
     } 
     if (listener.code != HttpURLConnection.HTTP_OK) { 
      throw new HttpRetryException("Server return error code " + listener.code, 
        listener.code); 
     } 
     if (TextUtils.isEmpty(listener.result)) { 
      throw new HttpRetryException("Service return empty response", listener.code); 
     } 
     return listener.result; 
    } 
    /** 
    * Callback interface for receiving API Service response from JavaScript inside WebView 
    */ 
    private static class RequestResultListener { 

     CountDownLatch latch = new CountDownLatch(1); 
     String result = null; 
     int code; 

     @JavascriptInterface 
     public void onResult(int code, String result) { 
      this.result = result; 
      this.code = code; 
      latch.countDown(); 
     } 
    } 
}