HTTP转免费且安全的HTTPS之路

背景

现有系统是通过http与后端接口交互且使用的是对称秘钥加密用户密码且服务端校验密码通过后也是通过对称密码加密Token(用来客户端签名请求数据)返回给Web前端或APP端,由于秘钥保存在web前端或APP端那都可以通过阅读代码直接获取,故间接暴露用户密码与Token。

思路

  • 方案一:将对称密码改成非对称密码,web端与app端写死公钥,并通过公钥加密用户密码并在客户端生成Token也加密传给服务端。
    1. 可保证请求数据的完整与保密,但无法保证响应数据的真实与保密(比如使用Fiddler就可以实现请求拦截并修改响应数据)
    2. 变更公私钥时需要同步变更前端代码,且无法实现版本兼容
    3. 只加密请求时的重要数据,请求耗时短
  • 方案二:使用HTTPS
    1. 可保证服务端的真实性(不能被假冒,除非客户端信任了非CA颁发的恶意证书)
    2. 可保证客户端的真实性(同一个会话中不会出现不同客户端)
    3. 可保证数据内容的不被篡改与泄露
    4. 请求与响应的数据都被加密,请求耗时长

其实方案一与方案二都有各种的优劣,可以根据系统特性来选择。由于咱们系统会涉及到资金,就选择了HTTPS。

概念

  • TCP(摘抄自:https://baijiahao.baidu.com/s?id=1638084913643758145&wfr=spider&for=pc
    TCP是面向连接的协议,为用户进程提供可靠的全双工字节流。通过这种方式,可以确保可靠有序的数据包,并且可以支持流量控制。

    1. OSI网络图层


      image.png
    2. 各层数据组装


      image.png
    3. TCP协议数据结构
      屏幕截图 2020-04-20 21.43.38.png

      3.1. Seq(上图中序号)表示接收方处理数据包的开始序号
      3.2. Ack(上图中确认序号)表示即将处理接收方数据包的开始序号
      Seq与Ack的理解:打个比方就是两个人协作工作,每个人都说你帮我从A开始弄,我给你从B开始搞。然后每隔一段时间再次沟通怎么搞。
    4. TCP协议生命周期


      image.png
  • SSL(摘抄自:https://cloud.tencent.com/developer/article/1425339
    SSL由Netscape公司于1994年创建,它旨在通过Web创建安全的Internet通信。它是一种标准协议,用于加密浏览器和服务器之间的通信。它允许通过Internet安全轻松地传输账号密码、银行卡、手机号等私密信息。

    1. SSL证书:由受信任的CA机构颁发的遵守SSL协议的数字证书
    2. SSL证书使用


      image.png

      客户端在接受到服务端发来的SSL证书时,会对证书的真伪进行校验,以浏览器为例说明如下:
      (1)首先浏览器读取证书中的证书所有者、有效期等信息进行一一校验;
      (2)浏览器开始查找操作系统中已内置的受信任的证书发布机构CA,与服务器发来的证书中的颁发者CA比对,用于校验证书是否为合法机构颁发;
      (3)如果找不到,浏览器就会报错,说明服务器发来的证书是不可信任的;
      (4)如果找到,那么浏览器就会从操作系统中取出 颁发者CA 的公钥,然后对服务器发来的证书里面的签名进行解密;
      (5)浏览器使用相同的hash算法计算出服务器发来的证书的hash值,将这个计算的hash值与证书中签名做对比;
      (6)对比结果一致,则证明服务器发来的证书合法,没有被冒充;
      (7)此时浏览器就可以读取证书中的公钥,用于后续加密了;

  • HTTPS(摘抄自:https://cloud.tencent.com/developer/article/1425339
    HTTPS是HTTP的安全版本,它可以通过SSL / TLS连接保护在线传输的任何通信。简而言之,HTTPS=HTTP+SSL。如果想要建立HTTPS连接,则首先必须从受信任的证书颁发机构(CA)注册 SSL证书。安装SSL证书后,网站地址栏HTTP后面就会多一个“S”,还有绿色安全锁标志。

    1. HTTPS的交互图


      image.png

      (1)看蓝色的部分是tcp链接。所以https的加密层也是在tcp之上的。
      (2)客户端首先发起clientHello消息。包含一个客户端随机生成的random1 数字,客户端支持的加密算法,以及SSL信息。
      (3)服务器收到客户端的clientHello消息以后,取出客户端法发来的random1数字,并且取出客户端发来的支持的加密算法,
      然后选出一个加密算法,并生成一个随机数random2,发送给客户端serverhello让客户端对服务器进行身份校验,服务端通过将自己的公钥通过数字证书的方式发送给客户端。
      (4)客户端收到服务端传来的证书后,先从 CA 验证该证书的合法性,验证通过后取出证书中的服务端公钥,再生成一个随机数 Random3,再用服务端公钥非对称加密 Random3 生成 PreMaster Key。并将PreMaster Key发送到服务端。
      (5)服务端通过私钥将PreMaster Key解密获取到Random3,此时客户端和服务器都持有三个随机数Random1 Random2 Random3,双方在通过这三个随即书生成一个对称加密的密钥.双方根据这三个随即数经过相同的算法生成一个密钥,而以后应用层传输的数据都使用这套密钥进行加密。Change Cipher Spec Finished:告诉客户端以后的通讯都使用这一套密钥来进行。
      (6)最后ApplicationData 全部使用对称加密的原因就是非对称加密太卡,对称加密不影响性能。所以实际上也看的出来,HTTPS的真正目的就是保证对称加密的 密钥不被破解,不被替换,不被中间人攻击,如果发生了上述情况,那么HTTPS的加密层也能获知,避免发生事故。

部署HTTPS服务端

  • SSL证书颁发机构
    Let’s Encrypt 是一家免费、开放、自动化的证书颁发机构(CA),为公众的利益而运行。它是一项由 Internet Security Research Group(ISRG)提供的服务。
  • 申请SSL证书(摘抄自:https://my.oschina.net/u/2306127/blog/1929904
    # 以ubuntu18.04为例(用的阿里云,有的依赖已有就不安装了)
    # 安装Git 和bc ,并从github上将代码克隆到本地
    sudo apt install git bc
    sudo git clone https://github.com/certbot/certbot /opt/certbot-master
    # 停掉nginx(因为需要用到80端口连接验证)
    sudo service nginx stop
    # 安装过程中会设置邮箱地址,邮箱地址是用来接收紧急通知和找回密钥的
    sudo /opt/certbot-master/letsencrypt-auto certonly --standalone -d 域名
    # 启用更安全的加密方式
    # 默认是 SHA-1 形式,而现在主流的方案应该都避免 SHA-1,为了确保更强的安全性,我们可以采取迪菲-赫尔曼密钥交换。
    openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048
    
  • 配置SSL证书(同上)
    # 在Nginx的域名配置文件中添加
    server {
        #nginx 监听端口,443为默认https端口,ssl指使用https,如果多处监听了80则将 default删除
        listen 80 default backlog=2048;
        listen 443 ssl;
        # 服务器名称
        server_name bnxb.com;
        # https证书公钥
        ssl_certificate /etc/letsencrypt/live/bnxb.com/fullchain.pem;
        # https证书私钥 要注意保存!
        ssl_certificate_key /etc/letsencrypt/live/域名/privkey.pem;
        # 支持的加密协议
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        #nginx默认会使用Diffiel-Hellman交换密钥是1024位的,相对不安全,所以需要替换使用更安全的
        ssl_dhparam /etc/ssl/certs/dhparam.pem;
        # 支持的加密套件
        ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
        # 定义session过期时间
        ssl_session_timeout 1d;
        ssl_session_cache shared:SSL:50m;
        ssl_stapling on;
        ssl_stapling_verify on;
        # 如果是全站 HTTPS 并且不考虑 HTTP 的话可以为响应头添加要求浏览器使用https重定向的 header
        add_header Strict-Transport-Security max-age=15768000;
        # 禁止外部站点iframe
        add_header X-Frame-Options DENY;
        
        # 以下就是自身服务的配置
        ...
    }
    
  • 续期SSL证书(同上)
    # 手动续期
    sudo ./opt/certbot-master/letsencrypt-auto renew --force-renewal
    # 自动续期
    touch renewCerts.sh
    sudo chmod +x renewCerts.sh
    # 将以下脚本内容添加到renewCerts.sh中
    #!/bin/sh
    # This script renews all the Let's Encrypt certificates with a validity < 30 days
    if ! /opt/certbot-master/letsencrypt-auto renew > /var/log/letsencrypt/renew.log 2>&1 ; then
        echo Automated renewal failed:
        cat /var/log/letsencrypt/renew.log
        exit 1
    fi
    nginx -t && nginx -s reload
    
    # 配置系统定时执行脚本
    crontab -e
    # 每个月一号凌晨执行
    # m h  dom mon dow   command
        0 0  1   *    *    sh /root/renewCerts.sh >/dev/null 2>&1 &
    
    1587397401(1).png

简单HTTPS客户端

强烈建议利用Wireshark查看TCP生命周期中的网络包信息,利用Fiddler拦截HTTP与HTTPS请求并篡改响应数据玩玩。以下是Java版本的简化版https客户端,可以进一步了解https的代码实现。

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509TrustManager;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;
import java.security.GeneralSecurityException;
import java.security.cert.X509Certificate;

public class HttpsTest {

    public static void main(String[] args) {
        // 初始化SSL上下文
        initSSLContext();

        try {
            // 设置请求的url
            URL url = new URL("https://test.api.xxx.com/manager/login");
            URLConnection urlConn = url.openConnection();
            urlConn.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
            // 设置请求的body内容
            urlConn.setDoOutput(true);
            OutputStreamWriter writer = new OutputStreamWriter(urlConn.getOutputStream());
            writer.write("{userName: \"admin\", password: \"666\"}");
            writer.flush();
            writer.close();

            // 执行Https请求并获取响应数据的输入流
            InputStream is = urlConn.getInputStream();
            // 读取响应信息
            BufferedReader in = new BufferedReader(new InputStreamReader(is));
            String line;
            while ((line = in.readLine()) != null) {
                System.out.println("响应数据:" + line);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 通过HttpsURLConnection提供的静态方法注册默认的SSLSocketFactory与HostnameVerifier
    public static void initSSLContext() {
        // 初始化TLS协议SSLContext
        SSLContext sslContext;
        try {
            sslContext = SSLContext.getInstance("TLS");
            X509TrustManager[] xtmArray = new X509TrustManager[]{xtm};
            sslContext.init(null, xtmArray, new java.security.SecureRandom());
        } catch (GeneralSecurityException gse) {
            throw new RuntimeException("初始化SSL上下文失败!", gse);
        }

        //为javax.net.ssl.HttpsURLConnection设置默认的SocketFactory和HostnameVerifier
        HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
        HttpsURLConnection.setDefaultHostnameVerifier(hnv);
    }

    // 利用内部类定义信任所有服务器证书的证书管理器
    private static final X509TrustManager xtm = new X509TrustManager() {
        public void checkClientTrusted(X509Certificate[] chain, String authType) {}

        public void checkServerTrusted(X509Certificate[] chain, String authType) {
            System.out.println("证书信息: " + chain[0].toString() + ", 认证方式: " + authType);
        }

        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }
    };

    // 利用内部类定义主机校验名校验器
    private static final HostnameVerifier hnv = (hostname, session) -> {
        System.out.println("主机信息:" + hostname);
        return true;
    };

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容