54

如何在 Android 上接受 Java 中的自签名证书?

代码示例将是完美的。

我在 Internet 上到处查看,虽然有些人声称找到了解决方案,但它要么不起作用,要么没有示例代码来支持它。

4

8 回答 8

37

我在 exchangeIt 中有这个功能,它通过 WebDav 连接到 Microsoft Exchange。下面是一些创建 HttpClient 的代码,它将通过 SSL 连接到自签名证书:

SchemeRegistry schemeRegistry = new SchemeRegistry();
// http scheme
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
// https scheme
schemeRegistry.register(new Scheme("https", new EasySSLSocketFactory(), 443));

HttpParams params = new BasicHttpParams();
params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 30);
params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(30));
params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);

ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);

EasySSLSocketFactory 在这里,EasyX509TrustManager 在这里

exchangeIt 的代码是开源的,如果您有任何问题,请在此处托管在 googlecode 上。我不再积极研究它了,但代码应该可以工作。

请注意,自 Android 2.2 以来,该过程发生了一些变化,因此请检查此项以使上述代码正常工作。

于 2009-08-01T18:22:12.070 回答
37

正如 EJP 正确评论的那样,“读者应该注意,这种技术根本不安全。除非至少对一个对等方进行身份验证,否则 SSL 是不安全的。请参阅 RFC 2246。”

话虽如此,这是另一种方式,没有任何额外的类:

import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.X509TrustManager;

private void trustEveryone() {
    try {
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier(){
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }});
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, new X509TrustManager[]{new X509TrustManager(){
            public void checkClientTrusted(X509Certificate[] chain,
                    String authType) throws CertificateException {}
            public void checkServerTrusted(X509Certificate[] chain,
                    String authType) throws CertificateException {}
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }}}, new SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(
                context.getSocketFactory());
    } catch (Exception e) { // should never happen
        e.printStackTrace();
    }
}
于 2009-10-22T15:24:00.180 回答
36

我昨天在将我们公司的 RESTful API 迁移到 HTTPS 时遇到了这个问题,但使用的是自签名 SSL 证书。

我到处寻找,但我发现的所有“正确”标记答案都包括禁用证书验证,显然覆盖了 SSL 的所有意义。

我终于找到了一个解决方案:

  1. 创建本地密钥库

    要使您的应用能够验证您的自签名证书,您需要以 Android 可以信任您的端点的方式提供带有证书的自定义密钥库。

此类自定义密钥库的格式是来自BouncyCastle的“BKS” ,因此您需要 1.46 版本的 BouncyCastleProvider,您可以在此处下载。

您还需要您的自签名证书,我假设它的名称为self_cert.pem.

现在创建密钥库的命令是:

<!-- language: lang-sh -->

    $ keytool -import -v -trustcacerts -alias 0 \
    -file *PATH_TO_SELF_CERT.PEM* \
    -keystore *PATH_TO_KEYSTORE* \
    -storetype BKS \
    -provider org.bouncycastle.jce.provider.BouncyCastleProvider \
    -providerpath *PATH_TO_bcprov-jdk15on-146.jar* \
    -storepass *STOREPASS*

PATH_TO_KEYSTORE指向将在其中创建密钥库的文件。它不能存在

PATH_TO_bcprov-jdk15on-146.jar.JAR是下载的 .jar 库的路径。

STOREPASS是您新创建的密钥库密码。

  1. 在您的应用程序中包含 KeyStore

将新创建的密钥库从复制PATH_TO_KEYSTOREres/raw/certs.bkscerts.bks只是文件名;您可以使用任何您想要的名称)

res/values/strings.xml用_

<!-- language: lang-xml -->

    <resources>
    ...
        <string name="store_pass">*STOREPASS*</string>
    ...
    </resources>
  1. 创建一个继承的此类DefaultHttpClient

    import android.content.Context;
    import android.util.Log;
    import org.apache.http.conn.scheme.PlainSocketFactory;
    import org.apache.http.conn.scheme.Scheme;
    import org.apache.http.conn.scheme.SchemeRegistry;
    import org.apache.http.conn.ssl.SSLSocketFactory;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.apache.http.params.HttpParams;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.security.*;
    
    public class MyHttpClient extends DefaultHttpClient {
    
        private static Context appContext = null;
        private static HttpParams params = null;
        private static SchemeRegistry schmReg = null;
        private static Scheme httpsScheme = null;
        private static Scheme httpScheme = null;
        private static String TAG = "MyHttpClient";
    
        public MyHttpClient(Context myContext) {
    
            appContext = myContext;
    
            if (httpScheme == null || httpsScheme == null) {
                httpScheme = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
                httpsScheme = new Scheme("https", mySSLSocketFactory(), 443);
            }
    
            getConnectionManager().getSchemeRegistry().register(httpScheme);
            getConnectionManager().getSchemeRegistry().register(httpsScheme);
    
        }
    
        private SSLSocketFactory mySSLSocketFactory() {
            SSLSocketFactory ret = null;
            try {
                final KeyStore ks = KeyStore.getInstance("BKS");
    
                final InputStream inputStream = appContext.getResources().openRawResource(R.raw.certs);
    
                ks.load(inputStream, appContext.getString(R.string.store_pass).toCharArray());
                inputStream.close();
    
                ret = new SSLSocketFactory(ks);
            } catch (UnrecoverableKeyException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (KeyStoreException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (KeyManagementException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (NoSuchAlgorithmException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (IOException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (Exception ex) {
                Log.d(TAG, ex.getMessage());
            } finally {
                return ret;
            }
        }
    }
    

现在只需使用一个实例**MyHttpClient****DefaultHttpClient**进行 HTTPS 查询,它将正确使用和验证您的自签名 SSL 证书。

HttpResponse httpResponse;

HttpPost httpQuery = new HttpPost("https://yourserver.com");
... set up your query ...

MyHttpClient myClient = new MyHttpClient(myContext);

try{

    httpResponse = myClient.(peticionHttp);

    // Check for 200 OK code
    if (httpResponse.getStatusLine().getStatusCode() == HttpURLConnection.HTTP_OK) {
        ... do whatever you want with your response ...
    }

}catch (Exception ex){
    Log.d("httpError", ex.getMessage());
}
于 2013-03-20T18:09:10.790 回答
15

除非我错过了什么,否则此页面上的其他答案是危险的,并且在功能上等同于根本不使用 SSL。如果您信任自签名证书而不进行进一步检查以确保证书是您期望的证书,那么任何人都可以创建自签名证书并可以伪装成您的服务器。那时,你没有真正的安全感。

唯一合法的方法(无需编写完整的 SSL 堆栈)是在证书验证过程中添加一个额外的受信任锚点。两者都涉及将受信任的锚点证书硬编码到您的应用程序中,并将其添加到操作系统提供的任何受信任的锚点中(否则,如果您获得真正的证书,您将无法连接到您的站点)。

我知道有两种方法可以做到这一点:

  1. 按照http://www.ibm.com/developerworks/java/library/j-customssl/#8中的描述创建自定义信任库

  2. 创建 X509TrustManager 的自定义实例并覆盖 getAcceptedIssuers 方法以返回包含您的证书的数组:

    public X509Certificate[] getAcceptedIssuers()
    {
        X509Certificate[] trustedAnchors =
            super.getAcceptedIssuers();
    
        /* Create a new array with room for an additional trusted certificate. */
        X509Certificate[] myTrustedAnchors = new X509Certificate[trustedAnchors.length + 1];
        System.arraycopy(trustedAnchors, 0, myTrustedAnchors, 0, trustedAnchors.length);  
    
        /* Load your certificate.
    
           Thanks to http://stackoverflow.com/questions/11857417/x509trustmanager-override-without-allowing-all-certs
           for this bit.
         */
        InputStream inStream = new FileInputStream("fileName-of-cert");
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream);
        inStream.close();
    
        /* Add your anchor cert as the last item in the array. */
        myTrustedAnchors[trustedAnchors.length] = cert;
    
        return myTrustedAnchors;
    }
    

请注意,此代码完全未经测试,甚至可能无法编译,但至少应该引导您朝着正确的方向前进。

于 2012-10-20T18:14:07.293 回答
4

如果您按如下方式修改更大的 createSocket 方法重载,Brian Yager 的答案也适用于 Android 2.2。我花了一段时间才让自签名 SSL 工作。

public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
    return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
}
于 2010-11-04T20:48:06.967 回答
0

在 Android 上,HttpProtocolParams接受ProtocolVersion而不是HttpVersion.

ProtocolVersion pv = new ProtocolVersion("HTTP", 1, 1);
HttpProtocolParams.setVersion(params, pv);
于 2011-05-26T16:53:47.950 回答
0

@Chris - 将其发布为答案,因为我(还)无法添加评论。我想知道您的方法是否应该在使用 webView 时起作用。我在 Android 2.3 上无法做到这一点 - 相反,我只是得到一个白屏。

经过更多搜索后,我发现了这个在 webView 中处理 SSL 错误的简单修复程序,这对我来说就像一个魅力。

在处理程序中,我检查我是否处于特殊的开发模式并调用 handler.proceed(),否则我调用 handler.cancel()。这使我可以在本地网站上针对自签名证书进行开发。

于 2013-01-09T00:57:09.587 回答
0

这个用例有很多选择。如果您不想在代码库中包含任何自定义代码,例如自定义,TrustManager我建议您尝试GitHub - SSLContext Kickstart和以下代码片段:

<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>sslcontext-kickstart</artifactId>
    <version>6.7.0</version>
</dependency>

SSL 配置

SSLFactory sslFactory = SSLFactory.builder()
    .withUnsafeTrustMaterial()
    .build();

SSLContext sslContext = sslFactory.getSslContext();
SSLSocketFactory sslSocketFactory = sslFactory.getSslSocketFactory();

HttpClient 配置

HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);

SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("https", sslSocketFactory, 443));

ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);

HttpClient httpClient = new DefaultHttpClient(ccm, params);

HttpsUrlConnection

HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory); 
于 2021-07-13T20:28:51.913 回答