MQTT安全:使用ssl实现EMQ与android客户端安全通信

原文地址

使用 SSL 对 MQTT的消息交换进行加密,提高安全性。

服务器启用SSL

我们需要数字证书来对进行ssl通信用户进行强认证。由于获得一个真正受外界信任的证书需要花费money,所有我们采用自签名证书。

要实现双向认证(服务器认证客户端、客户端认证服务器)我们需要3个证书,一个CA证书,一个EMQ服务器证书,一个客户端证书。

具体的生成证书操作、启用EMQ ssl和双向认证,参考这篇文章 Securing EMQ Connections with SSL

android客户端的ssl实现

上一步我们生成了客户端使用的证书 MyClient1.pem 和私有秘钥MyClient1.key,但是要想在android上使用需要将其转成bks格式。

pem 转 bks

1、首先生成.p12文件:

openssl pkcs12 -export -nodes -in MyClient1.pem -inkey MyClient1.key -out client.p12
  • -inkey为私钥文件
  • -in为证书,如果pem私钥没有密码,则使用-nodes表示无密码,如果有密码使用-passin;如果私钥和证书都在同一文件里则-in-inkey指定同一个文件。

会提示输入给.p12秘钥库设置的密码,请记住,下面会用到

2、生成.bks证书:

keytool -importkeystore -srckeystore client.p12 -srcstoretype pkcs12 -destkeystore client.bks -deststoretype bks -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath bcprov-ext-jdk15on-158.jar 

使用 KeyTool 转换为 BKS 格式时,需要 bcprov-ext-jdk15on-158.jar,可以在 这里 找到。文件路径直接带在-providerpath 参数后面即可。也可以把jar包放到如下路径:jdk/jre/lib/ext,从而省略-providerpath

会首先提示输入给bks秘钥库设置的密码,请记住,下面会用到。

然后会提示输入p12秘钥库密码,即上一步设置的密码。

3、查看bks证书库列表进行验证

keytool -list -rfc -keystore client.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider -storepass 'bks秘钥库密码'

源码

完整源码见 GitHub

主要代码

package paho.android.mqtt_example;

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.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.security.cert.CertificateException;
import timber.log.Timber;

/**
 * Original SocketFactory file taken from https://github.com/owntracks/android
 */

public class SelfSignedSocketFactory extends javax.net.ssl.SSLSocketFactory {
    private javax.net.ssl.SSLSocketFactory factory;


    public static class SocketFactoryOptions {

        private InputStream caCrtInputStream;
        private InputStream caClientBksInputStream;
        private String caClientBksPassword;


        /**
         *
         * @param stream the self-signed Root CA Certificate's stream
         * @return
         */
        public SocketFactoryOptions withCaInputStream(InputStream stream) {
            this.caCrtInputStream = stream;
            return this;
        }


        /**
         *
         * @param stream the self-signed client Certificate's stream .
         * @return
         */
        public SocketFactoryOptions withClientBksInputStream(InputStream stream) {
            this.caClientBksInputStream = stream;
            return this;
        }


        public SocketFactoryOptions withClientBksPassword(String password) {
            this.caClientBksPassword = password;
            return this;
        }


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


        public boolean hasClientBksCrt() {
            return caClientBksPassword != null;
        }


        public InputStream getCaCrtInputStream() {
            return caCrtInputStream;
        }


        public InputStream getCaClientBksInputStream() {
            return caClientBksInputStream;
        }


        public String getCaClientBksPassword() {
            return caClientBksPassword;
        }


        public boolean hasClientBksPassword() {
            return (caClientBksPassword != null) && !caClientBksPassword.equals("");
        }
    }


    public SelfSignedSocketFactory()
        throws CertificateException, KeyStoreException, NoSuchAlgorithmException, IOException, KeyManagementException,
               java.security.cert.CertificateException, UnrecoverableKeyException {
        this(new SocketFactoryOptions());
    }


    private TrustManagerFactory tmf;


    public SelfSignedSocketFactory(SocketFactoryOptions options)
        throws KeyStoreException, NoSuchAlgorithmException, IOException, KeyManagementException,
               java.security.cert.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");
            // CA certificate is used to authenticate server
            CertificateFactory cAf = CertificateFactory.getInstance("X.509");
            X509Certificate ca = (X509Certificate) cAf.generateCertificate(options.getCaCrtInputStream());
            KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType());
            caKs.load(null, null);
            caKs.setCertificateEntry("ca-certificate", ca);
            tmf.init(caKs);
        } else {
            Timber.v("CA sideload: false, using system keystore");
            KeyStore keyStore = KeyStore.getInstance("AndroidCAStore");
            keyStore.load(null);
            tmf.init(keyStore);
        }

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

            // init client key store
            KeyStore clientkeyStore = KeyStore.getInstance("BKS");
            clientkeyStore.load(options.getCaClientBksInputStream(),
                options.hasClientBksPassword() ? options.getCaClientBksPassword().toCharArray() : new char[0]);
            kmf.init(clientkeyStore,
                options.hasClientBksPassword() ? options.getCaClientBksPassword().toCharArray() : new char[0]);

        } else {
            Log.v(this.toString(), "Client .bks 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 = context.getSocketFactory();

    }


    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;
    }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容

  • 信息安全三要素 1. 保密性:信息在传输时不被泄露 2. 完整性:信息在传输时不被篡改 3. 身份认证:用于确定你...
    Jason1226阅读 1,212评论 0 0
  • 一、作用 不使用SSL/TLS的HTTP通信,就是不加密的通信。所有信息明文传播,带来了三大风险。 (1)窃听风险...
    XLsn0w阅读 10,464评论 2 44
  • 原文地址 http://blog.csdn.net/u012409247/article/details/4985...
    0fbf551ff6fb阅读 3,502评论 0 13
  • 编写TCP服务器和客户端 Vert.x 允许您很轻松的编写一个非阻塞的TCP客户端和服务器。 创建TCP服务器 使...
    安静点就睡吧阅读 6,416评论 1 4
  • 今天下午,我预习了二年级下册,第一单元第二课的课文,名字叫《找春天》。 词语辨析:近义词:脱掉一脱...
    刘俊艳阅读 2,730评论 0 0