MQTT 是用于物联网 (IoT) 的 OASIS 标准消息传递协议。它被设计为一种极其轻量级的发布/订阅消息传输,非常适合以较小的代码占用空间和最小的网络带宽连接远程设备。如今,MQTT 已广泛应用于各个行业,例如汽车、制造、电信、石油和天然气等。
在上一篇站在Android开发者的角度认识MQTT - 使用篇已经介绍过Android开发如何去使用MQTT基本操作,本篇文章将从TSL认证方面介绍在连接MQTT时进行TSL单向和双向认证。
文章将从TLS 认证的基本概念、单项认证和双向认证三个部分介绍。
基本概念
TLS(Transport Layer Security)名为传输层安全性,它的目的是加强互联网通信的私密性和安全性。TLS 是在SSL的基础上演变而来,目前TLS的最新版本为1.3,1.0版本已经被废弃,我们常用的接口HTTPS就是在HTTP的基础上通过TLS进行加密。
了解了TLS的基本概念之后,我们停下想一想为啥要进行TLS认证呢?使用MQTT时,直接使用用户名和密码不是已经可以检验客户端的身份了么?
- 如果是直接通过用户名和密码进行连接的话,通信过程中消息是不经过加密的,直接以明文的形式进行传输,而TLS认证的方式连接它的消息是经过加密的,这样消息是受保护的不容易被窃听和篡改;
- 用户名和密码一般都是固定不变,这样很容易被破解然后直接可以连接MQTT,而TLS可以做到动态下发证书和KEY,保证每一个客户端都拥有不一样的证书和KEY,极大的提高了安全性。
接着我们再来看看TLS的单向认证和双向认证的区别:
- 单向认证:只有客户端会根据根证书来验证服务端的证书,而服务端不会验证客户端的真实性;
- 双向认证:在客户端验证服务端之后,服务端也需要验证客户端的证书是否正确。
在验证过程中有几个重要的证书需要了解下,根证书部分一般为root.crt和root.key,服务端证书部分一般为server.crt和server.key,对于我们客户端来说也有client.crt和client.key。在单向认证过程中,客户端会持有root.crt来验证server.crt,双向认证过程中,除了客户端认证方面以外,服务端也会验证client.crt。在进行双向认证时,可以采用动态下发证书的方式确认每一个客户端使用不同的证书和KEY,这样可以进一步加强传输的安全性。
单向认证
在使用TLS加密认证之前,我们需要提前添加一下bouncycastle依赖,它是适用于Java平台的轻量级密码术包。
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
下面进行MQTT的单向认证流程:
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处需要注意的是TLS的版本,这里采用的是1.2的版本,最终可以从SSLContext中获取到SocketFactory。
拿到单向认证的SocketFactory之后,在MQTT进行连接的时候,将SocketFactory传入到MqttConnectionOptions中:
mqttConnectOptions.socketFactory = getOneWayAuthSocketFactory()
这样我们使用的MQTT就是单向认证的加密方式了。
双向认证
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目录读取,直接从动态下发的字符串转换成流:
val clientCrt = "xxxxxxxxxx"
bis = BufferedInputStream(clientCrt.byteInputStream())
最后的版本号和单向认证保持一致即可。
通过单向或者双向认证之后,我们MQTT在传输过程中信息就不再是裸奔的状态了,TLS会将传输信息进行加密然后传输,这样我们就不必担心传输的信息被别人窃取。
本篇文章内容不多也不复杂,主要还是说明了下TLS认证在Android MQTT中是如何运用的,如果大家想更深入的了解下TLS 加密的具体过程,推荐阅读SSL 单向与双向认证
MQTT系列文章:
站在Android开发者的角度认识MQTT - TLS 认证篇
关于我
我是Taonce,如果觉得本文对你有所帮助,帮忙关注、赞或者收藏三连一下,谢谢😆~