環境:服務器:apache服務器,openssl。
客戶端:PC、java平台、android平台。
思路:
1、先搞定ssl單向驗證,再解決雙向。
2、先PC,再java平台,再android,不一定非得這樣,自由選擇,個人是為了弄清整個流程,多走了些路。
過程步驟:
1、在pc上用apache搭建了一個http服務器,用openssl建立自簽名的CA證書ca.crt,簽發服務器證書server.crt,簽發客戶端證書client.crt。(apache+openssl配置ssl通信網上資料很多)
2、安裝ca.crt,配置服務器,開啟單向驗證,用浏覽器測試驗證單向ssl通信。
3、將client.crt和client.key打包生成pkcs12格式的client.pfx文件。
4、配置服務器,開啟雙向驗證,通過浏覽器導入client.pfx文件,測試驗證雙向ssl通信。
重點:
Java平台默認識別jks格式的證書文件,但是android平台只識別bks格式的證書文件,需要在java中配置BC庫,個人推薦參考:http://hi.baidu.com/yaming/item/980f253e17f585be124b142d,配置好BC庫,看看有沒有keytool工具,沒有自己弄個Keytool工具
代碼參考:http://momoch1314.iteye.com/blog/540613,由於服務端有apache,上面的代碼就不用了,此處列出客戶端,此文中是通過socket通信的,建議改成https通信,效果會更好,因為和apache服務器打交道,處理起來會更方便。(裡面有些東西需要微調的,希望各位自己改一下,對於某些人來說,可能會有些坑,有疑問,請留言,本屌盡力解答)
-
public class MySSLSocket extends Activity {
-
private static final int SERVER_PORT = 50030;//端口號
-
private static final String SERVER_IP = "218.206.176.146";//連接IP
-
private static final String CLIENT_KET_PASSWORD = "123456";//私鑰密碼
-
private static final String CLIENT_TRUST_PASSWORD = "123456";//信任證書密碼
-
private static final String CLIENT_AGREEMENT = "TLS";//使用協議
-
private static final String CLIENT_KEY_MANAGER = "X509";//密鑰管理器
-
private static final String CLIENT_TRUST_MANAGER = "X509";//
-
private static final String CLIENT_KEY_KEYSTORE = "BKS";//密庫,這裡用的是BouncyCastle密庫
-
private static final String CLIENT_TRUST_KEYSTORE = "BKS";//
-
private static final String ENCONDING = "utf-8";//字符集
-
private SSLSocket Client_sslSocket;
-
private Log tag;
-
private TextView tv;
-
private Button btn;
-
private Button btn2;
-
private Button btn3;
-
private EditText et;
-
-
/** Called when the activity is first created. */
-
@Override
-
public void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.main);
-
tv = (TextView) findViewById(R.id.TextView01);
-
et = (EditText) findViewById(R.id.EditText01);
-
btn = (Button) findViewById(R.id.Button01);
-
btn2 = (Button) findViewById(R.id.Button02);
-
btn3 = (Button) findViewById(R.id.Button03);
-
-
btn.setOnClickListener(new Button.OnClickListener(){
-
@Override
-
public void onClick(View arg0) {
-
if(null != Client_sslSocket){
-
getOut(Client_sslSocket, et.getText().toString());
-
getIn(Client_sslSocket);
-
et.setText("");
-
}
-
}
-
});
-
btn2.setOnClickListener(new Button.OnClickListener(){
-
@Override
-
public void onClick(View arg0) {
-
try {
-
Client_sslSocket.close();
-
Client_sslSocket = null;
-
} catch (IOException e) {
-
e.printStackTrace();
-
}
-
}
-
});
-
btn3.setOnClickListener(new View.OnClickListener(){
-
@Override
-
public void onClick(View arg0) {
-
init();
-
getIn(Client_sslSocket);
-
}
-
});
-
}
-
-
public void init() {
-
try {
-
//取得SSL的SSLContext實例
-
SSLContext sslContext = SSLContext.getInstance(CLIENT_AGREEMENT);
-
//取得KeyManagerFactory和TrustManagerFactory的X509密鑰管理器實例
-
KeyManagerFactory keyManager = KeyManagerFactory.getInstance(CLIENT_KEY_MANAGER);
-
TrustManagerFactory trustManager = TrustManagerFactory.getInstance(CLIENT_TRUST_MANAGER);
-
//取得BKS密庫實例
-
KeyStore kks= KeyStore.getInstance(CLIENT_KEY_KEYSTORE);
-
KeyStore tks = KeyStore.getInstance(CLIENT_TRUST_KEYSTORE);
-
//加客戶端載證書和私鑰,通過讀取資源文件的方式讀取密鑰和信任證書
-
kks.load(getBaseContext()
-
.getResources()
-
.openRawResource(R.drawable.kclient),CLIENT_KET_PASSWORD.toCharArray());
-
tks.load(getBaseContext()
-
.getResources()
-
.openRawResource(R.drawable.lt_client),CLIENT_TRUST_PASSWORD.toCharArray());
-
//初始化密鑰管理器
-
keyManager.init(kks,CLIENT_KET_PASSWORD.toCharArray());
-
trustManager.init(tks);
-
//初始化SSLContext
-
sslContext.init(keyManager.getKeyManagers(),trustManager.getTrustManagers(),null);
-
//生成SSLSocket
-
Client_sslSocket = (SSLSocket) sslContext.getSocketFactory().createSocket(SERVER_IP,SERVER_PORT);
-
} catch (Exception e) {
-
tag.e("MySSLSocket",e.getMessage());
-
}
-
}
-
-
public void getOut(SSLSocket socket,String message){
-
PrintWriter out;
-
try {
-
out = new PrintWriter(
-
new BufferedWriter(
-
new OutputStreamWriter(
-
socket.getOutputStream()
-
)
-
),true);
-
out.println(message);
-
} catch (IOException e) {
-
e.printStackTrace();
-
}
-
}
-
-
public void getIn(SSLSocket socket){
-
BufferedReader in = null;
-
String str = null;
-
try {
-
in = new BufferedReader(
-
new InputStreamReader(
-
socket.getInputStream()));
-
str = new String(in.readLine().getBytes(),ENCONDING);
-
} catch (UnsupportedEncodingException e) {
-
e.printStackTrace();
-
} catch (IOException e) {
-
e.printStackTrace();
-
}
-
new AlertDialog
-
.Builder(MySSLSocket.this)
-
.setTitle("服務器消息")
-
.setNegativeButton("確定", null)
-
.setIcon(android.R.drawable.ic_menu_agenda)
-
.setMessage(str)
-
.show();
-
}
-
}
單向:
1、用keytool將ca.crt導入到bks格式的證書庫ca.bks,用於驗證服務器的證書,命令如下:
keytool -import -alias ca -file ca.crt -keystore ca.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider
2、服務器配置成單向驗證,將ca.bks放到android工程的assets或raw下,對應的讀取就是代碼中的
-
kks.load(getBaseContext()
-
.getResources()
-
.openRawResource(R.drawable.kclient),CLIENT_KET_PASSWORD.toCharArray());
不一定是R.drawable.kclient,自己根據實際做修改,讀取文件,不懂網上查,不啰嗦了。
至此,單向ssl通信應該是OK了。
(PS: 針對2中的操作不一定非得這麼做,也可以把ca.bks導入到android平台下的cacerts.bks文件中,然後從這個文件讀取認證,怎麼導入,網上資料很多,如:http://blog.csdn.net/haijun286972766/article/details/6247675
調試中遇到的問題,提一下:
一般在模擬器中能通過,在真實平台上就沒問題了。
這裡需要注意的是證書的有效期,一定要在證書的有效期內操作
雙向:
雙向在單向的基礎上實現,不過要先生成android平台能識別的客戶端證書,這個玩意也傷腦筋,網上提到生成bks格式客戶端證書的資料很少,鮮有借鑒之用。
在這個點上,太傷腦筋了,估計很多伙計也在這兒卡得蛋疼,一開始是毫無頭緒,在PC、JAVA平台上生成客戶端證書,都能測通,但是轉到android平台就傻眼了,用keytool將其它工具生成的crt證書,導成bks格式,不通;用keytool工具新生成bks格式證書,也不通;
各種能想的方法試盡,一度懷疑自己是不是哪個細節出錯了,理論上肯定能做的東西,怎麼看不到一點可實現性,找資料連續幾天,一點進展都沒。
後面看國外的資料上提到先用openssl生成pkcs12的.pfx格式證書,然後用工具portecle轉換成BKS格式,在android平台上使用,一開始是直接強制性轉換,出錯,怎麼轉都轉不成功,但是轉換成jks格式又沒問題,只能根據提示錯誤,找解決方案,試了好多還是不行,又迷茫了;
1、最後看到國外的資料上的一句話,頓悟靈光,用portecle工具,先建立一個bks格式的keystore,然後將client.pfx中的key pair導入(import key pair),再保存bks文件,測試成功,事實證明:二了一點。
PS:用portecle直接轉應該是可以的,只是我一直沒轉成功過,可能是我的java環境有問題,老提示illegal key size。
2、將服務器配置成雙向驗證,將ca.bks放到android工程的assets或raw下,對應的讀取就是代碼中的
-
tks.load(getBaseContext()
-
.getResources()
-
.openRawResource(R.drawable.lt_client),CLIENT_TRUST_PASSWORD.toCharArray());