社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  NGINX

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

JaydenZhou1 • 5 年前 • 282 次点击  
阅读 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证书

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/44904
 
282 次点击