Android上的自签名SSL接受

如何在Android上使用Java接受自签名证书?

代码示例将是完美的。

我已经在互联网上到处寻找,而有些人声称已经找到了解决方案,它要么不工作,要么没有示例代码来备份它。

我有这个功能exchangeIt,它通过WebDav连接到微软交换。 这里有一些代码来创建一个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在这里 。

交换的代码它是开源的,如果你有任何问题,在这里 googlecode托管。 我没有积极的工作,但代码应该工作。

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

正如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(); } } 

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

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

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

  1. 创建本地KeyStore

    为了使您的应用能够验证您的自签名证书,您需要以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包含在您的应用程序中

将您新创建的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; } } } 

现在只需像使用**DefaultHttpClient** **MyHttpClient**那样使用**MyHttpClient**的实例来进行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()); } 

除非我错过了一些东西,否则本页面上的其他答案是危险的,在功能上等同于根本不使用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; } 

请注意,这段代码是完全未经测试的,甚至可能不会编译,但至少应该引导您朝正确的方向发展。

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

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

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

 ProtocolVersion pv = new ProtocolVersion("HTTP", 1, 1); HttpProtocolParams.setVersion(params, pv); 

@Chris – 发布这个作为答案,因为我不能添加评论(还)。 我想知道如果你的方法是应该在使用webView时工作。 我不能在Android 2.3上这样做 – 而只是得到一个白色的屏幕。

经过一些更多的搜索,我遇到了这个简单的修复处理SSL错误在一个webView中工作像我的魅力。

在处理程序中,我检查是否处于特殊的开发模式,并调用handler.proceed(),否则我调用handler.cancel()。 这使我能够在本地网站上进行自签证明的开发工作。