原文地址
使用 SSL 對 MQTT的消息交換進行加密,提高安全性肩杈。
- 服務(wù)端使用的是 EMQ v2.0
- 客戶端使用的是 Eclipse Paho Android Service
服務(wù)器啟用SSL
我們需要數(shù)字證書來對進行ssl通信用戶進行強認證档址。由于獲得一個真正受外界信任的證書需要花費money领迈,所有我們采用自簽名證書压彭。
要實現(xiàn)雙向認證(服務(wù)器認證客戶端运翼、客戶端認證服務(wù)器)我們需要3個證書双谆,一個CA證書壳咕,一個EMQ服務(wù)器證書,一個客戶端證書顽馋。
具體的生成證書操作谓厘、啟用EMQ ssl和雙向認證,參考這篇文章 Securing EMQ Connections with SSL
android客戶端的ssl實現(xiàn)
上一步我們生成了客戶端使用的證書 MyClient1.pem
和私有秘鑰MyClient1.key
寸谜,但是要想在android上使用需要將其轉(zhuǎn)成bks
格式竟稳。
pem 轉(zhuǎn) bks
1、首先生成.p12
文件:
openssl pkcs12 -export -nodes -in MyClient1.pem -inkey MyClient1.key -out client.p12
-
-inkey
為私鑰文件 -
-in
為證書熊痴,如果pem私鑰沒有密碼他爸,則使用-nodes
表示無密碼,如果有密碼使用-passin
果善;如果私鑰和證書都在同一文件里則-in
和-inkey
指定同一個文件诊笤。
會提示輸入給.p12秘鑰庫設(shè)置的密碼,請記住巾陕,下面會用到
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 轉(zhuǎn)換為 BKS 格式時纪他,需要 bcprov-ext-jdk15on-158.jar
,可以在 這里 找到晾匠。文件路徑直接帶在-providerpath
參數(shù)后面即可茶袒。也可以把jar包放到如下路徑:jdk/jre/lib/ext
,從而省略-providerpath
凉馆。
會首先提示輸入給bks秘鑰庫設(shè)置的密碼弹谁,請記住,下面會用到句喜。
然后會提示輸入p12秘鑰庫密碼,即上一步設(shè)置的密碼沟于。
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;
}
}