Linux网络http与https

发布于:2025-04-14 ⋅ 阅读:(18) ⋅ 点赞:(0)

应用层协议HTTP

提示
因为现在大多数都是https,所以就用https来介绍http,https比http多了一个加密功能,不影响介绍http。

什么是http

虽然我们说, 应用层协议是我们程序猿自己定的. 但实际上, 已经有大佬们定义了一些现成的, 又非常好用的应用层协议, 供我们直接参考使用. HTTP(超文本传输协议)就是其中之一。
在互联网世界中,HTTP(HyperText Transfer Protocol,超文本传输协议)是一个至关重要的协议。它定义了客户端(如浏览器)与服务器之间如何通信,以交换或传输超文本(如 HTML 文档)。
HTTP 协议是客户端与服务器之间通信的基础。客户端通过 HTTP 协议向服务器发送请求,服务器收到请求后处理并返回响应。HTTP 协议是一个无连接、无状态的协议,即每次请求都需要建立新的连接,且服务器不会保存客户端的状态信息。

域名

在这里插入图片描述

打开浏览器页面,我的默认搜索是百度。
红圈部分是网址,复制粘贴之后,我们无论打开哪个网页,在这个栏目当当中输入https://www.baidu.com/就可以跳转到百度搜索的首页。
这个网址是如何做到这种功能的呢?
网址——>域名——>域名解析——>IP地址

这是因为有域名解析这个功能,将网址转换成ip地址。
Windows打开命令窗口:
在这里插入图片描述
在这里插入图片描述
这里得到一个ip地址,我们输入到浏览器一下:
在这里插入图片描述
也就是说,每个域名都是ip地址,访问每个域名都是通过ip地址。

URL

在百度当中搜索
https://www.helloworld.net/p/4442555963
这一串我们简称为URL,也叫做:统一资源定位符。(网上所有的资源都可以用唯一的一个“字符串”标识获取)
在这里插入图片描述
网络行为
目前我们最常用的网络行为就是把别人的东西拿出来,把自己的东西传上去,我们访问的服务器可以理解为同时连接一台电脑,我们进入这台电脑获取数据或者是上传数据。
url就是搜索目录的存在。

urlencode 和 urldecode

像 / ? : 等这样的字符, 已经被 url 当做特殊意义理解了. 因此这些字符不能随意出现. 比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义。
搜索hello world
https://www.baidu.com/s?tn=15007414_15_dg&ie=utf-8&wd=hello%20world
搜索aaaa+??//: /&bbbb
https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&tn=15007414_15_dg&wd=aaaa%2B%3F%3F%2F%2F%3A%2F%26bbbb&oq=hello%2520world&rsv_pq=eb10b3d000010b64&rsv_t=aa46E6F5%2BL6oqrBlcwAIRYoHHt%2FkY40E5WdjW0FikHG%2B0Wo9hEJLKyWNfAMEq%2BhRJ5K2Pas&rqlang=cn&rsv_dl=tb&rsv_enter=1&rsv_sug3=18&rsv_sug1=15&rsv_sug7=100&rsv_sug2=0&rsv_btype=t&inputT=18981&rsv_sug4=18981

在这里插入图片描述

这里变成了这个样子,说明如果搜索的内容需要这些特殊字符,那么在输入的过程中就会进行转译。
(要求BS双方进行编码和解码)
转义的规则如下:

将需要转码的字符转为 16 进制,然后从右到左,取 4 位(不足 4 位直接处理),每 2 位
做一位,前面加上%,编码成%XY 格式

HTTP 常见 Header

Connection:keep-alive 保持长连接
Content-Type: 数据类型(text/html 等)
Content-Length: Body 的长度
Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
User-Agent: 声明用户的操作系统和浏览器版本信息;
referer: 当前页面是从哪个页面跳转过来的;
Location: 搭配 3xx 状态码使用, 告诉客户端接下来要去哪里访问;
Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;

http请求与响应

http从客户端发到服务器,是怎么发送和接收的呢?
是客户端请求,服务器响应。
请求的结构体:
在这里插入图片描述
这里的\r\n可以看作一种特殊字符,所有的请求内容都是连在一起的,只不过由\r\n隔离起来了而已。
在这里插入图片描述
这是请求的格式。
请求行:
Method是GET/POST,也就是获取与传输。(我们百分之95都是再用这两种命令)
在这里插入图片描述
假设要在网站中输入账号和密码,使用GET,就是通过URL提交的参数。(以问好作为分隔符,右侧是要提交的参数)
注意:参数数量受限,不私秘,会在URL当中体现。
如果用的是POST,那么参数会放在请求正文当中提交。

HTTP Version是http的版本。(1.0,1.1(用的多),2.0)
报头:
里面都是KV属性结构。(里面含有正文长度等等的属性,后面会详细说明)

那么报头的最后一行也是\r\n,怎么才能分辨报头与正文呢?
答案就是,在报头后面加上一个空行。
在这里插入图片描述

同理,请求就要有响应,响应也有自己的结构体:
在这里插入图片描述

Linux输入

telnet www.baidu.com 80
GET / HTTP/1.1
然后按两下回车(因为有空行)
在这里插入图片描述
这里就是报头,报头下面的其他东西就是网页了。
在这里插入图片描述
所以

首先说一下HTTP Version,请求客户端发送给服务器的时候,客户端是什么http版本,服务器就会响应什么http版本。

那么状态码是什么呢?
平时我们访问某个网页,突然蹦出来404,说帖子被删除或者是网页不见了,404就是状态码,后面说的话就是状态码描述符。(字符串版本的描述)

类别 原因短语
1XX Informational(信息性状态码) 接收的请求正在处理
2XX Success(成功状态码) 请求正常处理完毕
3XX Redirection(重定向状态码) 需要进行附加操作以完成请求
4XX Client Error(客户端错误状态码) 服务器无法处理请求
5XX Server Error(服务器错误状态码) 服务器处理请求出错

重定向:分为临时和永久。
临时:例如登录页面会跳转到微信或者是QQ,登录完毕之后就会跳转回去。
永久:例如某个域名不用了,公司创建了个新的域名,但是又需要照顾不知道新域名的老用户,所以就会在旧网站提示当前域名不再使用,将要跳转到新网站。(服务器给浏览器发报文,报头当中的Location会指导浏览器应该去访问新的网站)

(现在可以安装一个抓包软件来进行测试了,推荐Fiddler 或者 Postman)
抓包软件是如何工作的呢?
是客户端将http的请求发给Fiddler,Fiddler再发给服务器,相当于Fiddler是一个代理。

简单实现一个httpserver

预备知识

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
第一个参数是读取哪个套接字
第二个参数是读取到哪里
第三个参数是读取的长度
第四个参数是读取的方式

在这里插入图片描述

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
第四个参数是写入方式

在这里插入图片描述
一个巨大的网页会包含非常多的元素,比如打开淘宝京东,琳琅满目的商品,标注的价格,推荐物品,图片,各种其他功能。——每一个元素都是资源,打开首页又很多图片,其实看到的每一张图片都是浏览器发送的访问请求,然后服务器给响应,通过渲染等等手段显示给浏览器。

一次请求响应一个资源,关闭连接——短连接。(http1.0)
客户端通过TCP与服务器建立链接,发送多个请求返回多个响应,在没有完成这些请求和响应之前不会关闭。——长连接(http1.1)

在网页当中插入图片,是需要知道ContentType。(http内容类型)

我们在访问B站的时候,会提示让我们去登录账号密码,登录之后,未来的几天再次进入B站就不用再去登录了,这是为什么呢?
首先明白一点,HTTP协议默认是无状态的,这意味着服务器不会主动记录或保留客户端(如浏览器)的请求历史信息。每个HTTP请求都是独立的,服务器处理完一个请求后,不会“记住”这个客户端的状态,下一次请求时服务器会将其视为全新的请求。
因为http对登录用户有会话保持功能。
原理version:(文件级)
在这里插入图片描述
这里就算是关闭浏览器再打开也可以进去,因为信息被保存在文件里。
在这里插入图片描述
在这里插入图片描述
Cookie是会自动消除的。

内存级如果关闭浏览器,再次打开B站就不会自动登录了。

可如果黑客窃取了cookie文件,就会造成巨大损失。(账号密码被盗,个人隐私泄露)
解决方案:
在这里插入图片描述
也就是说,就算黑客拿到了cookie也是ID,如果在自己的机器登录会被B站检测到,因为B站的session文件中有登录信息,如果IP不同就会让黑客登录账号与密码。(一定程度上避免了信息泄露)

代码实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <!-- <form action="/a/b/hello.html" method="post">
        name: <input type="text" name="name"><br>
        password: <input type="password" name="passwd"><br>
        <input type="submit" value="提交">
    </form> -->
    <h1>这个是我们的首页</h1>
     <!-- <img src="/image/1.png" alt="这是一直猫" width="100" height="100"> 根据src向我们的服务器浏览器自动发起二次请求 -->
    <!-- <img src="/image/2.jpg" alt="这是花"> -->
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>这是第二张网页</h1>
    <h1>这是第二张网页</h1>
    <h1>这是第二张网页</h1>
    <h1>这是第二张网页</h1>
    <h1>这是第二张网页</h1>
    <h1>这是第二张网页</h1>
    <h1>这是第二张网页</h1>
    <a href="http://120.78.126.148:8899">回到首页</a>

    <a href="http://120.78.126.148:8899/x/y/world.html">到第三张网页</a>
</body>
</html>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>这是第3张网页</h1>
    <h1>这是第3张网页</h1>
    <h1>这是第3张网页</h1>
    <h1>这是第3张网页</h1>
    <h1>这是第3张网页</h1>
    <h1>这是第3张网页</h1>

    <a href="http://120.78.126.148:8899">回到首页</a>
    <a href="http://120.78.126.148:8899/a/b/hello.html">到第二张网页</a>
</body>
</html>
#pragma once

#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

#define SIZE 1024

#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4

#define Screen 1
#define Onefile 2
#define Classfile 3

#define LogFile "log.txt"

class Log
{
public:
    Log()
    {
        printMethod = Screen;
        path = "./log/";
    }
    void Enable(int method)
    {
        printMethod = method;
    }
    std::string levelToString(int level)
    {
        switch (level)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        default:
            return "None";
        }
    }

    void printLog(int level, const std::string &logtxt)
    {
        switch (printMethod)
        {
        case Screen:
            std::cout << logtxt << std::endl;
            break;
        case Onefile:
            printOneFile(LogFile, logtxt);
            break;
        case Classfile:
            printClassFile(level, logtxt);
            break;
        default:
            break;
        }
    }
    void printOneFile(const std::string &logname, const std::string &logtxt)
    {
        std::string _logname = path + logname;
        int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"
        if (fd < 0)
            return;
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }
    void printClassFile(int level, const std::string &logtxt)
    {
        std::string filename = LogFile;
        filename += ".";
        filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"
        printOneFile(filename, logtxt);
    }

    ~Log()
    {
    }
    void operator()(int level, const char *format, ...)
    {
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);
        char leftbuffer[SIZE];

        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
                 ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
                 ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

        va_list s;
        va_start(s, format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);

        // 格式:默认部分+自定义部分
        char logtxt[SIZE * 2];
        snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);

        // printf("%s", logtxt); // 暂时打印
        printLog(level, logtxt);
    }

private:
    int printMethod;
    std::string path;
};
Log lg;

#pragma once

#include <iostream>
#include <memory>
#include <string>
#include <pthread.h>
#include <fstream>
#include <vector>
#include <sstream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unordered_map>
#include "log.hpp"
#include "Socket.hpp"
using namespace std;
const string wwwroot="./wwwroot"; // web 根目录,在当前文件下的文件夹,里面存放的就是网页里的所有内容
const string sep = "\r\n";
const string homepage = "index.html";
static const int defaultport = 8080;
class HttpServer;
class ThreadData
{
public:
    ThreadData(int fd, HttpServer *s) : sockfd(fd), svr(s)
    {
    }
    ThreadData()
    {}
public:
    int sockfd;
    HttpServer *svr;
};
class HttpRequest
{
public:
    
    void Deserialize(string req)//序列化
    {
         while(true)
        {
            size_t pos = req.find(sep);
            if(pos == string::npos) break;
            string temp = req.substr(0, pos);
            if(temp.empty()) break;
            req_header.push_back(temp);
            req.erase(0, pos+sep.size());
        }
        text = req;
    }
    void Parse()
    {
        stringstream ss(req_header[0]);
        ss >> method >> url >> http_version;
        file_path = wwwroot; // ./wwwroot
        if(url == "/" || url == "/index.html")//条件满足判断为要访问首页
        {
            file_path += "/";
            file_path += homepage; // ./wwwroot/index.html
        }
        else file_path += url;// /a/b/c/d.html->./wwwroot/a/b/c/d.html,无论有多少个参数,都要从wwwroot的web根目录开始查询访问
        auto pos = file_path.rfind(".");
        if(pos == string::npos) suffix = ".html";//没找到后缀默认为是.html
        else suffix = file_path.substr(pos);
    }
    void DebugPrint()
    {
         for(auto &line : req_header)
        {
            cout << "--------------------------------" << endl;
            cout << line << "\n\n";
        }
        cout << "method: " << method << endl;
        cout << "url: " << url << endl;
        cout << "http_version: " << http_version << endl;
        cout << "file_path: " << file_path << endl;
        cout << text << endl;
    }
public:
    vector<string> req_header;//请求报头
    string text;//正文部分

    // 解析之后的结果
    string method;
    string url;
    string http_version;
    string file_path; // ./wwwroot/a/b/c.html 2.png文件类型

    string suffix;//文件后缀
};
class HttpServer
{
public:
    HttpServer(uint16_t port = defaultport):port_(port)
    {
        content_type.insert({".html", "text/html"});
        content_type.insert({".png", "image/png"});
    }
    bool Start()
    {
        listensock_.Socket();
        listensock_.Bind(port_);
        listensock_.Listen();
        for (;;)
        {
            string clientip;
            uint16_t clientport;
            int sockfd = listensock_.Accept(&clientip, &clientport);
            if (sockfd < 0)
                continue;
            lg(Info, "get a new connect, sockfd: %d", sockfd);
            pthread_t tid;
            ThreadData *td = new ThreadData(sockfd, this);
            pthread_create(&tid, nullptr, ThreadRun, td);
        }
    }

    static string ReadHtmlContent(const string &htmlpath)//http的本质就是读到固定格式的字符串进行分析在分拣中搜索资源,然后在响应回去
    {
        ifstream in(htmlpath, ios::binary);//要以二进制的方式去读,因为图片是按照二进制的方式存储的,不然图片会读取失败
        if(!in.is_open()) return "";

        in.seekg(0, ios_base::end);
        auto len = in.tellg();//文件大小
        in.seekg(0, ios_base::beg);

        string content;
        content.resize(len);

        in.read((char*)content.c_str(), content.size());
        in.close();

        return content;
    }
    string SuffixToDesc(const std::string &suffix)
    {
        auto iter = content_type.find(suffix);
        if(iter == content_type.end()) return content_type[".html"];//没找到返回类型默认是.html
        else return content_type[suffix];
    }
    void HandlerHttp(int sockfd)
    {
        char buffer[10240];
        ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
        if (n > 0)
        {
            buffer[n] = 0;
            cout << buffer << endl; // 假设我们读取到的就是一个完整的,独立的http 请求
            //响应部分,网页的内容会被拼接到响应正文当中。
            //特定部分情况下,访问某个服务器URL就不会计算ip地址与端口号,所以剩下的部分就是RUL。
            //用户请求的时候,会在RUL当中包含请求的页面,什么样的资源,根据路径来寻找相应的资源
            HttpRequest req;
            req.Deserialize(buffer);
            req.Parse();
            //返回响应过程
            string text;
            bool ok = true;
            text = ReadHtmlContent(req.file_path); 
            if(text.empty())//如果失败就返回404
            {
                ok = false;
                string err_html = wwwroot;
                err_html += "/";
                err_html += "err.html";
                text = ReadHtmlContent(err_html);
            }
            string response_line;
            
            if(ok)
                response_line = "HTTP/1.0 200 OK\r\n";
            else
                response_line = "HTTP/1.0 404 Not Found\r\n";
            string response_header = "Content-Length: ";
            response_header += to_string(text.size()); // Content-Length: 11
            response_header += "\r\n";
            response_header += "Content-Type: ";//文件类型
            response_header += SuffixToDesc(req.suffix);
            response_header += "\r\n";
            response_header += "Set-Cookie: name=haha&&passwd=12345";//Cookie文件
            response_header += "\r\n";

            string blank_line = "\r\n"; // \n

            string response = response_line;
            response += response_header;
            response += blank_line;
            response += text;
            send(sockfd, response.c_str(), response.size(), 0);
        }
        close(sockfd);
    }
    static void *ThreadRun(void *args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData *>(args);
        td->svr->HandlerHttp(td->sockfd);
        delete td;
        return nullptr;
    }
    ~HttpServer()
    {}
private:
    Sock listensock_;
    uint16_t port_;

    unordered_map<string, string> content_type;//ContentType对照表
};
#include "HttpServer.hpp"

int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        exit(1);
    }
    uint16_t port = std::stoi(argv[1]);
    std::unique_ptr<HttpServer> svr(new HttpServer(port));
    svr->Start();
    return 0;
}

HTTPS

什么是HTTPS

HTTP协议中,用户的信息要么在正文里,要么在URL里,很不安全。
HTTPS也是一个应用层协议. 是在 HTTP 协议的基础上引入了一个加密层. HTTP 协议内容都是按照文本的方式明文传输的. 这就导致在传输过程中出现一些被篡改的情况.
在这里插入图片描述
https = http+ssl
在应用层正文进行加密,然后通过协议栈发送给对方,对方也是在应用层进行解密读取到正文,除了双方的应用层,其他层是看不到这份报文的正文内容的。

加密

加密就是把 明文 (要传输的信息)进行一系列变换, 生成 密文 。
解密就是把 密文 再进行一系列变换, 还原成 明文 。
在这个加密和解密的过程中, 往往需要一个或者多个中间的数据, 辅助进行这个过程, 这样的数据称为 密钥。
例如:
要发送一个数字,7,然后我们可以先进行^5再发给对方,发过去的是2,对方 ^5之后就能拿到7了。
这里7就是明文,2就是密文,5就是密钥。

在以前,去浏览器下载某款应用,如果找不到官网的话,到了别的网站下载这个应用,会发生两种情况,一种是未被劫持,应用下载会成功;如果被劫持应用就会被中间的运营商给替换掉,换成别的应用。
由于我们通过网络传输的任何的数据包都会经过运营商的网络设备(路由器, 交换机等), 那么运营商的网络设备就可以解析出你传输的数据内容, 并进行篡改.
点击 “下载按钮”, 其实就是在给服务器发送了一个 HTTP 请求, 获取到的 HTTP 响应其实就包含了该 APP 的下载链接. 运营商劫持之后,会修改掉这个下载地址。
所以,因为 http 的内容是明文传输的,明文数据会经过路由器、wifi 热点、通信服务运营商、代理服务器等多个物理节点,如果信息在传输过程中被劫持,传输的内容就完全暴露了。劫持者还可以篡改传输的信息且不被双方察觉,这就是 中间人攻击 ,所以我们才需要对信息进行加密。

不止运营商可以劫持, 其他的 黑客 也可以用类似的手段进行劫持, 来窃取用户隐私信息, 或者篡改内容。
在互联网上, 明文传输是比较危险的事情!!!
HTTPS 就是在 HTTP 的基础上进行了加密, 进一步的来保证用户的信息安全。

常见的加密方式

对称加密
采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密,特征:加密和解密所用的密钥是相同的。
就像上面说的传输数字 7 的例子。
特点:算法公开、计算量⼩、加密速度快、加密效率⾼。

非对称加密
需要两个密钥来进行加密和解密,这两个密钥是公开密钥(public key,简称公钥)和私有密钥(private key,简称私钥)。
其中公开密钥可以让所有人都知道,私有密钥只能自己知道,私钥加密的内容可以被公钥进行解密,但是公钥加密的内容只能被私钥解密。
比如说我们发送给一个服务器一条信息(只有这个服务器有私钥),所有人通过公钥进行加密,其他人读取不了这些消息,只有服务器的私钥才能进行解密。
特点:算法强度复杂、安全性依赖于算法与密钥但是由于其算法复杂,而使得加密解密速度没有对称加密解密的速度快。
非对称加密要用到两个密钥, 一个叫做 “公钥”, 一个叫做 “私钥”. 公钥和私钥是配对的. 最大的缺点就是运算速度非常慢,比对称加密要慢很多.

数据摘要 && 数据指纹

数字指纹(数据摘要),其基本原理是利用单向散列函数(Hash 函数)对信息进行运算, 生成一串固定⻓度的数字摘要。数字指纹并不是一种加密机制,但可以用来判断数据有没有被篡改。(将一个字符串通过hash算法转化成一个固定长度,非常低概率冲突的固定长度字符串,被转化的这个字符串具有唯一性——MD5算法)
摘要特征:和加密算法的区别是,摘要严格意义不是加密,因为没有解密,只不过从摘要很难反推原信息,通常用来进行数据对比

上面说的这个例子:
在这里插入图片描述
在第一次输入账号密码认证的时候就会通过MD5算法形成数据摘要(因为每个人的账号是不相同的),然后返回给用户的浏览器,也就是cookit:ID。

百度网盘还有个秒传的功能,这个也是通过MD5的算法来实现的技术。
假设用户A上传了一部电影,电影形成了数据摘要存在了百度网盘的库里,过了一段时间用户B也要上传这部电影,在上传的时候,会将要上传的电影进行数据摘要,然后判断库里是否有这个电影,发现这部电影存在,就会将这个电影变成共享文件,让用户A和用户B通过软连接的方式连接到这个共享文件;如果用户B上传的不是这个电影,那么再进行上传即可。

数字签名
摘要也是可以进行加密的,加密的摘要就叫做数字签名。

HTTPS工作过程探究

逐步设计完善一套安全方案。
假设客户端给服务器发送数据,中间有黑客进行劫持。

方案一:只使用对称加密

在这里插入图片描述
引入对称加密之后, 即使数据被截获, 由于黑客不知道密钥是啥, 因此就无法进行解密, 也就不知道请求的真实内容是啥了,可真的是这样吗?
实则不然,因为密钥也是需要传输给服务器,服务器才知道密钥是什么,那么传输的途中密钥要不要加密呢?加密服务器又不知道了,不加密黑客也会截取到密钥,也能解开密文,所以这种方法不可靠。

方案二:只使用非对称加密

在这里插入图片描述
也就是说,这种方法可以暂时保证客户端传输给服务器的数据不被黑客劫持,但是服务器发送给客户端的数据会被劫持,因为黑客也有公钥,服务器用私钥加密过的密文会被解开。

方案三:双方都使用非对称加密

简单来讲,就是方案二中客户端也生成私钥和公钥,互相传送给对方公钥,双方通过自己的私钥加密,用对方的公钥进行解密。
这样看起来是安全的,但是有和方案二的客户端发送给服务器数据一样的漏洞,并且这种方法效率低下。

方案四:非对称加密+对称加密

在这里插入图片描述
这里本质就是服务器给客户端公钥,客户端通过公钥对自己形成的对称密钥进行加密传给服务器,因为只有服务器才有私钥进行解密,所以看起来就保证了C的安全性,以后的通信同C加密解密即可。
但是,方案二三四的那种漏洞是非常致命的,如果黑客从最开始就从中间进行操作了呢?

方案二三四的致命问题

在这里插入图片描述
如果中间人形成了自己的公钥私钥,将服务器向客户端发送的S替换成M,那么客户端加密C就是用中间人的M进行了加密,并且服务器和客户端都不知道,因为客户端加密之后被中间人拿到,中间人又将C+M进行解密得到C,再让C+S发送给服务器。
这种叫做MITM攻击。
所以说还需要进一步对方案四进行改造才可以解决这个问题。(问题的本质是客户端无法验证服务器发送给自己的公钥是否合法)

引入证书

在边境,有关人员如何确定入境与出境人的身份合法?那就是身份证护照等等“证书”,并且是权威机构颁发的(当地政府)。
同理:服务端在使用 HTTPS 前,需要向 CA 机构申领一份数字证书,数字证书里含有证书申请者信息、公钥信息等。服务器把证书传输给浏览器,浏览器从证书里获取公钥就行了,证书就如身份证,证明服务端公钥的权威性

证书的原理
在这里插入图片描述
1.申请认证证书,首先服务器要有自己的公钥和密钥。准备好:域名/法人/服务器自己的公钥,生成.csr文件发给CA机构去申请证书。
2.CA机构审核是否合法。
3.CA机构审核合法之后进行签发证书,这个证书不是纸笔打印的那种证书,而是安装到服务器上的。
4.服务器发送给客户端证书,不单单是公钥这么简单了。(证书里面有服务器的公钥,这就防止了中间人篡改公钥)
5.客户端验证服务器的证书是否合法。
6.合法之后在进行密钥协商。(发送对称密钥)

CSR文件生成
在这里插入图片描述
服务器对应的公司会将自己的信息生成csr文件,然后生成一个公钥和私钥,这个私钥服务器要自己保存。(这里的公钥是面向客户端的,发送给了CA机构进行认证,私钥就是服务器自己的私钥,也就是说在形成CSR文件前服务器没有公钥和私钥)
签名
在这里插入图片描述

这是CA签名的过程,数据就是证书当中的明文:
在这里插入图片描述
将这个明文进行数据摘要形成一段数据,然后用CA机构自己的私钥进行加密就形成了签名。
然后再次拿出将明文和这个签名合二为一就形成了带有签名的证书。
在这里插入图片描述
注意:签名的形成是基于非对称加密算法。
CA机构也有自己的公钥与私钥。
为什么签名不直接加密,而是要先 hash 形成摘要?
缩⼩签名密文的⻓度,加快数字签名的验证签名的运算速度。

方案五:非对称加密 + 对称加密 + 证书认证

在这里插入图片描述
客户端认证的时候,要拆开签名与明文,进行对比。
当然,每个客户端在出厂的时候都会内置很多权威机构CA的公钥。(和服务器的公钥无关)
在形成签名的时候,CA机构用自己的私钥进行加密,此刻进行认证就需要用CA机构的公钥进行解密形成散列值——数据摘要。
然后让明文也进行数据摘要。
在这里插入图片描述
客户端只认CA的公钥,也就是说,签名只有CA机构才有资格形成签名。(中间人没有CA的私钥,无法修改签名)
这样就不怕黑客修改明文了,就算改了签名,客户端也没有黑客的公钥,无法对签名进行解密,上面的散列值等式就不成立。

可如果黑客将整个证书全都掉包了呢?(黑客自己进行CA认证,发送给客户端)那就搞笑了,浏览器申请访问的是A网站,返回来的证书是B网站的,域名不同,也会被识别出来。

这样就可以防止中间人从一开始进行替换服务器公钥的操作了。

总结HTTPS的保密三组

HTTPS 工作过程中涉及到的密钥有三组.
第一组(非对称加密): 用于校验证书是否被篡改. 服务器持有私钥(私钥在形成 CSR 文件与申请证书时获得), 客户端持有公钥(操作系统包含了可信任的 CA 认证机构有哪些, 同时持有对应的公钥). 服务器在客户端请求时,返回携带签名的证书. 客户端通过这个公钥进行证书验证, 保证证书的合法性,进一步保证证书中携带的服务端公钥权威性。
第⼆组(非对称加密): 用于协商生成对称加密的密钥. 客户端用收到的 CA 证书中的公钥(是可被信任的)给随机生成的对称加密的密钥加密, 传输给服务器, 服务器通过私钥解密获取到对称加密密钥.
第三组(对称加密): 客户端和服务器后续传输的数据都通过这个对称密钥加密解密. 其实一切的关键都是围绕这个对称加密的密钥. 其他的机制都是辅助这个密钥工作的.