Android 连接MQTT SSL/TLS协议 单向加密

网上很多文章都说android仅支持BKS格式的证书 ,要使用keytool装换CRT成为BKS格式,按照步骤试了一下,乱七八糟的,也还是没有成功,最后还是找到靠谱的方法,Android支持CRT!Android支持CRT!Android支持CRT!

下面就按照步骤做,不出意外的话,会成功的。至于CRT哪来的,去问问搞服务器的搭档。

代码是从项目中摘出来的,可能有些地方会报错

最关键的是 MySocketFactory.class 和 MQTTManager.class 两个类

其他的代码可以根据自己的实际情况调用

 

 

1.引入依赖 我用的是paho的MQTT

implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0'
implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'

2.在AndroidManifest.xml文件注册service ,如果没有用Service管理连接只要下面一行,否则还添加自己的Service,基础知识,不解释

<service android:name="org.eclipse.paho.android.service.MqttService" />

3.创建  MySocketFactory.class

import android.util.Log;

import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Enumeration;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

public class MySocketFactory extends SSLSocketFactory {
    private SSLSocketFactory factory;

    public static class SocketFactoryOptions {

        private InputStream caCrtInputStream;
        private InputStream caClientP12InputStream;
        private String caClientP12Password;

        public SocketFactoryOptions withCaInputStream(InputStream stream) {
            this.caCrtInputStream = stream;
            return this;
        }
        public SocketFactoryOptions withClientP12InputStream(InputStream stream) {
            this.caClientP12InputStream = stream;
            return this;
        }
        public SocketFactoryOptions withClientP12Password(String password) {
            this.caClientP12Password = password;
            return this;
        }

        public boolean hasCaCrt() {
            return caCrtInputStream != null;
        }

        public boolean hasClientP12Crt() {
            return caClientP12Password != null;
        }

        public InputStream getCaCrtInputStream() {
            return caCrtInputStream;
        }

        public InputStream getCaClientP12InputStream() {
            return caClientP12InputStream;
        }

        public String getCaClientP12Password() {
            return caClientP12Password;
        }

        public boolean hasClientP12Password() {
            return (caClientP12Password != null) && !caClientP12Password.equals("");
        }
    }
    public MySocketFactory() throws CertificateException, KeyStoreException, NoSuchAlgorithmException, IOException, KeyManagementException, CertificateException, UnrecoverableKeyException {
        this(new SocketFactoryOptions());
    }


    private TrustManagerFactory tmf;

    public MySocketFactory(SocketFactoryOptions options) throws KeyStoreException, NoSuchAlgorithmException, IOException, KeyManagementException, CertificateException, UnrecoverableKeyException {
        Log.v(this.toString(), "initializing CustomSocketFactory");

        tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");


        if(options.hasCaCrt()) {
            Log.v(this.toString(), "MQTT_CONNECTION_OPTIONS.hasCaCrt(): true");

            KeyStore caKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            caKeyStore.load(null, null);

            CertificateFactory caCF = CertificateFactory.getInstance("X.509");
            X509Certificate ca = (X509Certificate) caCF.generateCertificate(options.getCaCrtInputStream());
            String alias = ca.getSubjectX500Principal().getName();
            // Set propper alias name
            caKeyStore.setCertificateEntry(alias, ca);
            tmf.init(caKeyStore);

            Enumeration<String> aliasesCA = caKeyStore.aliases();
            for (; aliasesCA.hasMoreElements(); ) {
                String o = aliasesCA.nextElement();
            }



        } else {
//            Timber.v("CA sideload: false, using system keystore");
            KeyStore keyStore = KeyStore.getInstance("AndroidCAStore");
            keyStore.load(null);
            tmf.init(keyStore);
        }

        if (options.hasClientP12Crt()) {
            Log.v(this.toString(), "MQTT_CONNECTION_OPTIONS.hasClientP12Crt(): true");

            KeyStore clientKeyStore = KeyStore.getInstance("PKCS12");

            clientKeyStore.load(options.getCaClientP12InputStream(), options.hasClientP12Password() ? options.getCaClientP12Password().toCharArray() : new char[0]);
            kmf.init(clientKeyStore, options.hasClientP12Password() ? options.getCaClientP12Password().toCharArray() : new char[0]);

            Log.v(this.toString(), "Client .p12 Keystore content: ");
            Enumeration<String> aliasesClientCert = clientKeyStore.aliases();
            for (; aliasesClientCert.hasMoreElements(); ) {
                String o = aliasesClientCert.nextElement();
//                Timber.v("Alias: %s", o);
            }
        } else {
            Log.v(this.toString(), "Client .p12 sideload: false, using null CLIENT cert");
            kmf.init(null,null);
        }

        // Create an SSLContext that uses our TrustManager
        SSLContext context = SSLContext.getInstance("TLSv1.2");
        context.init(kmf.getKeyManagers(), getTrustManagers(), null);
        this.factory= getSSLSocketFactory();

    }

    public TrustManager[] getTrustManagers() {
        return tmf.getTrustManagers();
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return this.factory.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return this.factory.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket() throws IOException {
        SSLSocket r = (SSLSocket)this.factory.createSocket();
        r.setEnabledProtocols(new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"});
        return r;
    }

    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
        SSLSocket r = (SSLSocket)this.factory.createSocket(s, host, port, autoClose);
        r.setEnabledProtocols(new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"});
        return r;
    }

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

        SSLSocket r = (SSLSocket)this.factory.createSocket(host, port);
        r.setEnabledProtocols(new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"});
        return r;
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
        SSLSocket r = (SSLSocket)this.factory.createSocket(host, port, localHost, localPort);
        r.setEnabledProtocols(new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"});
        return r;
    }

    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
        SSLSocket r = (SSLSocket)this.factory.createSocket(host, port);
        r.setEnabledProtocols(new String[]{ "TLSv1", "TLSv1.1", "TLSv1.2"});
        return r;
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
        SSLSocket r = (SSLSocket)this.factory.createSocket(address, port, localAddress,localPort);
        r.setEnabledProtocols(new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"});
        return r;
    }
 private static SSLSocketFactory getSSLSocketFactory() {
        try {
            // Create a trust manager that does not validate certificate chains
            final TrustManager[] trustAllCerts = new TrustManager[]{
                    new X509TrustManager() {
                        @Override
                        public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
                        }

                        @Override
                        public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
                        }

                        @Override
                        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                            return new java.security.cert.X509Certificate[]{};
                        }
                    }
            };

            // Install the all-trusting trust manager
            final SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
            // Create an ssl socket factory with our all-trusting manager
            final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

            return sslSocketFactory;
        } catch (KeyManagementException | NoSuchAlgorithmException e) {
            return null;
        }

    }
}

4.创建  MQTTManager 用于管理MQTT的 连接 订阅 发布

先把自己的crt证书放到raw文件夹下

 

 

 

 

 

 

import android.content.Context;


import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;


import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;


public class MQTTManager {
    private static final String TAG = "MQTTManager";
    //换上你的主机地址
    public static final String SERVER_HOST = "ssl://XXXXXXXXXXX:8883";
    //设置的 clientid 可以自己生成
    private String clientid = "xxxxxxx";
    private static MQTTManager mqttManager=null;
    private MqttClient client;
    private MqttConnectOptions options;

    private Context context;

    private MQTTManager(Context context){
        this.context=context;
    }
    /**
     * 获取一个MQTTManager单例
     * @param
     * @return 返回一个MQTTManager的实例对象
     */
    public static MQTTManager getInstance(Context context) {
        LogUtils.d("mqttManager="+mqttManager);
        if (mqttManager==null) {
            mqttManager=new MQTTManager(context);
            synchronized (Object.class) {
                LogUtils.d("synchronized mqttManager="+mqttManager);
                if (mqttManager!=null) {
                    return mqttManager;
                }
            }
        }else {
            LogUtils.d("else mqttManager="+mqttManager);
            return mqttManager;
        }
        return null;
    }

    /**
     * 连接服务器
     */
    public void connect(){
        LogUtils.d("开始连接MQtt");
        try {
            // host为主机名,clientid即连接MQTT的客户端ID,一般以唯一标识符表示,MemoryPersistence设置clientid的保存形式,默认为以内存保存
            if(client==null){
                client = new MqttClient(SERVER_HOST, clientid, new MemoryPersistence());
            }

            // MQTT的连接设置
            options = new MqttConnectOptions();
            // 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,这里设置为true表示每次连接到服务器都以新的身份连接
              options.setCleanSession(false);
            // 设置连接的用户名
            options.setUserName(你的用户名);
            // 设置连接的密码
            options.setPassword(你的密码.toCharArray());
            // 设置超时时间 单位为秒
            options.setConnectionTimeout(30);
            // 设置会话心跳时间 单位为秒 服务器会每隔1.5*20秒的时间向客户端发送个消息判断客户端是否在线,但这个方法并没有重连的机制
            options.setKeepAliveInterval(30);
            options.setAutomaticReconnect(true);
            if (SERVER_HOST.contains("ssl")) {
                MySocketFactory.SocketFactoryOptions socketFactoryOptions = new MySocketFactory.SocketFactoryOptions();
                try {
                    socketFactoryOptions.withCaInputStream(getResources().openRawResource(R.raw.ca));
                    options.setSocketFactory(new MySocketFactory(socketFactoryOptions));
                } catch (IOException | NoSuchAlgorithmException | KeyStoreException | CertificateException | KeyManagementException | UnrecoverableKeyException e) {
                    e.printStackTrace();
                }
            }
            // 设置回调
//              MqttTopic topic = client.getTopic(TOPIC);
            //setWill方法,如果项目中需要知道客户端是否掉线可以调用该方法。设置最终端口的通知消息
//              options.setWill(topic, "close".getBytes(), 2, true);
            client.setCallback(new PushCallback());
            client.connect(options);
            LogUtils.d("ClientId="+client.getClientId());
        } catch (MqttException e) {
            LogUtils.d( "connect: " + e );
            e.printStackTrace();
        }
    }

    /**
     * 订阅消息
     * @param topic 订阅消息的主题
     */
        public void subscribeMsg(String topic,int qos){
            if (client!=null) {
                int[] Qos  = {qos};
                String[] topic1 = {topic};
                try {
                    client.subscribe(topic1, Qos);
                    LogUtils.d("开始订阅topic= "+topic);
                } catch (MqttException e) {
                    e.printStackTrace();
                }
            }
        }


    public void subscribeMsgArray(String topic[],int qos[]){
        if (client!=null) {

            try {
                client.subscribe(topic, qos);
                LogUtils.d("开始订阅topic= "+topic);
            } catch (MqttException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 发布消息
     * @param topic 发布消息主题
     * @param msg 消息体
     * @param isRetained 是否为保留消息
     */
    public void publish(String topic,String msg,boolean isRetained,int qos) {
        try {
            if (client!=null) {
                MqttMessage message = new MqttMessage();
                message.setQos(qos);
                message.setRetained(isRetained);
                message.setPayload(msg.getBytes());
                client.publish(topic, message);
                LogUtils.d("topic="+topic+"--msg="+msg+"--isRetained"+isRetained);
            }
        } catch (MqttPersistenceException e) {
            e.printStackTrace();
        } catch (MqttException e) {
            e.printStackTrace();
        }
    }

    public void unsubcribe(String topic[]){
        if(client!=null){
            try {
                client.unsubscribe(topic);
            } catch (MqttException e) {
                e.printStackTrace();
            }
        }
    }



    /**
     * 发布和订阅消息的回调
     *
     */
    public class PushCallback implements MqttCallbackExtended {
        @Override
        public void connectionLost(Throwable cause) {
            LogUtils.d("断线了 ");

        }
        /**
         * 发布消息的回调
         */
        @Override
        public void deliveryComplete(IMqttDeliveryToken token) {
            //publish后会执行到这里
            LogUtils.d("发布消息成功的回调 "+token.isComplete());
        }

        /**
         * 接收消息的回调方法
         */
        @Override
        public void messageArrived(final String topicName, final MqttMessage message)
                throws Exception {
            //subscribe后得到的消息会执行到这里面
            LogUtils.d("接收TOPIC息==" + topicName);
            LogUtils.d("接收消息==" + new String(message.getPayload()));

            MyMqttMessage myMqttMessage=new MyMqttMessage();
            myMqttMessage.setTopic(topicName);
            myMqttMessage.setMsgContent(new String(message.getPayload()));
            EventBus.getDefault().post(myMqttMessage);

        }

        @Override
        public void connectComplete(boolean reconnect, String serverURI) {
            if(reconnect){
                LogUtils.d("已重连");

            }
        }
    }
    
    

    /**
     * 断开链接
     */
    public void disconnect(){
        if (client!=null&&client.isConnected()) {
            try {
                client.disconnect();
                mqttManager=null;
            } catch (MqttException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 释放资源
     */
    public void release(){
        if (mqttManager!=null) {
            mqttManager=null;
        }
    }
    /**
     *  判断服务是否连接
     * @return
     */
    public boolean isConnected(){
        if (client!=null) {
            return client.isConnected();
        }
        return false;
    }

}

5.用到的 ResourcesUtils一个小工具类 自己可以修改一下

import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.support.annotation.StringRes;
import android.text.TextUtils;
import android.view.View;

import java.text.MessageFormat;


public class ResourcesUtils {
    /**
     * 获取Resources
     * @return  资源Resources
     */
    public static Resources getResources() {
        return MyApplication.getInstance().getResources();
    }

    /**
     * 获取strings.xml资源文件字符串
     *
     * @param id 资源文件id
     * @return 资源文件对应字符串
     */
    public static String getString(int id) {
        return getResources().getString(id);
    }

    /**
     * 获取strings.xml资源文件字符串数组
     *
     * @param id 资源文件id
     * @return 资源文件对应字符串数组
     */
    public static String[] getStringArray(int id) {
        return getResources().getStringArray(id);
    }

    /**
     * 获取drawable资源文件图片
     *
     * @param id 资源文件id
     * @return 资源文件对应图片
     */
    public static Drawable getDrawable(int id) {
        return getResources().getDrawable(id);
    }

    /**
     * 获取colors.xml资源文件颜色
     *
     * @param id 资源文件id
     * @return 资源文件对应颜色值
     */
    public static int getColor(int id) {
        return getResources().getColor(id);
    }

    /**
     * 获取颜色的状态选择器
     *
     * @param id 资源文件id
     * @return 资源文件对应颜色状态
     */
    public static ColorStateList getColorStateList(int id) {
        return getResources().getColorStateList(id);
    }

    /**
     * 获取dimens资源文件中具体像素值
     *
     * @param id 资源文件id
     * @return 资源文件对应像素值
     */
    public static int getDimen(int id) {
        return getResources().getDimensionPixelSize(id);// 返回具体像素值
    }

    /**
     * 加载布局文件
     *
     * @param id 布局文件id
     * @return 布局view
     */
    public static View inflate(int id) {
        return View.inflate(MyApplication.getInstance(), id, null);
    }

    /**
     * 获取字符串资源并格式化输出
     *
     * @param resId
     * @param params
     * @return
     */
    public static String getString(@StringRes int resId, Object... params) {
        String _str = MyApplication.getInstance().getString(resId);
        if (TextUtils.isEmpty(_str)) {
            return "";
        }
        if (params != null && params.length > 0) {
            for (int i = 0; i < params.length; i++) {
                params[i] += "";
            }
            try {
                _str = _str.contains("{0}") ? MessageFormat.format(_str, params) : String.format(_str, params);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return _str.replace("\u2028","");
    }
}

6.准备工作基本完成,下面就是开始调用了,至于怎么用,可以根据自己的实际情况,我是在一个Service中开始建立连接的,在Activity或者Fragment中判断一下是否是连接状态,然后进行订阅,配合EventBus会很方便

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

public class MyMqttService extends Service {


    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
        super.onCreate();

    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        LogUtils.d("MqttService onStartCommand executed");
        MQTTManager instance=MQTTManager.getInstance(getApplicationContext());
        if(!instance.isConnected()){
            LogUtils.d("未连接,开始连接");
            instance.connect();
        }else {
            LogUtils.d("之前已连接,无需再连");
        }

        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        MQTTManager.getInstance(getApplicationContext()).disconnect();
        LogUtils.d("MqttService onDestroy executed");
    }
}

在主Activity中启动 或者自己认为需要的时候启动,我是在MainActivity中启动的

Intent intent = new Intent(this, MyMqttService.class);
startService(intent);

在需要订阅的Activity中订阅 比如在自己的 init() 方法中

 String topics[] = {"xxxxxxx", "xxxxxxxxx"};
            int qoss[] = {1, 1};
MQTTManager instance = MQTTManager.getInstance(getApplicationContext());
   if (instance.isConnected()) {
        instance.subscribeMsgArray(topics, qoss);
    }



在页面中获取消息

//结合EventBus获取消息
@Subscribe(threadMode = ThreadMode.MAIN)
    public void getMyMqttMessge(MyMqttMessage myMqttMessage) {
        String topic = myMqttMessage.getTopic();
        String message = myMqttMessage.getMsgContent();
        //做你要做的事

    }

 

其中MyMqttMessage 是自己定义的实体类 我只需要两个属性

public class MyMqttMessage {
    private String topic;
    private String msgContent;

    public String getTopic() {
        return topic;
    }

    public void setTopic(String topic) {
        this.topic = topic;
    }

    public String getMsgContent() {
        return msgContent;
    }

    public void setMsgContent(String msgContent) {
        this.msgContent = msgContent;
    }
}

退出的时候可以取消订阅

@Override
    protected void onDestroy() {

        if (dialog != null) {
            dialog.dismiss();
        }
        String topics[] = {"xxxxxxx", "xxxxxxxx"};
        MQTTManager.getInstance(getApplicationContext()).unsubcribe(topics);
        EventBus.getDefault().unregister(this);
        super.onDestroy();
    }

大概就是这样,把握两个关键类,其他的自己控制,如有遗漏,可以留言。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值