众所周知,iOS9已经开始在联网方面默认强制使用Https替换原来的Http请求了,虽然Http和Https各有各的优势,但是总得来说,到了现在这个安全的信息时代,开发者已经离不开Https了。
网上有很多搭建Https的教程,但是比较零散,Web浏览器端和移动端具体部署也不是特别明确,如果真的用于项目中,还需要折腾一番,本人直接来个项目级别的Demo。
双向Https验证参考:Tomcat双向Https验证搭建,亲自实现与主流浏览器、Android/iOS移动客户端超安全通信
在开始之前,我总结一下keytool这个证书工具需要处理的几种常见后缀格式的意义:
jsk/keystore, 表示一个密钥库,里面可以包含多个密钥条目(证书),密钥条目(证书)还可以分私有的和信任的等,私有的一般包括私钥、公钥和密钥条目信息,信任的一般包括公钥和密钥条目信息(公钥证书)。打开密钥库需要一个密码,同时打开每个私有密钥条目也需要一个密码(但一般建议将打开私有密钥条目的密码设置跟打开密钥库密码相同,省的弄乱了,以下我的Demo演示是设置相同的),做过给安卓apk签名打包的一定能体会到这个。
csr/certreq,证书请求文件,你把这个提交给CA,CA会给你颁发cer格式的含有公钥和密钥条目信息的证书(公钥证书)给你。
cer,用于存储某个密钥条目(证书)的公钥文件,一般你提交了csr/certreq给CA后,CA会颁发给你,你也可以通过自签名的CA颁发,如果你已经有密钥条目(证书)在密钥库里,也可以从jsk/keystore中的某个密钥条目(证书)导出其公钥和密钥条目内容的证书(公钥证书)。
综上,其实最简单的理解就是密钥库就相当于SQL数据库,各种密钥条目(证书)就相当于SQL数据库表 ,一个SQL数据库表其实跟其它的表又有父子(外键)关系的,这种关系叫做密钥条目(证书)的密钥链。为了描述更加方便,以下将“密钥库”描述词叫做《证书库》,“密钥条目”描述词叫做《证书》,将“cer格式的公钥和密钥条目内容的证书”叫做《公钥证书》。
接下来开始演示Demo示例:
1、生成服务器端证书库和证书:(生成服务器端证书库和证书可以有多种方式,推荐通过走第三方CA方式,这样生成的证书以后更具有保障性和安全性(尤其是对Web客户端,可以启动“绿色地址栏/安全锁 地址栏显示单位名称 EV国际认证标识”等等))
1-1-1、方式一、使用keytool,生成自签名的CA证书和自签名的server证书(下面生成的CA是自签名的,当然下面生成的server也是自签名的,这些证书在浏览器上使用绝对不会出现绿条):
1.生成自签名CA:keytool -genkey -v -alias ca -keyalg RSA -keystore D:\ca_cert_lib.jks -validity 3650
2.生成服务器证书:keytool -genkey -v -alias server -keyalg RSA -keystore D:\server_cert_lib.jks -validity 365
注意证书名叫ca定义为自签名的CA证书,证书名叫server定义为服务器证书,它们分别保存在证书库路径为 D:\ca_cert_lib.jks 和 D:\server_cert_lib.jks 中
之所以要分自签名的CA证书和server服务器证书,是因为正常情况下我们的server服务器证书是需要向第三方CA申请的,第三方CA会用它的根证书给你生成一份公钥证书(这个过程叫做第三方CA给你签名),而此处就是要自导自演展示自签名的CA给server证书签名这个过程
1-1-2、用自签名的CA给server签上CA的签名(server本身也是自签名的,下面要做的相当于将server的自签名换成CA的签名,也许你会问CA的签名是谁的,CA也可以是别人的,比如如果沃通愿意给你的CA签名的话,那么CA的颁发者就是沃通,我这里的Demo演示没有权威机构给它签名,所以我这个CA就是自己给自己签名的,这个CA其实就是ROOT证书,只不过不会被任何客户端信任(如:浏览器等)而已,即用我这个CA签发的所有server服务器证书在任何浏览器上绝对不会出现绿条):
在给server签名之前,查看一下当前证书库情况,它们的确都是各自给自己签名的:
keytool -list -v -keystore D:\ca_cert_lib.jks
keytool -list -v -keystore D:\server_cert_lib.jks
现在使用自签名CA给server签名(如果你要沃通CA给你server签名,就把下面的csr交给沃通):
1.生成server的证书请求文件:keytool -certreq -alias server -keystore D:\server_cert_lib.jks > D:\server.csr (linux上:keytool -certreq -alias server -keystore <路径>/server_cert_lib.jks | tee <路径>/server.csr)
2.使用自签名的CA对server的证书请求文件进行签名颁发服务器server.cer公钥证书:keytool -gencert -alias ca -keystore D:\ca_cert_lib.jks -infile D:\server.csr -outfile D:\server.cer
3.生成自签名CA的公钥文件:keytool -export -alias ca -keystore D:\ca_cert_lib.jks -rfc -file D:\ca.cer
此时可以先查看以下ca.cer和server.cer公钥证书具体内容(注意ca.cer是自签名CA的公钥文件,其颁发者还是它自己,而server.cer是server服务器的公钥文件,其颁发者是自签名的CA,两者是有本质区别的,下面安装回复后可以看到这个区别),不过其实他们都是个Base64过的字符串:
keytool -printcert -rfc -file D:\ca.cer
keytool -printcert -rfc -file D:\server.cer
安装证书回复(回复这个翻译也许不太好,反正这个意思就是:将CA颁发的cer公钥证书安装到server服务器端证书库,前提条件是CA的cer公钥证书也需要先被安装):
1.先安装CA的公钥证书(这步不可以少,否则下面的证书回复没法安装):keytool -importcert -alias ca -keystore D:\server_cert_lib.jks -file D:\ca.cer
2.安装server的公钥证书(安装证书回复(被CA签名过的)):keytool -importcert -alias server -keystore D:\server_cert_lib.jks -file D:\server.cer
此时再查看下服务器server证书:keytool -list -v -keystore D:\server_cert_lib.jks -alias server
这时发现这个server证书变化挺大的,一是证书连变长了,变成2了,这个server证书附带了上一级证书SELF CA ROOT CERT的信息,其次是server的发布者变成了SELF CA ROOT CERT,这也就是说明成功的使用自签名的CA给server签名成功了
1-2-1、方式二、通过权威CA(第三方SSL证书机构)生成,如通过沃通生成免费/收费服务器端证书库和证书,CA生成的证书更具有保障性,最直观的表现是客户端用Web浏览器访问该Https网站时会有绿色标识(当然要显示越华丽就得给权威CA交更多的钱)如github:,以下演示使用沃通申请免费的DV SSL证书。
1-2-1、登陆沃通,申请一个免费的DV SSL证书。
1-2-2、申请需要先绑定域名
1-2-3、申请完后需要验证域名,验证域名这个事就自己去搞定吧
1-2-4、上面用自签名的CA给server证书签名已经提到了如何生成csr文件,此处通过提交证书申请文件csr申请的步骤略。以下演示在线生成,即本次讲的通过沃通CA自动生成公钥证书,顺便把server服务器证书库也一并生成好并将公钥证书导入到这个证书库,此处输入的密码实际上既是server服务器证书库密码也是server服务器证书(此种方式生成的证书名字叫做1,这个1对应上面自签名CA导入的server证书)的密码
输入密码生成证书之后就可以下载沃通CA颁布给你server服务器端用的证书库和证书了,然后部署到对应的服务器程序中,本案例部署到tomcat,为了保持统一性和直观性此处将沃通CA颁发的证书库名andy5.me.jks改名为server_cert_lib.jks
一般通过此时生成的证书名字(alias)叫做:1,对应自签名CA方式中的server证书
因此你拿到了上面的沃通颁发的证书后,你还可以继续颁发给别人,这些你颁发的证书都是可信任的,因为沃通上面的根证书一定是可信任的,不然沃通本身就是不可以信任的。
2、通过上面的步骤,已经得到了含有CA颁发的证书的证书库server_cert_lib.jks了,接下来,给服务器程序tomcat配置证书库(可以理解为给服务器端安装server证书库)
2-1、在tomcat的安装目录/conf/server.xml中配置和启用以下port为8443的Connector
<Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol"
maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS"
keystoreFile="D:\\server_cert_lib.jks" keystorePass="s123456"
/>
3、接下来,要操作客户端了(这里的客户端包括多种:1是主流浏览器、2是Android、3是iOS)
3-1、先从jks格式的服务器端证书库server_cert_lib.jks导出cer格式的服务器端公钥证书server.cer。
keytool -keystore D:\server_cert_lib.jks -export -alias server -file D:\server.cer
3-2、将server.cer服务器端公钥证书导入到客户端信任证书库(这个实现不能用keytool导入了,而是要根据具体的各个客户端平台进行实现,此步骤可以理解为给客户端安装服务器端公钥证书)
3-2-1、Web浏览器实现:使用浏览器安装服务器端server.cer公钥证书
3-2-1-1、IE浏览器,直接双击server.cer,然后在证书存储——将所有证书放入下列存储,选择受信任的根证书颁发机构
3-2-1-2、火狐浏览器,火狐没办法直接将来自自签名CA颁发的server.cer公钥证书导入到证书机构中,因为火狐会检查你的服务器公钥证书server.cer的最高一级颁发者是不是权威机构(权威第三方CA),不是的话,不会通过的,比如你在火狐选项——高级——证书——查看证书——服务器/证书机构:导入server.cer,会提示无法导入,因此解决方法是不用导入server.cer,而是在服务器选项卡中添加列外地址(显然并不安全),如果来自权威CA颁发的server.cer,则可以直接导入。
3-2-2、Android客户端实现
3-2-2-1、将服务器端公钥证书server.cer放入安卓assert目录,然后使用HttpsUtil.getSslSocketFactory进行初始化(该工具类的方法具体实现见后面的Demo代码)
private void initHttpsEngine(boolean isSelfCa) { try { // 初始化服务器端公钥证书,得到SSLSocketFactory SSLSocketFactory sslSocketFactory = HttpsUtil.getSslSocketFactory(new InputStream[]{getAssets().open ("server.cer")}); OkHttpClient.Builder builder = new OkHttpClient.Builder(); if (isSelfCa) { /** * 注意;如果你的server.cer是来自自签名CA颁发的,那么就要设置下面的customVerifier,主要是为了解决报以下异常, * 即跳过Hostname www.andy5.me在CA上的验证,如果你的server.cer是来自第三方SSL权威机构颁发的,不用设置这个customVerifier * * javax.net.ssl.SSLPeerUnverifiedException: Hostname www.andy5.me not verified: * certificate: sha1/EnrjjhNxjvuDkO/rJqPmJ9XaIMs= * DN: CN=Andy Wu(www.andy5.me),OU=Andy5 Server,O=www.andy5.me,L=Guangzhou,ST=Guangdong,C=CN * subjectAltNames: [] */ HostnameVerifier customVerifier = new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { // 指定SERVER_URL一定可以通过 if (SERVER_URL.equalsIgnoreCase(hostname)) { return true; } else { // 使用默认的OkHostnameVerifier进行验证 return OkHostnameVerifier.INSTANCE.verify(hostname, session); } } }; mOkHttpClient = builder.sslSocketFactory(sslSocketFactory).connectTimeout(30, TimeUnit.SECONDS) .hostnameVerifier(customVerifier).build(); } else { mOkHttpClient = builder.sslSocketFactory(sslSocketFactory).connectTimeout(30, TimeUnit.SECONDS).build(); } } catch (IOException e) { e.printStackTrace(); } }
3-2-2-2、可以在任何地方对该服务器发起Https请求了,如果是自签名CA签发的服务器端server证书,需要忽略域名验证才能正常通信(具体看Demo代码),显然也是不安全的。
public void testHttps(View v) { if (mOkHttpClient == null) { Toast.makeText(getApplicationContext(), "请先初始化客户端密钥库和服务器端公钥!", Toast.LENGTH_SHORT).show(); return; } mTvResult.setText("正在从 " + HTTPS_SERVER_URL + " 获取数据...."); mWvResult.loadData("", "text/html", "UTF-8"); Request request = new Request.Builder().url(HTTPS_SERVER_URL).build(); Call call = mOkHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, final IOException e) { e.printStackTrace(); runOnUiThread(new Runnable() { @Override public void run() { mTvResult.setText("从 " + HTTPS_SERVER_URL + " 获取数据失败!\n" + e); } }); } @Override public void onResponse(Call call, Response response) throws IOException { final String html = response.body().string(); runOnUiThread(new Runnable() { @Override public void run() { mTvResult.setText("从 " + HTTPS_SERVER_URL + " 获取数据成功!"); mWvResult.loadData(html, "text/html", "UTF-8"); } }); } }); }
3-2-2-3、成功请求界面如下,软件环境:MIUI 6(Android 4.4.2) + AS 1.5.1