首页   

Android+Nginx一步步配置https单向/双向认证请求

JaydenZhou1  ·  · 4 年前
阅读 59

Android+Nginx一步步配置https单向/双向认证请求

  最近想实现一个旧项目https的防抓包功能,重新学习并且配置了下https相关通信知识,参考了不少文章,有些文章比较旧不全或者有误,走了不少弯路和坑,所以整理出来方便自己巩固以及供大家参考,指出不足或者有误之处互相学习。   本文的服务端环境:

Debian 9.8
Nginx
复制代码

原创文章,欢迎转载,转载请注明:ifish.site 作者:JaydenZhou

一、需要的前置知识点

  本文需要的前置知识点,这也是我刚接触时候绕得很晕的问题,自己前后端流程走一遍后,就清晰多了。

网络相关的知识点: https通信,CA发证机构,自签发机构,公钥,私钥,数字证书,数字签名、相关cer,pem,scr,key等关键字定义。

Android相关的知识点: 如何发起网络请求,如何设置单向/双向认证ssl,如何把一些数字证书转成Android识别的bks格式证书等。

后端相关知识点: Liunx基本知识;如何域名解析(或直接ip)访问;https的CA证书/自签名证书如何生成;如何配置nginx中的https访问;

  https的通信,是从非对称加密(RSA)到对称加密(AES)的一个过程,其中数字证书扮演着重要作用。 借助文章:juejin.im/post/5c9cbf… 里面所讲,

数字证书  = 公钥 + 签名 + 申请者和颁发者的信息
签名 = 私钥 + 信息摘要(hash处理过的不可逆的明文信息)
复制代码

类比于我们的身份证(数字证书) = 证件号(公钥) + 公安盖章(签名) + 个人姓名/发证公安局(申请者和颁发者的信息)。 私钥一般以.key结尾,用它才能跟对应的数字证书(公钥)互相解密。

二、单向/双向认证的应用场景在哪里呢?

单向认证: 这里有个简单的理解,凡是你可以直接访问的网站(比如我的域名: ifish.site ); 直接请求的https的api等,都是单向认证,因为它只需要client端能够解密出server端的数字证书(分CA和自签发的),操作系统或者浏览器一般都内置了一堆相关CA的证书,所以可以直接访问;若是自签发的,浏览器会提示该证书不受信任。适用场景是站点访问,非高机密数据传输。 双向认证: 顾名思义,就是在单向基础上,添加上了服务端要校验客户端的公钥,客户端要自己保存着自己的私钥来加密,客户端发过来的请求要用该私钥来加密后,才能跟服务器进行完整通信。适用场景:企业间对应机密api接口的数据传输。

三、证书的生成

  配置好Linux环境后,首先我们要生成对应的证书,两种方法如下:

方法一:用CA签的证书(有收费 or 免费),这样浏览器就不会显示不信任提示,前提是要有合法的境内实名域名,然后比如在阿里云服务器管理后台界面进行证书的申请。

方法二:用openssl在自己服务器上,制作自签发的证书,浏览器也可以访问,但是会有不信任提示。

方法一按照对应服务商的提示来操作就行,这里讲下openssl来生成的方法,先抛出一个我自己现在也疑惑的问题: 为何要生成root根CA证书,然后再发布二级server和client证书? 是为了一个根证书可以直接管理多个二级证书? 本文为了简化演示https单向/双向认证,只需要生成对应的 server 和 client 相关证书就行,避免文件太多导致像我这样的新手造成的困惑和配置出错。 1.生成服务端key:

openssl genrsa -out server-key.key 1024
复制代码

2.生成服务端证书请求文件(这步很关键,弹出信息填写提示时候,“Common Name”一定要填写你自己的域名,其他的可以直接回车):

openssl req -new -out server-req.csr -key server-key.key
复制代码

比如我的域名是 ifish.site

name

3.生成服务端证书cer:

openssl x509 -req -in server-req.csr -out server-cert.cer -signkey server-key.key  -CAcreateserial -days 3650
复制代码

4.生成客户端key(同上面方法一样):

openssl genrsa -out client-key.key 1024
复制代码

5.生成服务端证书请求文件(Common Name最好一致):

openssl req -new -out client-req.csr -key client-key.key
复制代码

6.生成客户端证书cer:

openssl x509 -req -in client-req.csr -out client-cert.cer -signkey client-key.key -CAcreateserial -days 3650
复制代码

7.生成客户端带密码的p12证书(这步很重要,双向认证的话,浏览器访问时候要导入该证书才行;Android请求的时候也需要把它转成bks来请求双向认证):

openssl pkcs12 -export -clcerts -in client-cert.cer -inkey client-key.key -out client.p12
复制代码

四、nginx配置

1.将生成的证书,为了方便管理,建议放到nginx相同目录下,比如我是放到“/usr/local/nginx/conf/ssl_cust” 里面; 2.打开对应的 conf 里面要https访问的域名,如果之前有配过443端口,那么只需要指定下对应的证书即可,其中

单向认证是:
ssl_certificate      ssl_cust/server-cert.cer;
ssl_certificate_key  ssl_cust/server-key.key;
复制代码
双向认证是:
ssl_certificate      ssl_cust/server-cert.cer;
ssl_certificate_key  ssl_cust/server-key.key;
ssl_client_certificate   ssl_cust/client-cert.cer;
ssl_verify_client    on;
复制代码

3.若完全没有配过https的话,可以参考我的配置:

server
    {
        listen       443 ssl;
        server_name  ifish.site www.ifish.site;
        ssl on;
        ssl_certificate      ssl_cust/server-cert.cer;
        ssl_certificate_key  ssl_cust/server-key.key;
# 双向认证一般不开启, #是注释掉
#        ssl_client_certificate   ssl_cust/client-cert.cer;
#        ssl_verify_client    on;
        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;
    }
复制代码

4.保存关闭后,执行以下命令让nginx重启生效:

sudo nginx -s reload
复制代码

5.如此我们配置就生效了,可以用浏览器来验证下,如果只是单向认证,直接浏览器输入域名来访问即可,会有不安全提示; 若是双向认证,直接访问会出现 400 Bad Requst, 需要我们手动添加证书,这里是Chrome下的截图,我们需要添加之前我们生成的 client.p12 文件。

img

五、Android代码请求

  终于到Android请求的代码写法了,为了简化Demo的独立访问,这里引入了 xUtils库 (github.com/wyouflf/xUt… ),当然你也可以自己手写或者用比如Retrofit、OkHttp等网络库。 单向认证的写法:   其实单向认证,用xUtils的话可以不需要设置setSslSocketFactory内容,因为库代码DefaultParamsBuilder.java里面判断了如果没有设置自定义ssl,那么就直接用操作系统自带的进行返回,从而来访问https。   我们这里为了演示下具体代码,所以自定义一个ssl的构造出来,操作如下: 把服务端server-cert.cer证书放到 assets 目录下,然后创建一个 SSLHelper.java 的辅助类:

public static SSLSocketFactory getSSLSingleFactory(Context context) {
    try {
        CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(null);
        InputStream is = context.getAssets().open("server-cert.cer");
        keyStore.setCertificateEntry("0", certificateFactory.generateCertificate(is));
        if(is != null) {
            is.close();
        }
        SSLContext sslContext = SSLContext.getInstance("TLS");
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(keyStore);
        sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
            return sslContext.getSocketFactory();
    } catch (CertificateException e) {
        e.printStackTrace();
    } catch (KeyStoreException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (KeyManagementException e) {
        e.printStackTrace();
    }
    return null;
}
复制代码

对应的Activity请求里的代码是:

RequestParams params = new RequestParams("https://ifish.site");
params.setSslSocketFactory(SSLHelper.getSSLSingleFactory(this));
// 因为是自签不受权威机构认证,所以绕过不检查域名ssl。
params.setHostnameVerifier(new HostnameVerifier() {
    @Override
    public boolean verify(String hostname, SSLSession session) {
        return true;
    }
});

x.http().get(params, new Callback.CommonCallback<String>() {
    @Override
    public void onSuccess(String result) {
        Log.d(TAG, "onSuccess...result = " + result);
    }
    @Override
    public void onError(Throwable ex, boolean isOnCallback) {
        Log.d(TAG, "onError...ex = " + ex.getMessage());
    }
    @Override
    public void onCancelled(CancelledException cex) { }
    @Override
    public void onFinished() { }
});
复制代码

双向认证的写法: 双向认证要求的是客户端也需要持有一份自己的私钥key、服务端要有一份客户端的公钥证书。但是由于Android系统限制,我们需要把client.p12转成client.bks格式,才能被访问到。介绍一个转化工具,叫做“Portecle”,亲测可用的下载和使用链接如下:blog.csdn.net/zhangyong12… 可以用 java -jar protecle.jar 来运行,然后按照上文链接的使用方法,把对应的 client.p12转成client.bks格式。 对应的 SSLHelper.java添加一个双向认证方法:

public static SSLSocketFactory getSSLDoubleFactory(Context context) {
    try {
        CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(null);
        InputStream is = context.getAssets().open("server-cert.cer");
        keyStore.setCertificateEntry("0", certificateFactory.generateCertificate(is));
        if(is != null) {
            is.close();
        }
        SSLContext sslContext = SSLContext.getInstance("TLS");
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(keyStore);

        // 初始化双向客户端keyStore
        KeyStore clientKeyStore = KeyStore.getInstance("BKS");
        clientKeyStore.load(context.getAssets().open("client.bks"), "123456".toCharArray());
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyManagerFactory.init(clientKeyStore, "123456".toCharArray());
        sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
        return sslContext.getSocketFactory();
    } catch (CertificateException e) {
        e.printStackTrace();
    } catch (KeyStoreException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (UnrecoverableKeyException e) {
        e.printStackTrace();
    } catch (KeyManagementException e) {
        e.printStackTrace();
    }
    return null;
}
复制代码

然后Activity里面,params.setSslSocketFactory(SSLHelper.getSSLSingleFactory(this)); 换成 params.setSslSocketFactory(SSLHelper.getSSLDoubleFactory(this)); 即可。

六、总结

疑难点: 1.涉及的知识点比较多,很多不是Android本身的东西; 2.https单向/双向原理不太好理解,可以看该文https://juejin.im/post/5c9cbf1df265da60f6731f0a; 3.仅验证单双向认证来说,没必要生成根CA证书,部分文章生成太多证书会导致配置上容易乱。

调试验证技巧:   原Android网络请求框架庞大,对应的域名是生产环境的域名,绝对不能随便动后台的生产环境配置。因此需要自己的一台服务器,自己搭建一个简单的后台Demo https请求,以及Android的Demo网络请求app,从而方便Debug。

参考: www.zhihu.com/question/29… -- SSL中,公钥、私钥、证书的后缀名都是些啥? juejin.im/post/5c9cbf… -- 扯一扯HTTPS单向认证、双向认证、抓包原理、反抓包策略 zhuanlan.zhihu.com/p/60392573 -- 为了抓包某app,我折腾了10天,原来他是用SSL Pinning防抓包的 www.cnblogs.com/yelao/p/948… -- Nginx https 双向认证 blog.csdn.net/jsc702325/a… -- Android 用自签名证书实现https请求 www.cnblogs.com/guogangj/p/… -- 那些证书相关的玩意儿(SSL,X.509,PEM,DER,CRT,CER,KEY,CSR,P12等) blog.csdn.net/zhangyong12… -- P12证书转BKS证书

推荐文章
FM93交通之声  ·  5分钟!26人!1条命!  ·  10 月前  
最神奇的视频  ·  美翻了!-20191218160756  ·  4 年前  
© 2022 51好读
删除内容请联系邮箱 2879853325@qq.com