您的位置:首页 > 移动开发 > Android开发

android平台ssl单双向验证

2015-06-11 15:03 537 查看
最近做android平台ssl通信研究,被坑了好些,最后历经磨难,终于搞定它了,得到的结论:实践才是检验坑有多深的最佳途径!!

看网上的中文资料,提到android ssl通信的也有,但都是零零碎碎,提到双向的实现更少,国外有些资料还可以,推荐stackoverflow,里面有不少探讨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工具,没有自己弄个,这个网上资料多。

代码参考:/article/7487813.html,由于服务端有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文件中,然后从这个文件读取认证,怎么导入,网上资料很多,如:/article/9888197.html

调试中遇到的问题,提一下:

一般在模拟器中能通过,在真实平台上就没问题了。

个人遇到一个最蛋疼的问题是模拟器上通过了,但真实平台就是报错,卡了好几天,查了好多资料没查到,网上也有类似的提问,但没有靠谱的解答,最后硬着头皮终于查出来是真实平台的时间不对,2007年,不在证书的有效期内,就一个感受:坑!!

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

双向:

双向在单向的基础上实现,不过要先生成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());

至此,问题基本搞定,不过个人还有个心瘤,理论上用keytool生成bks格式的证书再使用,但是个人各种方法试尽,仍旧没有结果,网上也没找到资料,原谅我的无能,如果有能实现的,麻烦分享一下,感激不尽!!!

原地址:http://blog.csdn.net/hfeng101/article/details/10163627
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: