一、 了解数字证书
在HTTPS的传输过程中,有一个非常关键的角色——数字证书,那什么是数字证书?又有什么作用呢?
所谓数字证书,是一种用于电脑的身份识别机制。由数字证书颁发机构(CA)对使用私钥创建的签名请求文件做的签名(盖章),表示CA结构对证书持有者的认可。数字证书拥有以下几个优点:
使用数字证书能够提高用户的可信度 数字证书中的公钥,能够与服务端的私钥配对使用,实现数据传输过程中的加密和解密
在证认使用者身份期间,使用者的敏感个人数据并不会被传输至证书持有者的网络系统上 X.509证书包含三个文件:key
,csr
,crt
key【密钥/私钥 Private Key】是服务器上的私钥文件,用于对发送给客户端数据的加密,以及对从客户端接收到数据的解密
csr【证书认证签名请求 Certificate signing request】是证书签名请求文件,用于提交给证书颁发机构(CA)对证书签名
crt【证书 Certificate】是由证书颁发机构(CA)签名后的证书,或者是开发者自签名的证书,包含证书持有人的信息,持有人的公钥,以及签署者的签名等信息。
备注:在密码学中,X.509是一个标准,规范了公开秘钥认证、证书吊销列表、授权凭证、凭证路径验证算法等。
此外还会涉及到不同平台的相关不同格式证书,这些证书可以在不同格式间相互转换
。
cer
【俗称数字证书】,目的就是用于存储公钥证书,任何人都可以获取这个文件pem
-【Privacy Enhanced Mail】打开看⽂本格式,以"-----BEGIN…"开头, "-----END…"结尾,内容是BASE64编码。Apache和*NIX服务器偏向于使⽤这种编码格式.p12
【是PKCS12的缩写】同样是一个存储私钥的证书库,由.jks文件导出的,用户在PC平台安装,用于标示用户的身份bks
【Android平台支持的证书库格式
】由于Android平台不识别.keystore__和.jks**格式的证书库文件,因此Android平台引入一种的证书库格式,BKS。jks
【数字证书库】。JKS里有KeyEntry和CertEntry,在库里的每个Entry都是靠别名(alias)来识别的。
二、生成数字证书
数字证书的生成需要使用到openssl
,这里只是简单介绍一下,详细咨询请自行查阅相关资料
- 安装 openssl
yum install openssl -y
- 常用命令
-req 产生证书签发申请命令
-newkey 生成新私钥 rsa:4096 生成秘钥位数
-nodes 表示私钥不加密
-sha256 使用SHA-2哈希算法
-keyout 将新创建的私钥写入的文件名
-x509 签发X.509格式证书命令。X.509是最通用的一种签名证书格式。
-out 指定要写入的输出文件名
-subj 指定用户信息
-days 有效期(3650表示十年)
-storepass 设置密码
-import 导入证书
-importkeystore 导入已有证书(转换格式)
-export 导出证书
-genkeypair 生成相关证书
-genkey 生成密钥
三、双向认证
1. 单向认证
操作系统或者浏览器一般都内置了一堆相关CA的证书,所以可以直接访问;若是自签发的,浏览器会提示该证书不受信任。适用场景是站点访问,非高机密数据传输。
https一般是单向认证,通常由客户端来校验服务器证书。
2. 双向认证
双向认证,即不仅客户端校验服务器,服务器也校验客户端,通常用于高机密数据传输。
客户端持有服务端的公钥证书,并持有自己的私钥,服务端持有客户的公钥证书,并持有自己私钥。
建立连接的时候,客户端利用服务端的公钥证书来验证服务器是否上是目标服务器;服务端利用客户端的公钥来验证客户端是否是目标客户端。(请参考RSA非对称加密以及HASH校验算法)
服务端给客户端发送数据时,需要将服务端的证书发给客户端验证,验证通过才运行发送数据,同样,客户端请求服务器数据时,也需要将自己的证书发给服务端验证,通过才允许执行请求。
四、生成自签名私有证书
1. 单向认证(只需要服务端证书)
1、生成服务端密钥(配置了jdk的环境变量即可用keytool命令)
命令:
keytool -genkey -keystore server_ks.jks -storepass server_password -keyalg RSA -keypass server_password
结果:会生成server_ks.jks密钥文件
操作:将生成的server_ks.jks密钥文件配置到服务端
2、生成服务端证书
命令:
keytool -export -keystore server_ks.jks -storepass server_password -file server.cer
结果:会生成server.cer证书文件
操作:将生成的server.cer证书文件配置到客户端
2. 双向认证(还需要客户端证书,和信任证书)
1、生成客户端密钥
命令:
keytool -genkey -keystore client_ks.jks -storepass client_password -keyalg RSA -keypass client_password
结果:会生成client_ks.jks密钥文件
操作:将生成的client_ks.jks密钥文件配置到客户端
2、生成客户端证书
命令:
keytool -export -keystore client_ks.jks -storepass client_password -file client.cer
结果:会生成client.cer证书文件
下面重点:证书交换
将客户端证书导入服务端keystore中,再将服务端证书导入客户端keystore中, 一个keystore可以导入多个证书,生成证书列表。
3、将server端证书添加到serverTrust_ks.jks文件中
命令:
keytool -import -keystore serverTrust_ks.jks -storepass client -file server.cer
结果:会生成serverTrust_ks.jks密钥文件
操作:将生成的serverTrust_ks.jks密钥文件配置到客户端
4、将client端证书添加到clientTrust_ks.jks文件中
命令:
keytool -import -keystore clientTrust_ks.jks -storepass server -file client.cer
结果:会生成clientTrust_ks.jks密钥文件
操作:将生成的clientTrust_ks.jks密钥文件配置到服务端
有些人可能有疑问,为什么有些博主操作只需要共用一个证书文件,而这里需要生成二个Trust文件?
因为有时客户端可能需要访问多个服务器,而每个服务器的证书都不相同,因此客户端需要制作一个truststore来存储受信任的服务器的证书列表
。因此为了规范创建一个truststore.jks用于存储受信任的服务器证书,创建一个client.jks来存储客户端自己的私钥。对于只涉及与一个服务端进行双向认证的应用,将server.cer导入到client.jks中也可
。
5将证书转换为Android平台支持的BKS格式
服务端信任证书jks转bks
keytool -importkeystore -srckeystore serverTrust_ks.jks -destkeystore serverTrust_ks.bks -srcstoretype JKS -deststoretype BKS -srcstorepass client -deststorepass client -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath bcprov-ext-jdk15on-154.jar
客户端信任证书jks转bks
keytool -importkeystore -srckeystore clientTrust_ks.jks -destkeystore clientTrust_ks.bks -srcstoretype JKS -deststoretype BKS -srcstorepass server -deststorepass server -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath bcprov-ext-jdk15on-154.jar
服务端证书jks转bks
keytool -importkeystore -srckeystore server_ks.jks -destkeystore server_ks.bks -srcstoretype JKS -deststoretype BKS -srcstorepass server_password -deststorepass server_password -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath bcprov-ext-jdk15on-154.jar
客户端证书jks转bks
keytool -importkeystore -srckeystore client_ks.jks -destkeystore client_ks.bks -srcstoretype JKS -deststoretype BKS -srcstorepass client_password -deststorepass client_password -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath bcprov-ext-jdk15on-154.jar
做完这些操作,目录下应该有这些文件
至此准备工作做完,开始coding
五、Android项目配置
- 服务端代码(如果是使用NanoHttpd,可以copy一份SimpleWebServer测试)
public void init() {
//生成秘钥的manager
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
//加载信任的证书
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
//加载秘钥
KeyStore keyStoreKey = KeyStore.getInstance("BKS");
KeyStore keyStoreTrust = KeyStore.getInstance("BKS");
//这里要把相关证书文件放到res-raw文件夹下,(放到assets下也行,读取方法不一样)
InputStream keyStream = AppContext.getApp().getResources().openRawResource(R.raw.server_ks);
InputStream trustStream = AppContext.getApp().getResources().openRawResource(R.raw.client_trust_ks);
keyStoreKey.load(keyStream, "server_password".toCharArray());
keyStoreTrust.load(trustStream, "server".toCharArray());
//秘钥初始化
keyManagerFactory.init(keyStoreKey, "server_password".toCharArray());
trustManagerFactory.init(keyStoreTrust);
//初始化SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
//设置ServerSocketFactory,需重写相关方法
setServerSocketFactory(new DoubleSecureServerSocketFactory(sslServerSocketFactory, null));
}
//如果是使用NanoHttpd,需要重写SecureServerSocketFactory,将ss.setNeedClientAuth(false);
中的值改为true
,这样就开启了服务端校验客户端功能。
/**
* Creates a new SSLServerSocket
*/
public static class DoubleSecureServerSocketFactory implements ServerSocketFactory {
private SSLServerSocketFactory sslServerSocketFactory;
private String[] sslProtocols;
public DoubleSecureServerSocketFactory(SSLServerSocketFactory sslServerSocketFactory,
String[] sslProtocols) {
this.sslServerSocketFactory = sslServerSocketFactory;
this.sslProtocols = sslProtocols;
}
@Override
public ServerSocket create() throws IOException {
SSLServerSocket ss = null;
ss = (SSLServerSocket) this.sslServerSocketFactory.createServerSocket();
if (this.sslProtocols != null) {
ss.setEnabledProtocols(this.sslProtocols);
} else {
ss.setEnabledProtocols(ss.getSupportedProtocols());
}
ss.setUseClientMode(false);
ss.setWantClientAuth(false);
ss.setNeedClientAuth(true);
return ss;
}
}
- 客户端代码
以okhttp为例:
/**
* 关联Https请求验证证书
*/
public static SSLSocketFactory getSSLContext(Context context) {
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
//生成信任证书Manager,默认系统会信任CA机构颁发的证书,自定的证书需要手动的加载
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
KeyStore keyStoreKey = KeyStore.getInstance("BKS");
KeyStore keyStoreTrust = KeyStore.getInstance("BKS");
InputStream keyStream= AppContext.getApp().getResources().openRawResource(R.raw.client_ks);
InputStream trustStream= AppContext.getApp().getResources().openRawResource(R.raw.server_trust_ks);
//加载client端密钥
keyStoreKey.load(keyStream, "client_password".toCharArray());
//信任证书
keyStoreTrust.load(trustStream, "client".toCharArray());
keyManagerFactory.init(keyStoreKey, "client_password".toCharArray());
trustManagerFactory.init(keyStoreTrust);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null/* new SecureRandom()*/);
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
return sslSocketFactory;
} catch (CertificateException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (UnrecoverableKeyException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
val sslSocketFactory = HttpsUtils.getSSLContext(context)
val okHttpClient = OkHttpClient.Builder()
// .connectionSpecs(Collections.singletonList(spec))
.sslSocketFactory(sslSocketFactory, HttpsTrustManager() )
.hostnameVerifier(object : HostnameVerifier {
override fun verify(
hostname: String?,
session: SSLSession?
): Boolean {
//这里可以做域名检验
return true
}
})
.build()
val request: Request = Request.Builder().url(url).build()
okHttpClient.newCall(request).enqueue(object : Callback {
override fun onResponse(
call: Call,
response: Response
) {
val body = response.body
if (response.isSuccessful) {
Log.e("request", "success:${body?.string()}")
} else {
Log.e("request", "error,statusCode=${response?.code},body=${body?.string()}")
}
}
override fun onFailure(
call: Call,
e: IOException
) {
Log.e("request", "error:${e.toString()}")
e.printStackTrace()
}
})
完整代码