原文地址
使用 SSL 对 MQTT的消息交换进行加密,提高安全性。
- 服务端使用的是 EMQ v2.0
- 客户端使用的是 Eclipse Paho Android Service
服务器启用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;
}
}