環境:服務器: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());