站在Android开发者的角度认识MQTT - TSL认证篇

MQTT 是用于物联网 (IoT) 的 OASIS 标准消息传递协议。它被设计为一种极其轻量级的发布/订阅消息传输,非常适合以较小的代码占用空间和最小的网络带宽连接远程设备。如今,MQTT 已广泛应用于各个行业,例如汽车、制造、电信、石油和天然气等。

在上一篇站在Android开发者的角度认识MQTT - 使用篇已经介绍过Android开发如何去使用MQTT基本操作,本篇文章将从TSL认证方面介绍在连接MQTT时进行TSL单向和双向认证。

文章将从TSL认证的基本概念、单项认证和双向认证三个部分介绍。

基本概念

TSL(Transport Layer Security)名为传输层安全性,它的目的是加强互联网通信的私密性和安全性。TSL是在SSL的基础上演变而来,目前TSL的最新版本为1.3,1.0版本已经被废弃,我们常用的接口HTTPS就是在HTTP的基础上通过TSL进行加密。

了解了TSL的基本概念之后,我们停下想一想为啥要进行TSL认证呢?使用MQTT时,直接使用用户名和密码不是已经可以检验客户端的身份了么?

  • 如果是直接通过用户名和密码进行连接的话,通信过程中消息是不经过加密的,直接以明文的形式进行传输,而TSL认证的方式连接它的消息是经过加密的,这样消息是受保护的不容易被窃听和篡改;
  • 用户名和密码一般都是固定不变,这样很容易被破解然后直接可以连接MQTT,而TSL可以做到动态下发证书和KEY,保证每一个客户端都拥有不一样的证书和KEY,极大的提高了安全性。

接着我们再来看看TSL的单向认证和双向认证的区别:

  • 单向认证:只有客户端会根据根证书来验证服务端的证书,而服务端不会验证客户端的真实性;
  • 双向认证:在客户端验证服务端之后,服务端也需要验证客户端的证书是否正确。

在验证过程中有几个重要的证书需要了解下,根证书部分一般为root.crt和root.key,服务端证书部分一般为server.crt和server.key,对于我们客户端来说也有client.crt和client.key。在单向认证过程中,客户端会持有root.crt来验证server.crt,双向认证过程中,除了客户端认证方面以外,服务端也会验证client.crt。在进行双向认证时,可以采用动态下发证书的方式确认每一个客户端使用不同的证书和KEY,这样可以进一步加强传输的安全性。

单向认证

在使用TSL加密认证之前,我们需要提前添加一下bouncycastle依赖,它是适用于Java平台的轻量级密码术包。

implementation("org.bouncycastle:bcpkix-jdk15on:1.70")

下面进行MQTT的单向认证流程:

kotlin 复制代码
fun getOneWayAuthSocketFactory(): SSLSocketFactory {
    val caInputStream: InputStream = context.resources.openRawResource(R.raw.ca)
    Security.addProvider(BouncyCastleProvider())
    var caCert: X509Certificate? = null
    val bis = BufferedInputStream(caInputStream)
    val cf = CertificateFactory.getInstance("X.509")
    while (bis.available() > 0) {
        caCert = cf.generateCertificate(bis) as X509Certificate
    }
    val caKs = KeyStore.getInstance(KeyStore.getDefaultType())
    caKs.load(null, null)
    caKs.setCertificateEntry("cert-certificate", caCert)
    val tmf: TrustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
    tmf.init(caKs)
    val sslContext: SSLContext = SSLContext.getInstance("TLSv1.2")
    sslContext.init(null, tmf.trustManagers, null)
    return sslContext.socketFactory
}

编码之前需要先将跟证书放入到项目的raw目录下,然后就可以从raw目录下将根证书ca.crt读成输出流InputStream,在最后的SSLContext处需要注意的是TSL的版本,这里采用的是1.2的版本,最终可以从SSLContext中获取到SocketFactory。

拿到单向认证的SocketFactory之后,在MQTT进行连接的时候,将SocketFactory传入到MqttConnectionOptions中:

mqttConnectOptions.socketFactory = getOneWayAuthSocketFactory()

这样我们使用的MQTT就是单向认证的加密方式了。

双向认证

kotlin 复制代码
fun getTwoWayAuthSocketFactory(): SSLSocketFactory {
    val caCrtFileInputStream: InputStream = context.resources.openRawResource(R.raw.ca)
    val clientCrtFileInputStream: InputStream = context.resources.openRawResource(R.raw.client)
    val clientKeyFileInputStream: InputStream = context.resources.openRawResource(R.raw.client_key)
    Security.addProvider(BouncyCastleProvider())

    // 加载根证书ca.crt
    var caCert: X509Certificate? = null
    var bis = BufferedInputStream(caCrtFileInputStream)
    val cf = CertificateFactory.getInstance("X.509")
    while (bis.available() > 0) {
        caCert = cf.generateCertificate(bis) as X509Certificate
    }

    // 加载客户端证书client.crt
    bis = BufferedInputStream(clientCrtFileInputStream)
    var cert: X509Certificate? = null
    while (bis.available() > 0) {
        cert = cf.generateCertificate(bis) as X509Certificate
    }

    // 加载客户端KEY client.key
    val pemParser = PEMParser(InputStreamReader(clientKeyFileInputStream))
    val `object` = pemParser.readObject()
    val converter = JcaPEMKeyConverter().setProvider("BC")
    val key = converter.getKeyPair(`object` as PEMKeyPair)
    val caKs = KeyStore.getInstance(KeyStore.getDefaultType())
    caKs.load(null, null)
    caKs.setCertificateEntry("cert-certificate", caCert)
    val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
    tmf.init(caKs)
    val ks = KeyStore.getInstance(KeyStore.getDefaultType())
    ks.load(null, null)
    ks.setCertificateEntry("certificate", cert)
    ks.setKeyEntry("private-cert", key.private, "".toCharArray(), arrayOf<Certificate?>(cert))
    val kmf: KeyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
    kmf.init(ks, "".toCharArray())
    val context = SSLContext.getInstance("TLSv1.2")
    context.init(kmf.getKeyManagers(), tmf.trustManagers, null)
    return context.socketFactory
}

双向认证多出了client.crt和client.key文件需要处理,如果这两个人文件为固定不变的,可直接放入raw目录,如果是动态下发的,这里就不需要从raw目录读取,直接从动态下发的字符串转换成流:

ini 复制代码
val clientCrt = "xxxxxxxxxx"
bis = BufferedInputStream(clientCrt.byteInputStream())

最后的版本号和单向认证保持一致即可。

通过单向或者双向认证之后,我们MQTT在传输过程中信息就不再是裸奔的状态了,TSL会将传输信息进行加密然后传输,这样我们就不必担心传输的信息被别人窃取。

本篇文章内容不多也不复杂,主要还是说明了下TSL认证在Android MQTT中是如何运用的,如果大家想更深入的了解下TSL加密的具体过程,推荐阅读 SSL 单向与双向认证

MQTT系列文章:

站在Android开发者的角度认识MQTT - 使用篇

站在Android开发者的角度认识MQTT - TSL认证篇

关于我

我是Taonce,如果觉得本文对你有所帮助,帮忙关注、赞或者收藏三连一下,谢谢😆~

相关推荐
大白要努力!17 分钟前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟1 小时前
Android音频采集
android·音视频
小白也想学C2 小时前
Android 功耗分析(底层篇)
android·功耗
曙曙学编程2 小时前
初级数据结构——树
android·java·数据结构
闲暇部落5 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
诸神黄昏EX7 小时前
Android 分区相关介绍
android
大白要努力!8 小时前
android 使用SQLiteOpenHelper 如何优化数据库的性能
android·数据库·oracle
Estar.Lee8 小时前
时间操作[取当前北京时间]免费API接口教程
android·网络·后端·网络协议·tcp/ip
Winston Wood8 小时前
Perfetto学习大全
android·性能优化·perfetto
Dnelic-11 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记