SSL握手失败Android 5.1

时间:2015-10-07 21:52:03

标签: java android ssl https

自三星设备上安装Android 5.0.2或5.1.1以来,当连接旧路由器“FRITZ!Box 7170”的网页界面时,我的Android应用会收到错误消息。

  

javax.net.ssl.SSLProtocolException:SSL握手已中止:   ssl = 0xaecc7e00:SSL库失败,通常是协议错误   错误:14082174:SSL例程:SSL3_CHECK_CERT_AND_ALGORITHM:获取通道   ccs之前的ID(external / openssl / ssl / s3_clnt.c:3632   0xaf0e1679:00000000)

如果我使用Firefox浏览器连接相同的界面:

  

ssl_error_weak_server_ephemeral_dh_key

我认为这是因为Deffie-Hellmann关键长度不安全? 怎么避免这个?我正在使用HTTPClient建立连接。

1 个答案:

答案 0 :(得分:0)

我遇到同样的问题。

原因是三星安全更新,它更改了SSLSocketFactory给出的默认密码套件数组。如果您使用带有Android M的nexus设备,您将看到此错误消息

ssl_error_weak_server_ephemeral_dh_key

在三星设备上它是

SSL3_CHECK_CERT_AND_ALGORITHM

我的解决方案是覆盖密码套件数组。这是我的SSLSocketFactory,我用它来创建ssl套接字。

public class SpeedportSSLSocketFactory extends SSLSocketFactory {

private final static Logger logger = Logger.getLogger(SpeedportSSLSocketFactory.class);

/**
 * the order of ciphers in this list is important here e.g. TLS_DHE_* must not stay above TLS_RSA_*
 */
private static final String[] APPROVED_CIPHER_SUITES = new String[]{
        "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
        "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
        "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
        "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
        "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
        "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
        "TLS_RSA_WITH_AES_128_GCM_SHA256",
        "TLS_RSA_WITH_AES_128_CBC_SHA",
        "TLS_RSA_WITH_AES_256_CBC_SHA",
        "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
        "TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
        "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
};

private SSLSocketFactory factory;

public SpeedportSSLSocketFactory() {
    try {
        SSLContext sslcontext = SSLContext.getInstance("TLS");
        sslcontext.init(null, new TrustManager[]{
                // accepts certs with valid but expired key chain (incl. root cert)
                new ExpiredSpeedportTrustManager()}, new java.security.SecureRandom());
        factory = sslcontext.getSocketFactory();
    } catch (Exception ex) {
        logger.error("Cannot create SpeedportSSLSocketFactory", ex);
    }
}

// dirty
private void injectHostname(InetAddress address, String host) {
    try {
        Field field = InetAddress.class.getDeclaredField("hostName");
        field.setAccessible(true);
        field.set(address, host);
    } catch (Exception ignored) {
        logger.error("Cannot inject hostName");
    }
}

public static SocketFactory getDefault() {
    return new SpeedportSSLSocketFactory();
}

public Socket createSocket() throws IOException {
    return factory.createSocket();
}

public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
    return factory.createSocket(socket, host, port, autoClose);
}

public Socket createSocket(InetAddress addr, int port, InetAddress localAddr, int localPort) throws IOException {
    return factory.createSocket(addr, port, localAddr, localPort);
}

public Socket createSocket(InetAddress inaddr, int i) throws IOException {
    return factory.createSocket(inaddr, i);
}

public Socket createSocket(String host, int port, InetAddress localAddr, int localPort) throws IOException {
    return factory.createSocket(host, port, localAddr, localPort);
}

public Socket createSocket(String host, int port) throws IOException {

    InetAddress addr = InetAddress.getByName(host);
    injectHostname(addr, host);

    Socket socket = factory.createSocket(addr, port);
    ((SSLSocket) socket).setEnabledCipherSuites(getSupportedCipherSuites());
    return socket;
}

@Override
public String[] getDefaultCipherSuites() {
    return APPROVED_CIPHER_SUITES;
}

@Override
public String[] getSupportedCipherSuites() {
    return APPROVED_CIPHER_SUITES;
}

}

最后两个方法覆盖默认密码套件。我不确定,你需要覆盖两者。

密码套件数组中的顺序也非常重要