Android平台實現SSL單雙向驗證
環境:伺服器: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());