在Android中使用HTTPS的场景比较频繁,所以对于HTTPS的证书应该如何校验呢?关于HTTPS的校验原理可以参考我之前写的一篇文章:《 HTTPS协议实现原理 》,相信看完后应该对HTTPS有一个比较大致的了解。而且对HTTP(s)请求的工具进行了封装,需要体会这种封装工具类的思路,也就是编码中常见的Listener机制。然后是Android中TCP、UDP通信的例子,主要是把Android设备作为Client端,如果对Java的Socket编程比较熟悉的话,这些都是特别简单的示例程序,非常容易看懂。

TCP/UDP 简单示例

下面的例子演示了Client向Server发送了一串小写英文,Server返回大写字符串的功能:

UDPServer.java:

public class UDPServer {    private static final SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");    public static void main(String[] args) throws Exception {        DatagramSocket datagramSocket;        datagramSocket = new DatagramSocket(8090);        byte[] buf;        DatagramPacket packet;        while (true){            buf = new byte[1024];            packet = new DatagramPacket(buf, buf.length);            datagramSocket.receive(packet);            String content = new String(packet.getData());            InetAddress address = packet.getAddress();            System.out.println(format.format(new Date()) + "-" + address + "-" + content);            int port = packet.getPort();            String replyContent = content.toUpperCase();            byte[] sendData = replyContent.getBytes();            DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, address, port);            datagramSocket.send(sendPacket);        }    }}

UDPClient.java:

public class UDPClient {    private static final SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");    public static void main(String[] args) throws Exception {        System.out.println("请输入一句英文,服务器会返回其大写形式[exit退出]");        Scanner scanner = new Scanner(System.in);        InetAddress address = InetAddress.getLocalHost();        DatagramPacket packet;        DatagramSocket socket = new DatagramSocket();        while(true){            String line = scanner.nextLine();            if("exit".equals(line)) break;            byte[] bytes = line.getBytes();            packet = new DatagramPacket(bytes, bytes.length, address, 8090);            socket.send(packet);            byte[] recvBuf = new byte[1024];            DatagramPacket recvPacket = new DatagramPacket(recvBuf, recvBuf.length);            socket.receive(recvPacket);            System.out.println(format.format(new Date()) + "-" + address + "-" + new String(recvBuf));        }        socket.close();    }}

TCPServer.java:

public class TCPServer {    static SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");    public static void main(String[] args) throws IOException {        ServerSocket serverSocket = new ServerSocket(9090);        while (true){            Socket socket = serverSocket.accept();            InetAddress address = socket.getInetAddress();            InputStream is = socket.getInputStream();            byte[] readBuf = new byte[1024];            try{                int len = is.read(readBuf);                String recv = new String(readBuf, 0, len);                System.out.println(format.format(new Date()) + "-" + address + "-" + recv);                OutputStream os = socket.getOutputStream();                os.write(recv.toUpperCase().getBytes());            } catch (SocketException e){                System.err.println("客户端未发送信息");            } finally {                socket.close();            }        }    }}

TCPClient.java:

public class TCPClient {    private static final SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");    public static void main(String[] args) throws Exception {        System.out.println("请输入一句英文,服务器会返回其大写形式[exit退出]");        Scanner scanner = new Scanner(System.in);        while(true){            Socket socket = new Socket("127.0.0.1", 9090);            String line = scanner.nextLine();            if("exit".equals(line)) break;            OutputStream os = socket.getOutputStream();            os.write(line.getBytes());            InputStream is = socket.getInputStream();            byte[] readBuf = new byte[1024];            String recv = new String(readBuf, 0, is.read(readBuf));            InetAddress address = socket.getInetAddress();            System.out.println(format.format(new Date()) + "-" + address + "-" + recv);            socket.close();        }    }}

Client移植到Android

将两个Client移植到Android:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>                                

MainActivity.java:

public class MainActivity extends AppCompatActivity {    private static final String TAG = "MainActivity";    private static final SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss", Locale.CHINA);    private EditText etInput;    private TextView textView;    private EditText udpServerET;    private EditText tcpServerET;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        etInput = findViewById(R.id.et_content);        textView = findViewById(R.id.tv_show);        udpServerET = findViewById(R.id.et_udp_server);        tcpServerET = findViewById(R.id.et_tcp_server);    }    public void sendTcpMessage(View view) {        String[] tcpInfo = tcpServerET.getText().toString().split(":");        String inputContent = etInput.getText().toString();        new Thread(()->{            try (Socket socket = new Socket(tcpInfo[0], Integer.parseInt(tcpInfo[1]))){                OutputStream os = socket.getOutputStream();                os.write(inputContent.getBytes());                InputStream is = socket.getInputStream();                byte[] readBuf = new byte[1024];                String recv = new String(readBuf, 0, is.read(readBuf));                InetAddress address = socket.getInetAddress();                String ret = String.format("%s-%s-%s", df.format(new Date()), address, recv);                runOnUiThread(()-> textView.setText(ret));            }catch (IOException e){                Log.e(TAG, "sendTcpMessage: Error!");            }        }).start();    }    public void sendUdpMessage(View view) {        String[] udpInfo = udpServerET.getText().toString().split(":");        String inputContent = etInput.getText().toString();        new Thread(()->{            try {                DatagramSocket socket = new DatagramSocket();                byte[] bytes = inputContent.getBytes();                InetAddress address = InetAddress.getByName(udpInfo[0]);                int serverPort = Integer.parseInt(udpInfo[1]);                DatagramPacket packet = new DatagramPacket(bytes, bytes.length, address, serverPort);                socket.send(packet);                byte[] recvBuf = new byte[1024];                DatagramPacket recvPacket = new DatagramPacket(recvBuf, recvBuf.length);                socket.receive(recvPacket);                String ret = String.format("%s-%s-%s", df.format(new Date()), address, new String(recvBuf));                runOnUiThread(()-> textView.setText(ret));            }catch (IOException e){                Log.e(TAG, "sendUdpMessage: Error!");            }        }).start();    }}

AndroidManifest.xml:

注意点:1、网络访问权限 2、子线程代码中使用runOnUiThread()方法可更新UI

Android访问HTTPS

对于一个普通的HTTP请求,我们可以使用如下方式来发起请求,下面是一个简易的Http请求工具类:

public class HttpUtils {    private static Handler mUIHandler = new Handler(Looper.getMainLooper());    interface HttpListener {        void onSuccess(String content);        void onFail(Exception e);    }    public static void doGet(String urlStr, HttpListener listener) {        new Thread(() -> {            Looper.prepare();            try {                URL url = new URL(urlStr);                HttpURLConnection conn = (HttpURLConnection) url.openConnection();                conn.setRequestMethod("GET");                conn.setConnectTimeout(5000);                conn.setReadTimeout(5000);                conn.connect();                try (InputStream is = conn.getInputStream();                     InputStreamReader reader = new InputStreamReader(is)                ) {                    char[] buf = new char[4096];                    int len;                    StringBuilder sb = new StringBuilder();                    while ((len = reader.read(buf)) != -1) {                        sb.append(new String(buf, 0, len));                    }                    mUIHandler.post(() -> listener.onSuccess(sb.toString()));                } catch (IOException e) {                    e.printStackTrace();                    listener.onFail(e);                }            }catch (IOException e){                e.printStackTrace();                listener.onFail(e);            }        }).start();    }}

1、不校验证书(不推荐)

MyX509TrustManager.java,MyX509TrustManager实现不做任何事情:

...import java.security.cert.CertificateException;import java.security.cert.X509Certificate;import javax.net.ssl.X509TrustManager;public class MyX509TrustManager implements X509TrustManager {    @Override    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {        // TODO...    }    @Override    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {        // TODO...    }    @Override    public X509Certificate[] getAcceptedIssuers() {        return new X509Certificate[0];    }}

HttpsUtils.java

...public class HttpsUtils {    private static Handler mUIHandler = new Handler(Looper.getMainLooper());    interface HttpListener {        void onSuccess(String content);        void onFail(Exception e);    }    public static void doGet(Context context, String urlStr, HttpListener listener) {        new Thread(() -> {            Looper.prepare();            try {                URL url = new URL(urlStr);                HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();                SSLContext sslContext = SSLContext.getInstance("TLS");                // 放入自定义的MyX509TrustManager对象即可                TrustManager[] trustManagers = {new MyX509TrustManager()};                sslContext.init(null, trustManagers, new SecureRandom());                conn.setSSLSocketFactory(sslContext.getSocketFactory());                conn.setRequestMethod("GET");                conn.setConnectTimeout(5000);                conn.setReadTimeout(5000);                conn.connect();                try (InputStream is = conn.getInputStream();                     InputStreamReader reader = new InputStreamReader(is)                ) {                    char[] buf = new char[4096];                    int len;                    StringBuilder sb = new StringBuilder();                    while ((len = reader.read(buf)) != -1) {                        sb.append(new String(buf, 0, len));                    }                    mUIHandler.post(() -> listener.onSuccess(sb.toString()));                } catch (IOException e) {                    e.printStackTrace();                    listener.onFail(e);                }            }catch (Exception e){                e.printStackTrace();                listener.onFail(e);            }        }).start();    }}

2、校验证书(推荐)

拿我自己的博客站点来说,想要获得证书只需要在浏览器下载对应的证书即可(选择DER编码二进制和Base64编码均可),保存了一个名为srca.cer的文件到桌面:

将这份证书文件复制到项目的src/main/assets/目录下,没有assets就新建,所以完整路径为src/main/assets/srca.cer。

接下来需要实现MyX509TrustManager.java中的方法:

public class MyX509TrustManager implements X509TrustManager {    private static final String TAG = "MyX509TrustManager";    // 证书对象    private X509Certificate serverCert;    public MyX509TrustManager(X509Certificate serverCert) {        this.serverCert = serverCert;    }    @Override    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {    }    @Override    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {        // 遍历证书        for (X509Certificate certificate: chain){            // 校验合法性与是否过期            certificate.checkValidity();            try {                // 校验公钥                PublicKey publicKey = serverCert.getPublicKey();                certificate.verify(publicKey);            } catch (Exception e) {                throw new CertificateException(e);            }        }    }    @Override    public X509Certificate[] getAcceptedIssuers() {        return new X509Certificate[0];    }}

同时,将使用keyStore这个API来获取TrustManager数组,HttpsUtils.java如下:

public class Https2Utils {    private static Handler mUIHandler = new Handler(Looper.getMainLooper());    interface HttpListener {        void onSuccess(String content);        void onFail(Exception e);    }    public static void doGet(Context context, String urlStr, HttpListener listener) {        new Thread(() -> {            Looper.prepare();            try {                URL url = new URL(urlStr);                HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();                SSLContext sslContext = SSLContext.getInstance("TLS");                X509Certificate serverCert = getCert(context);                String defaultType = KeyStore.getDefaultType();                KeyStore keyStore = KeyStore.getInstance(defaultType);                keyStore.load(null);                // 别名、证书                keyStore.setCertificateEntry("srca", serverCert);                String algorithm = TrustManagerFactory.getDefaultAlgorithm();                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(algorithm);                trustManagerFactory.init(keyStore);                TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();                sslContext.init(null, trustManagers, new SecureRandom());                conn.setSSLSocketFactory(sslContext.getSocketFactory());                // 校验域名是否合法                conn.setHostnameVerifier((hostname, session) -> {                    HostnameVerifier verifier = HttpsURLConnection.getDefaultHostnameVerifier();                    return verifier.verify("zouchanglin.cn", session);                });                conn.setRequestMethod("GET");                conn.setConnectTimeout(5000);                conn.setReadTimeout(5000);                conn.connect();                try (InputStream is = conn.getInputStream();                     InputStreamReader reader = new InputStreamReader(is)                ) {                    char[] buf = new char[4096];                    int len;                    StringBuilder sb = new StringBuilder();                    while ((len = reader.read(buf)) != -1) {                        sb.append(new String(buf, 0, len));                    }                    mUIHandler.post(() -> listener.onSuccess(sb.toString()));                } catch (IOException e) {                    e.printStackTrace();                    listener.onFail(e);                }            }catch (Exception e){                e.printStackTrace();                listener.onFail(e);            }        }).start();    }    private static X509Certificate getCert(Context context) {        try {            // src/main/assets/srca.cer            InputStream inputStream = context.getAssets().open("srca.cer");            CertificateFactory factory = CertificateFactory.getInstance("X.509");            return (X509Certificate) factory.generateCertificate(inputStream);        } catch (IOException | CertificateException e) {            e.printStackTrace();        }        return null;    }}

在MainActivity中使用也很简单:

public class MainActivity extends AppCompatActivity {    private EditText etUrl;    private TextView tvShow;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        etUrl = findViewById(R.id.et_url);        tvShow = findViewById(R.id.tv_show);    }    public void loadContent(View view) {        String url = etUrl.getText().toString();        Https2Utils.doGet(this, url, new Https2Utils.HttpListener() {            @Override            public void onSuccess(String content) {                tvShow.setText(content);            }            @Override            public void onFail(Exception e) {                Toast.makeText(MainActivity.this, "Failed!", Toast.LENGTH_SHORT).show();            }        });    }}

原文地址 《Android Socket与HTTPS校验》

更多相关文章

  1. android辅助开发工具包介绍
  2. Android - 利用Android studio + Android Killer工具在手机未ROO
  3. Android实用视图动画及工具系列之四:多状态CheckBox,可设置大小尺
  4. Android性能测试 一些适用于Android Studio的代码审查和性能测试
  5. 实现Android播放声音资源的一个简单的工具类
  6. Android的界面设计工具——DroidDraw
  7. Android程序版本控制工具类
  8. Android aapt 工具介绍
  9. Android JNI开发工具篇(1)-开发环境搭建

随机推荐

  1. Android中集结了大量的系统管家Manager
  2. Android 代码实现应用强制装到手机内存
  3. android studio查看模拟器文件夹
  4. Android 对象序列化之你不知道的 Seriali
  5. [Android随笔]Android参考书籍
  6. Android:控件属性
  7. Android图标的制作
  8. Android Studio 自动更新失败解决方法
  9. 由Android想到的事情
  10. Android下的PVPlayer的实现