Android设备作为客户端,与PC服务端进行双向认证、基于TLS 1.2协议socket通信。
由于是双向认证,需要制作客户端和服务器证书,这里用到的是JAVA自带的keytool工具(请自行安装JDK环境)。
话不多说,上证书制作脚本:
1)生成服务器端公私钥对,存入密钥库,命令keytool -genkeypair:
keytool -genkeypair -alias serverkey -keyalg ec -validity 365 -keystore serverkey.keystore.jks
-storepass 1234567 -dname "cn=AA.COM, ou=AA, o=AA Cor, c=CN, l=SZ" -deststoretype pkcs12
参数 | 含义 |
---|---|
-alias | 设置别名,可自定义 |
-keyalg | 设置密钥算法,DSA、RSA、ECDSA等 |
-valiity | 有效期 |
-keystore | 密钥库名称 |
-storepass | 密钥库密码 |
-dname | 指定证书发行方信息 |
-deststoretype | 指定keystore类型,默认pkcs12 |
2)同理,生成客户端公私钥对:
keytool -genkeypair -alias clientkey -keyalg ec -validity 365 -keystore clientkey.keystore.jks
-storepass 7654321 -dname "cn=BB.COM, ou=BB, o=BB Cor, c=CN, l=SZ" -deststoretype pkcs12
这样就生成了自签名的服务器证书和客户端证书。
虽然我们将服务器与客户端密钥库分别提交给对方加入到各自受信任密钥库,在通信的时候互相校验,可以做到双向认证通过,但通常情况下不会这样做,一来直接将证书丢给对方很不安全,密钥库被盗用即有身份被冒用风险;二来客户端和服务端的密钥库一旦签发给对方,其存储的证书内容被固定住,无法扩展。
所以我们引入第三方受信CA,客户端和服务端分别只要信任该CA,则该CA签发的证书都是可信任的。
由于市面上流通的知名CA都是收费签发,这里使用自签CA来模拟这个过程。
1)首先生成CA密钥库:
keytool -genkeypair -alias rootca -keyalg ec -validity 3650 -keystore ca.keystore.jks -storepass
22222222 -dname "cn=CA.COM, ou=CA, o=CA Cor, c=CN, l=SZ" -deststoretype pkcs12
2)导出CA证书,用于后续导入到密钥库:
···
keytool -exportcert -alias rootca -file ca.cer -keystore ca.keystore.jks -storepass 22222222 -deststoretype pkcs12
···
3)生成服务器端证书请求serverkey.csr,即是待签发的证书文件:
keytool -certreq -alias serverkey -keystore serverkey.keystore.jks -storepass 1234567 -file
serverkey.csr -deststoretype pkcs12
4)使用 CA密钥库ca.keystore.jks对serverkey.csr进行签发:
keytool -gencert -alias rootca -infile serverkey.csr -outfile serverkey.cer -validity 365
-keystore ca.keystore.jks -storepass 22222222 -deststoretype pkcs12
使用keytool -printcert命令可以看到签发者变成了CA(CA.COM):
5)导入CA证书到服务器密钥库:
keytool -import -trustcacerts -alias rootca -file ca.cer -keystore serverkey.keystore.jks
-storepass 1234567 -deststoretype pkcs12
导入时会提示是否信任,输入y,表示信任
6)导入签发证书到服务器密钥库:
keytool -import -trustcacerts -alias serverkey -file serverkey.cer -keystore
serverkey.keystore.jks -storepass 1234567 -deststoretype pkcs12
使用keytool -list -v命令查看密钥库可以看到有两个证书,一个是签发后的密钥,一个是CA,其中密钥签发者变成了CA(CA.COM):
7)同理生成客户端证书请求并使用CA签发:
keytool -certreq -alias clientkey -keystore clientkey.keystore.jks -storepass 7654321 -file
clientkey.csr -deststoretype pkcs12
keytool -gencert -alias rootca -infile clientkey.csr -outfile clientkey.cer -validity 365
-keystore ca.keystore.jks -storepass 22222222 -deststoretype pkcs12
keytool -import -trustcacerts -alias rootca -file ca.cer -keystore clientkey.keystore.jks
-storepass 7654321 -deststoretype pkcs12
keytool -import -trustcacerts -alias clientkey -file clientkey.cer -keystore
clientkey.keystore.jks -storepass 7654321 -deststoretype pkcs12
至此,双向认证的JKS证书已经制作完成。
我们这里是Android客户端,不能直接使用JKS密钥,需要用BKS格式密钥文件。
这里借助portecle https://sourceforge.net/projects/portecle/files/latest/download 工具进行转换:
下载解压缩后,进入相应目录,执行
java -jar portecle.jar
转换CA密钥库:
首先将CA证书导入到一个新的JKS格式CA密钥库cacer.keystore.jks:
keytool -importcert -alias rootca -file ca.cer -keystore cacer.keystore.jks
-storepass 33333333
然后类似服务器BKS密钥库转换方式进行转换得到ca.keystore.bks【注意!!此时CA密钥库密码变为新密码 33333333】
这样我们拿到了三个BKS密钥库:
密钥库 | 用途 |
---|---|
ca.keystore.bks | 服务器和客户端各持一份,用于验签对方证书 |
serverkey.keystore.bks | 服务器持有,双向认证时发给客户端 |
clientkey.keystore.bks | 客户端持有,双向认证时发给服务器 |
加密套件(Ciphe Suites)是指在ssl/tls通信中,服务器和客户端所使用的加密算法的组合。在ssl握手初期,客户端将自身支持的加密套件列表发送给服务器;在握手阶段,服务器根据自己的配置从中尽可能的选出一个套件,作为之后所要使用的加密方式。这些算法包括:认证算法、密钥交换算法、对称算法和摘要算法等。
随着技术的发展,某些加密套件由于算法安全级别不够,不需要出现在双向认证的候选加密套件列表中,只需在openssl源码SSL_CIPHER kCiphers[]列表进行移除即可,比如下面去掉Cipher 04、Cipher 05、Cipher 0A:
/* kCiphers is an array of all supported ciphers, sorted by id. */
static const SSL_CIPHER kCiphers[] = {
/* The RSA ciphers */
/* Cipher 02 */
{
SSL3_TXT_RSA_NULL_SHA,
SSL3_CK_RSA_NULL_SHA,
SSL_kRSA,
SSL_aRSA,
SSL_eNULL,
SSL_SHA1,
SSL_HANDSHAKE_MAC_DEFAULT,
},
/*
/* Cipher 04 *
{
SSL3_TXT_RSA_RC4_128_MD5,
SSL3_CK_RSA_RC4_128_MD5,
SSL_kRSA,
SSL_aRSA,
SSL_RC4,
SSL_MD5,
SSL_HANDSHAKE_MAC_DEFAULT,
},
/* Cipher 05 *
{
SSL3_TXT_RSA_RC4_128_SHA,
SSL3_CK_RSA_RC4_128_SHA,
SSL_kRSA,
SSL_aRSA,
SSL_RC4,
SSL_SHA1,
SSL_HANDSHAKE_MAC_DEFAULT,
},
/* Cipher 0A
{
SSL3_TXT_RSA_DES_192_CBC3_SHA,
SSL3_CK_RSA_DES_192_CBC3_SHA,
SSL_kRSA,
SSL_aRSA,
SSL_3DES,
SSL_SHA1,
SSL_HANDSHAKE_MAC_DEFAULT,
},
*/
...
};
Android应用层,若是使用SSLSocket,则只需使用setEnabledCipherSuites方法设置想要的加密套件:
List allowedCiphers = Arrays.asList(
// TLS 1.2
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
// maximum interoperability
//"SSL_RSA_WITH_3DES_EDE_CBC_SHA", // FAQ 69
"TLS_RSA_WITH_AES_128_CBC_SHA",
// additionally
"TLS_RSA_WITH_AES_256_CBC_SHA",
//"TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
//"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA");
String[] enabledCiphers=allowedCiphers.toArray(new String[allowedCiphers.size()]);
Client_sslSocket = (SSLSocket) sslContext.getSocketFactory().createSocket(SERVER_IP,SERVER_PORT);
String[] supportSuite = Client_sslSocket.getSupportedCipherSuites();
Client_sslSocket.setEnabledCipherSuites(enabledCiphers);
完整demo代码见 https://download.csdn.net/download/peterhu_112/10606499
【参考】 https://docs.oracle.com/javase/8/docs/technotes/tools/windows/keytool.html