Http 与 Https

0.307字数 1255阅读 125

本文主要关注Https的两个核心问题:Https如何加密,以及Https如何保证安全

Https加密过程

Https加密过程直接用下面这张图可以加以说明:

Https加密有三个关键点:

  1. Https传输过程中用到了对称加密和非对称加密。对称加密:通信双方采用相同密钥进行加解密;非对称加密:传输数据采用公钥加密,必须采用公钥对应的私钥才能解密。
    对称加密:

    encrypt(明文,秘钥) = 密文
    decrypt(密文,秘钥) = 明文
    

    非对称加密:

    encrypt(明文,公钥) = 密文
    decrypt(密文,私钥) = 明文
    
  2. Https在握手过程中采用非对称加密协定双方数据传输中使用的密钥,具体过程如图示,Client端拿到Server端的公钥后,生成一个随机密钥,密钥采用公钥加密后传给Server端,Server可以采用私钥解密,至此,Client Server双方都持有了新的数据传输密钥;

  3. 实际的数据传输采用了新的实时生成的密钥进行加密传输,这种数据传输属于对称加密

Https如何保证安全

Https是在Http基础上进行数据传输的协议,其本质是Http层上面加了一个安全层,称之为TLS。TLS也是SSL的升级版。其主要提供三个基本服务:

  1. 加密
  2. 身份验证
  3. 消息完整性校验

加密
详细加密过程在第一节中讲到,通过这种机制保证了握手阶段密钥的协定,通过新的密钥保证了数据传输过程中的安全

身份验证
在TLS握手过程中服务端会提供给客户端它的证书。这个证书可不是随意生成的,而是通过指定的权威机构申请颁发的。服务端如果能够提供一个合法的证书,说明这个服务端是合法的,可以被信任。身份验证过程就是证书链的验证过程。

  1. 客户端获取到了站点证书,拿到了站点的公钥;
  2. 要验证站点可信后,才能使用其公钥,因此客户端找到其站点证书颁发者的信息;
  3. 站点证书的颁发者验证了服务端站点是可信的,但客户端依然不清楚该颁发者是否可信;
  4. 再往上回溯,找到了认证了中间证书商的源头证书颁发者。由于源头的证书颁发者非常少,我们浏览器之前就认识了,因此可以认为根证书颁发者是可信的;
  5. 一路倒推,证书颁发者可信,那么它所颁发的所有站点也是可信的,最终确定了我们所访问的服务端是可信的;
  6. 客户端使用证书中的公钥,继续完成TLS的握手过程。

Https中间人攻击与防范

所谓中间人攻击,指的是整个网络请求被中间人所劫持,Client信任了中间人证书,传输请求被中间人接管,中间人将请求解析之后重新发送给Server端。对于Server端来讲,中间人是实际请求的Client端,对于Client端来讲,中间人是实际请求的Server端。

Charles如何抓HTTPS包,回想一下这个过程,本质就是中间人攻击方式。其核心就是将私有CA签发的数字证书安装到手机中并且作为受信任证书保存,然后Charles接管整个传输过程,实现对数据的完全掌握。

中间人攻击的原因在于:没有对服务端证书及域名做校验或者校验不完整。

App中防范中间人攻击主要要考虑两个地方:

  1. 网络请求中的证书、域名强校验,针对安全性要求比较高的APP,如银行类App,可以采用预制证书的方式,只有服务端证书和本地证书完全一致才能进行网络请求,这种方式还需要考虑到证书过期如何更新问题,具体可以通过网络层面的封装来解决;除了预制证书,还可以考虑对证书和域名的强校验来保证安全性;
  2. WebView中的Https安全问题:WebViewClient中的重载方法, 如果直接忽略SSL错误,可采用handler.proceed();继续加载网络,安全的措施是在收到错误之后强校验证书,再决定是否加载网页
 @Override
 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
            String channel = "";
            ApplicationInfo appInfo = null;
            try {
                appInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
                channel = appInfo.metaData.getString("TD_CHANNEL_ID");
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            }
            if (!TextUtils.isEmpty(channel) && channel.equals("play.google.com")) {
                final AlertDialog.Builder builder = new AlertDialog.Builder(context);
                String message = context.getString(R.string.ssl_error);
                switch (error.getPrimaryError()) {
                    case SslError.SSL_UNTRUSTED:
                        message = context.getString(R.string.ssl_error_not_trust);
                        break;
                    case SslError.SSL_EXPIRED:
                        message = context.getString(R.string.ssl_error_expired);
                        break;
                    case SslError.SSL_IDMISMATCH:
                        message = context.getString(R.string.ssl_error_mismatch);
                        break;
                    case SslError.SSL_NOTYETVALID:
                        message = context.getString(R.string.ssl_error_not_valid);
                        break;
                }
                message += context.getString(R.string.ssl_error_continue_open);

                builder.setTitle(R.string.ssl_error);
                builder.setMessage(message);
                builder.setPositiveButton(R.string.continue_open, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        handler.proceed();
                    }
                });
                builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        handler.cancel();
                    }
                });
                final AlertDialog dialog = builder.create();
                dialog.show();
            } else {
                handler.proceed();
            }
        }