Preface
Mobile devices use a variety of network environments and are often connected to insecure public WIFI-if you are not secure in public WIFI environments, it is no wonder that you are developing insecure programs and putting your users in danger-this is not an exaggeration.
To securely use the network in an insecure network environment, the best way is to connect to the secure network environment through VPN. But this is not always guaranteed. Therefore, application developers should minimize user security risks during development.
Using HTTPS to connect to a network is a common method. However, there are several difficulties in actual use:
* Cost of using commercial certificates
* Using a custom certificate is not recognized by the System
* Ignoring Certificate verification may cause man-in-the-middle attacks"
This article will discuss technical solutions to these problems.
Because we have recently started to use Android Studio again, the Demo here is written in Android Studio 0.4.2. Complete Demo code is available in bitbucket: https://bitbucket.org/raptorz/democert
Basic HTTP Connection Methods
First, let's look at the implementation of the basic HTTP connection method. The project framework of the program is directly generated using the wizard and then slightly modified. An Asynchronous Network call task is added. The task content is roughly as follows:
HttpUriRequest request = new HttpGet(url);HttpClient client = DemoHttp.getClient();try { HttpResponse httpResponse = client.execute(request); int responseCode = httpResponse.getStatusLine().getStatusCode(); String message = httpResponse.getStatusLine().getReasonPhrase(); HttpEntity entity = httpResponse.getEntity(); if (responseCode == 200 && entity != null) { return parseString(entity); }}finally { client.getConnectionManager().shutdown();}return "";
The above function is to create an HttpClient to GET url content, if the HTTP return value is 200 (that is, no error), then return the response content.
The focus is on DemoHttp. getClient (). The following code implements basic HTTP connections:
public static HttpClient getClient() { BasicHttpParams params = new BasicHttpParams(); HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); HttpProtocolParams.setContentCharset(params, HTTP.DEFAULT_CONTENT_CHARSET); HttpProtocolParams.setUseExpectContinue(params, true); SchemeRegistry schReg = new SchemeRegistry(); schReg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); ClientConnectionManager connMgr = new ThreadSafeClientConnManager(params, schReg); return new DefaultHttpClient(connMgr, params);}
Of course, the actual implementation code is much more than the above two sections. Java is so troublesome, and a lot of code should be written for every small matter. In order to save space, it is not all listed, see the complete code on the bitbucket.
By the way, do not forget to add relevant permissions to Manifest. xml for network communication applications. Otherwise, some strange errors may occur.
The main problem with HTTP connection is that the content in the transmission process is in plain text. As long as you use a sniffer program within the same network segment, the communication content between other devices and servers in the network is completely insecure.
Use the HTTPS connection method of the business certificate recognized by the System
In the preceding example, if an https connection is attempted, an error is returned, indicating that https: Scheme 'https' not registered is not supported. The simplest solution is to add HTTPS support by referring to the HTTP method:
schReg.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
The key code is like this.
Now you can open a valid HTTPS connection like opening an HTTP link, such as a https://www.google.com.hk. However, the shameful 12306 HTTPS does not work because it uses a custom certificate that is not recognized by the system: No peer certificate.
This scheme uses an HTTPS connection. The transmitted content is encrypted and the sniffer program cannot obtain the communication content. The server certificate has been legally signed and recognized by the system, so there is no problem with normal communication.
However, you need to pay for the certificate.
Use a custom certificate and ignore the verified HTTPS connection method
If you don't want to spend money, you can only use OPENSSL to make a certificate by yourself, but the problem is that this certificate cannot be recognized by the system, and the consequences are 12306. To solve this problem, one way is to skip system verification.
To skip the system verification, you can no longer use the system-standard SSLSocketFactory. You need to customize one. Then, in order to skip the verification in the Custom SSLSocketFactory, you also need to customize a TrustManager to ignore all verifications, that is, TrustAll.
The implementation of SSLTrustAllSocketFactory and SSLTrustAllManager is as follows:
public class SSLTrustAllSocketFactory extends SSLSocketFactory { private static final String TAG = "SSLTrustAllSocketFactory"; private SSLContext mCtx; public class SSLTrustAllManager implements X509TrustManager { @Override public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return null; } } public SSLTrustAllSocketFactory(KeyStore truststore) throws Throwable { super(truststore); try { mCtx = SSLContext.getInstance("TLS"); mCtx.init(null, new TrustManager[] { new SSLTrustAllManager() }, null); setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); } catch (Exception ex) { } } @Override public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { return mCtx.getSocketFactory().createSocket(socket, host, port, autoClose); } @Override public Socket createSocket() throws IOException { return mCtx.getSocketFactory().createSocket(); } public static SSLSocketFactory getSocketFactory() { try { KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); trustStore.load(null, null); SSLSocketFactory factory = new SSLTrustAllSocketFactory(trustStore); return factory; } catch (Throwable e) { Log.d(TAG, e.getMessage()); e.printStackTrace(); } return null; }}
Finally, use this Factory when registering scheme:
schReg.register(new Scheme("https", SSLTrustAllSocketFactory.getSocketFactory(), 443));
In this way, the 12306 content can be successfully opened.
However, although this scheme uses HTTPS, the communication content is encrypted, and the sniffing program cannot know the content. However, through more troublesome "man-in-the-middle attacks", we can still steal communication content:
Configure a DNS in the network, resolve the domain name of the target server to a local address, and then use an intermediate server as the proxy on this address. It uses a fake certificate to communicate with the client, then, the proxy is used as the client to connect to the actual server and communicate with the server using the real certificate. In this way, all the communication content will go through this proxy. Because the client does not verify the certificate, the certificate used for encryption is actually a fake certificate provided by the proxy, so the proxy can fully understand the communication content. This proxy is the so-called "man-in-the-middle ".
However, unfortunately, most HTTPS connections to custom certificates found on the Internet are implemented using this method of ignore verification.
Therefore, we need a safer approach. For more information, see the next section.