前言
前面寫的TCP/UDP客戶端在訪問服務端的時候,需要輸入ip地址和端口號才可以訪問, 但在現實中,我們訪問一個網站是直接輸入的一個域名,而不是使用的ip地址+端口號。
比如在訪問百度 https://www.baidu.com/的時候, 是使用的域名,瀏覽器會把域名進行解析成為ip地址+端口號。
我們ping一下百度的域名,會得到來自百度的一個回復,然后我們可以訪問這個ip地址就會得到百度的主頁面。
上面在進行網絡請求的時候,并沒有輸入端口號,在輸入ip地址之后,直接進入到了百度的主頁面。
把這里復制粘貼。百度一下,你就知道 其實是有一個http前綴的,我們訪問其他網頁,也會有http或者https前綴的。輸入網址就算不輸入http或https,瀏覽器會把這兩個協議進行默認拼接。一般像這種知名的服務器會把端口號給固定下來。這種端口號是不能隨意修改的。所以我們在訪問百度的時候,把ip地址輸入進去,可以直接訪問到百度的主頁面,其實就是訪問的39.156.66.14:80。
認識URL
我們看到了好的文章,然后把鏈接(https://blog.csdn.net/weixin_73888239/category_12238116.html )復制下來分享給朋友,像這種鏈接就叫做URL 統一資源定位符。在全網當中,只要有這個URL,就可以訪問這個網頁。每一個字符串在全網當中,都是唯一的。在網絡上我們所看到的一些圖片,音樂,視頻,直播等資源都可以用唯一的一個字符串標識,并且可以獲取到,只要知道url就可以訪問這些資源。
url的格式一般為下圖所示
urlencode和urldecode
urlencode和urldecode是用于處理URL編碼和解碼的兩個相關的操作,通常用于將特殊字符轉換為URL安全的形式,以及將已編碼的URL轉換回原始形式。
urlencode 用于將字符串轉換為url安全的格式,將特殊字符轉換為其對應的百分比編碼形式。
urldecode 用于解碼已經被url編碼的字符串,將百分比編碼形式還原為原始字符
像 / ? : 等這樣的字符, 已經被url當做特殊意義理解了. 因此這些字符不能隨意出現.比如, 某個參數中需要帶有這些特殊字符, 就必須先對特殊字符進行轉義.
轉義的規則如下:
將需要轉碼的字符轉為16進制,然后從右到左,取4位(不足4位直接處理),每2位做一位,前面加上%,編碼成%XY格式
"+" 被轉義成了 "%2B",urldecode就是urlencode的逆過程;
HTTP協議格式
我們平時上網的行為其實就兩種
- 從服務器端拿下來資源數據 --- get方法 (可以通過 表單 的方法展示出來)表單收集用戶數據(表單是要被提交的),并把用戶數據推送給服務器(表單中的數據,會被轉成http request的一部分)
- 把客戶端的數據提交到服務器 --- post方法get方法都可以
get方法傳參通過url傳參,會回顯輸入的私密信息,不夠私密
post方法通過正文提交傳參,不會回顯的輸出信息.一般私密性是有保證的
這里的私密性不是安全性,數據只有經過加密和解密才會安全。
http request中,是有一個請求行,請求報頭,請求正文組成,在請求行中,有請求方法(GET,POST),URL,HTTP Version組成,這三個之間以空格作為分隔符。中間部分是請求報頭,都是以KV的形式存在,最后是請求正文,在請求正文和請求報頭之間,存在一個空行,這是為了區分請求報頭和請求正文而存在的。在讀取http request的時候,按照行讀取,這樣就可以將報文和有效載荷成功的分離,不會讀到不屬于自己的數據。
在HTTP請求的時候,會先將我們所輸入的域名進行解析,然后去訪問該內容,客戶端在與服務端建立TCP連接,通過三次握手確保雙方可以進行可靠的通信。然后構建HTTP請求消息。客戶端構建一個HTTP請求消息,其中包括請求行,請求報頭,空行,請求正文。請求消息發送到服務器,服務器會進行處理并構建響應消息(狀態行,響應報頭,響應正文),然后將響應消息發送給客戶端,并關閉連接。
可以使用telnet工具來完成一次http的請求和響應。
首行: [方法] + [url] + [版本]
Header: 請求的屬性, 冒號分割的鍵值對;每組屬性之間使用\n分隔;遇到空行表示Header部分結束
Body: 空行后面的內容都是Body. Body允許為空字符串. 如果Body存在, 則在Header中會有一個
Content-Length屬性來標識Body的長度;
telnet www.baidu.com 80
在按ctrl + ]
回車// http請求
GET / HTTP/1.1 // 請求行// http響應
HTTP/1.1 200 OK // 響應狀態行中存在 http的版本,狀態碼,狀態碼描述,跟請求一樣,都是以空格作為分隔符
Accept-Ranges: bytes
Cache-Control: no-cache
Connection: keep-alive
Content-Length: 9508
Content-Type: text/html
Date: Thu, 29 Feb 2024 07:49:00 GMT
P3p: CP=" OTI DSP COR IVA OUR IND COM "
P3p: CP=" OTI DSP COR IVA OUR IND COM "
Pragma: no-cache
Server: BWS/1.1
Set-Cookie: BAIDUID=BDADB3AA66EC6897715119E26C4CF88A:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: BIDUPSID=BDADB3AA66EC6897715119E26C4CF88A; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: PSTM=1709192940; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: BAIDUID=BDADB3AA66EC689787381350CD38E8C2:FG=1; max-age=31536000; expires=Fri, 28-Feb-25 07:49:00 GMT; domain=.baidu.com; path=/; version=1; comment=bd
Traceid: 1709192940051597876211264035507514709484
Vary: Accept-Encoding
X-Ua-Compatible: IE=Edge,chrome=1
X-Xss-Protection: 1;mode=blockHTML/CSS/JS 頁面。
首行: [版本號] + [狀態碼] + [狀態碼解釋]
Header: 請求的屬性, 冒號分割的鍵值對;每組屬性之間使用\n分隔;遇到空行表示Header部分結束
Body: 空行后面的內容都是Body. Body允許為空字符串. 如果Body存在, 則在Header中會有一個
Content-Length屬性來標識Body的長度; 如果服務器返回了一個html頁面, 那么html頁面內容就是在body中.
telnet是自己構建的請求。可以用費德勒這個軟件進行抓包。
HttpDone
其實我們也可以自己寫一個簡單的http。
#pragma once#include <iostream>
#include <string>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>#include "log1.hpp"
#include "Socket.hpp"extern Log lg;class HttpServer;class ThreadData {
public:ThreadData(int sockfd):_sockfd(sockfd){}~ThreadData(){}
public:public:HttpServer *serv;int _sockfd;
}; // 存儲線程數據const std::string defaultip = "0.0.0.0";
class HttpServer {
public:HttpServer(uint16_t port):_port(port),_ip(defaultip){}~HttpServer(){}
public:void Init(){_listensock = sock.Socket();lg(Info, "socket success");sock.Bind(_listensock, _port);lg(Info, "Bind success");sock.Listen(_listensock);lg(Info, "Listen success");}static void *ThreadRun(void *args){pthread_detach(pthread_self()); ThreadData* td = static_cast<ThreadData*>(args);char buf[1024];while (true){ssize_t n = read(td->_sockfd, buf, sizeof(buf) - 1);if (n > 0){buf[n] = 0;std::cout << buf << std::endl;}}}bool Start(){while (true){std::string clientip;uint16_t clientport;int sockfd = sock.Accept(_listensock, clientip, clientport);lg(Info, "accept success");ThreadData *td = new ThreadData(sockfd);pthread_t tid;pthread_create(&tid, nullptr, ThreadRun, td);}}
private:int _listensock;uint16_t _port;std::string _ip;Sock sock;
};
#include "HttpServer.hpp"
#include <memory>int main(int argc, char *argv[])
{ if (argc != 2){exit(1);}uint16_t port = std::stoi(argv[1]);std::unique_ptr<HttpServer> serv(new HttpServer(port));serv->Init();serv->Start();return 0;
}
#pragma once#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "log1.hpp"Log lg;const int backlog = 10;enum
{SocketErr = 2,BindErr,ListenErr,
};class Sock
{
public:Sock(){}~Sock(){}int Socket(){_listensocket = socket(AF_INET, SOCK_STREAM, 0);if (_listensocket < 0){lg(Fatal, "socker error, %s: %d", strerror(errno), errno);exit(SocketErr);}}void Bind(int listensock, uint16_t port){struct sockaddr_in serv;bzero(&serv, sizeof(serv));serv.sin_family = AF_INET;serv.sin_port = htons(port);serv.sin_addr.s_addr = INADDR_ANY;if (bind(listensock, (const sockaddr*)&serv, sizeof(serv)) < 0){lg(Fatal, "bind error, %s: %d", strerror(errno), errno);exit(BindErr);}}void Listen(int listensock){if (listen(listensock, backlog) < 0){lg(Fatal, "listen error, %s: %d", strerror(errno), errno); exit(ListenErr);}}int Accept(int listensock, std::string& ip, uint16_t& port){struct sockaddr_in serv;bzero(&serv, sizeof(serv));socklen_t len = sizeof(serv);int sockfd = accept(listensock, (struct sockaddr*)&serv, &len);if (sockfd < 0){lg(Warning, "accept error, %s: %d", strerror(errno), errno);return -1;}char ipstr[64];inet_ntop(AF_INET, &serv.sin_addr.s_addr, ipstr, sizeof(ipstr));ip = ipstr;port = ntohs(serv.sin_port);return sockfd;}bool Connect(int listensock, const std::string &ip, const uint16_t& port){struct sockaddr_in serv;bzero(&serv, sizeof(serv));serv.sin_family = AF_INET;serv.sin_port = htons(port);inet_pton(AF_INET, ip.c_str(), &serv.sin_addr.s_addr);int n = connect(listensock, (const struct sockaddr*)&serv, sizeof(serv));if (n < 0){std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;return false;}return true;}void Close(int listensock){close(listensock);}int Fd(){return _listensocket;}private:int _listensocket;
};
在運行之后,讓PC端和手機端分別訪問該服務端。
[Info][2024-3-1 14:2:55] socket success[Info][2024-3-1 14:2:55] Bind success[Info][2024-3-1 14:2:55] Listen success[Info][2024-3-1 14:3:11] accept success[Info][2024-3-1 14:3:11] accept successGET / HTTP/1.1
Host: 1.117.232.232:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6[Info][2024-3-1 14:3:41] accept success[Info][2024-3-1 14:3:41] accept successGET / HTTP/1.1
Host: 1.117.232.232:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Linux; U; Android 14; zh-CN; 23127PN0CC Build/UKQ1.230804.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.58 Quark/6.9.6.501 Mobile Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
可以看到請求報頭中都是kv結構的數據。其中有一個User-Agent,他代表的是瀏覽器的版本和用戶的操作系統。
還有其他的一些數據。
在瀏覽器上下載軟件的時候,可以直接下載PC版的安裝包,用手機瀏覽器下載軟件會直接下載手機版的安裝包,這就是通過User-Agent來判斷用戶是用的手機端還是PC端,判斷之后再給用戶推送合適的內容。
其實我們可以自己構建一個http響應,當客戶端連接服務端的時候,服務端會給客戶端一個響應。
先寫一個簡單的網頁當作響應正文
<!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>LOG IN</h1></body>
</html>
在寫一個響應狀態行和響應報頭,將狀態行,響應報頭和響應正文進行拼接。發送給瀏覽器,就可以得到數據了。
static std::string ReadHtmlContent(const std::string &htmlpath){std::ifstream in(htmlpath);if (!in.is_open()){return "404";}std::string line;std::string content;while (std::getline(in, line)){content += line;}return content;}static void HandlerHttp(int sockfd)
{char buf[10240];ssize_t n = recv(sockfd, buf, sizeof(buf) - 1, 0);if (n > 0){buf[n] = 0;std::cout << buf;// 構建服務端響應消息std::string text = ReadHtmlContent("wwwroot/index.html"); // 響應正文std::string response_line = "HTTP/1.1 200 OK\r\n"; // 響應狀態行std::string response_header = "Content-Length: "; // 響應報頭response_header += std::to_string(text.size()); // 響應報頭response_header += "\r\n";std::string blank_line = "\r\n"; // 空行,來分割響應正文和響應報頭std::string response = response_line;response += response_header;response += blank_line;response += text;ssize_t m = send(sockfd, response.c_str(), response.size(), 0);if (m < 0){lg(Debug, "send error");}// Close the socket after sending the responseclose(sockfd);}else if (n == 0){// Connection closed by the clientclose(sockfd);}else{// Handle the receive errorlg(Debug, "recv error");close(sockfd);}
}
運行之后就會出現剛才寫的頁面,也可以在頁面中添加a標簽,進行鏈接跳轉。
http的方法
上面的請求都是get方法,我們最常用的是get和post方法。其他的方法了解一下即可。
如何把數據提交給服務器呢?我們登錄賬號的時候,會有一個登錄頁面,這個登錄頁面其實就是一個表單,通過表單將數據提交給服務器。
這是隨便找的一個登錄頁面的頁面代碼。如果上面寫的http需要數據,也可以寫一個表單頁面,然后將數據提交給服務器。
<!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>LOG IN</h1><form action="" method="get">name: <input type="text" name="name" value=""><br>password: <input type="password" name="name" value=""><br><input type="submit" value="提交"></form>
</body>
</html>
表單很丑陋,哈哈,這不是我們該考慮的問題。
在我們輸入name和pwd之后,看瀏覽器中的url,
使用get方法將參數交給服務器,是通過url提交的,將參數拼接到了url的后面,來完成請求。
我們的服務端也會收到這個url。
將form中的method換成post方法之后,在將表單提交。,url中不會出現輸入的name和pwd。
在寫的服務端中查看瀏覽器的請求,可以看到使用的是post方法和數據。
可能會有人說,get方法會把數據顯示到url上,不安全;post不會顯示,相對安全。其實不是這樣,數據只有在經過加密之后,才會變得安全。get方法只能說是不夠私密,post方法私密一些。
http的狀態碼
前面寫的簡易http代碼中,瀏覽器發送請求后,服務端會進行響應,響應中的狀態碼寫的是200,代表服務端響應成功。
我們在訪問京東的時候,將url寫為 www.jd.com/a/b/c就會出現找不到的情況。這就是404(Not Found)。
最常見的狀態碼, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)
重定向
瀏覽器向服務端發送一個請求,服務端對瀏覽器做出響應,但是瀏覽器中輸入的url是不應該在被使用的url,應該使用新的地址向服務端發送請求。所以在使用老地址發送請求的時候,服務端做出的響應報頭中,會存在一個location: 新地址 的kv結構,然后瀏覽器使用這個新地址,在對服務端發送請求。
重定向就是服務器指導瀏覽器訪問新地址。
std::string response_line = "HTTP/1.1 302 Found\r\n"; // 響應狀態行std::string response_header = "Content-Length: "; // 響應報頭
response_header += std::to_string(text.size()); // 響應報頭response_header += "\r\n";
response_header += "Location: https://www.jd.com\r\n";
std::string blank_line = "\r\n"; // 空行,來分割響應正文和響應報頭std::string response = response_line;
response += response_header;
response += blank_line;
response += text;ssize_t m = send(sockfd, response.c_str(), response.size(), 0);
if (m < 0)
{lg(Debug, "send error");
}close(sockfd);
將響應狀態行進行修改,將響應報頭中添加上location:地址 字段,就可以完成重定向了。在訪問服務端,就會跳轉到jd的頁面了。
臨時重定向(Temporary Redirect):
- 使用狀態碼 302 Found 或 307 Temporary Redirect 表示。
- 表示請求的資源暫時被移動到了其他位置。
- 客戶端在接收到這樣的狀態碼時,應該繼續使用原始的 URL 進行請求。
- 臨時重定向是暫時性的,客戶端以后可能會繼續使用原始 URL,因為重定向只是暫時的。
永久重定向 (Permanent Redirect):
- 使用狀態碼 301 Moved Permanently 或 308 Permanent Redirect 表示。
- 表示請求的資源已經永久地移到了其他位置。
- 客戶端在接收到這樣的狀態碼時,應該更新其鏈接并使用新的 URL 進行以后的請求。
- 永久重定向是持久性的,客戶端應該更新其鏈接,以便將來的請求直接發送到新的 URL 上。
http常見的header
- Content-Type: 數據類型(text/html等)
<img src="C:\Users\Lenovo\Pictures\1708049137066.jpg" alt="src error">
在運行之后,圖片加載不出來,瀏覽器沒有解釋出來,這是因為格式的 問題,要在響應報頭上添加上Content-Type:數據類型 這個kv結構。因為我們寫的http有點簡陋,僅僅添加上這個報頭也不能響應,瀏覽器先發送請求請求的是html的頁面,然后再次請求,請求的才是圖片,如果把Content-Type改成圖片就不能顯示頁面了,圖片也顯示不出來,所以可以添加上一個函數,用來判斷請求的是什么類型。
- Content-Length: Body的長度
- Host: 客戶端告知服務器, 所請求的資源是在哪個主機的哪個端口上;
- User-Agent: 聲明用戶的操作系統和瀏覽器版本信息;
- referer: 當前頁面是從哪個頁面跳轉過來的;
- location: 搭配3xx狀態碼使用, 告訴客戶端接下來要去哪里訪問;
- Cookie: 用于在客戶端存儲少量信息. 通常用于實現會話(session)的功能
- connection:keep-alive---長連接
一次連接可以被多個請求-響應復用, 在HTTP/1.1中,默認情況下是啟用了長連接的。
9. connection:close---短連接
每個請求-響應都需要建立一個新的連接,通常用于HTTP/1.0中每個連接只處理一個請求,處理完畢后即關閉連接,不保持持續的連接狀態。
會話Cookie
在瀏覽器上登錄b站,關掉瀏覽器,再次打開瀏覽器看b站,是不需要在進行登錄。
這是服務器發送到用戶瀏覽器并保存在本地的一小塊數據。瀏覽器會存儲cookie并在下次向同意服務器再發起請求時,攜帶并發送到服務器上的。通常,它用于告知服務端兩個請求是否來自同一瀏覽器,如保持用戶的登陸狀態。Cookie使基于無狀態的HTTP協議記錄穩定的狀態信息成為了可能。
將http請求中添加上了set-cookie結構,就會產出cookie文件,里面保存的就是賬號密碼,這也就是為什么我們登錄網站的時候,一段時間內再次訪問同樣網站的時候,瀏覽器和服務器相互配合,服務器自動的去認證cookie,就不需要重復登錄了。
static void HandlerHttp(int sockfd){char buf[10240];ssize_t n = recv(sockfd, buf, sizeof(buf) - 1, 0);if (n > 0){buf[n] = 0;std::cout << buf;// 構建服務端響應消息std::string text = ReadHtmlContent("wwwroot/index.html"); // 響應正文std::string response_line = "HTTP/1.0 200 OK\r\n"; // 響應狀態行// std::string response_line = "HTTP/1.1 302 Found\r\n"; // 響應狀態行std::string response_header = "Content-Length: "; // 響應報頭response_header += std::to_string(text.size()); // 響應報頭response_header += "\r\n";response_header += "Set-Cookie: ";response_header += "123456";response_header += "\r\n";// response_header += "Location: https://www.jd.com\r\n";std::string blank_line = "\r\n"; // 空行,來分割響應正文和響應報頭std::string response = response_line;response += response_header;response += blank_line;response += text;ssize_t m = send(sockfd, response.c_str(), response.size(), 0);if (m < 0){lg(Debug, "send error");}// Close the socket after sending the responseclose(sockfd);}else if (n == 0){// Connection closed by the clientclose(sockfd);}else{// Handle the receive errorlg(Debug, "recv error");close(sockfd);}}
cookie固然方便,但也有一些問題
- cookie被盜取的問題
- 個人信息泄露的問題
當瀏覽器發起請求的時候,會把cookie發送過去,然后服務端進行認證,認證成功之和,服務端會為我們創建一個session文件,這個文件里會記錄下來用戶登錄相關的內容,形成session文件,還會生成一個全服務器內唯一的一個session ID,這個ID是一種序列號,以session ID為session文件進行命名,將session ID返回給用戶,用戶的Cookie中存儲的就是這個session ID,往后,瀏覽器再次發送請求,Cookie中存的就是session ID,服務端進行查找就行了。假如說我訪問的是B站,B站的用戶非常多,服務端如何管理這個session ID呢? 先描述,在組織,session文件中有用戶自己的屬性,還有sessionID,所以只要以某種數據結構的形式把這些文件或ID連接起來,就能以增刪查改的形式進行管理了。
如果黑客把用戶發送的請求給截取了,那么黑客就會拿到Cookie了,黑客就可以通過Cookie訪問用戶的賬號了,但是Cookie中存儲的是session ID,個人信息是不會泄露的。各大網站都有技術人員,這種情況肯定會有解決方法。
https
HTTPS也是?個應?層協議.是在HTTP協議的基礎上引?了?個加密層.HTTP協議內容都是按照?本的?式明?傳輸的.這就導致在傳輸過程中出現?些被篡改的情況.
http是以明文的形式傳輸,不加密,不提供對數據完整性的保障,容易受到中間人攻擊。HTTPS是在HTTP的基礎上添加了安全曾(SSL),用于對數據加密解密。通過SSL協議,確保數據在傳輸過程中不被竊取或篡改。應用層中http經過SSL加密解密后,到傳輸層,傳輸層并不知道該數據是經過加密的,只有應用層才會知道。
加密解密
加密就是把明?(要傳輸的信息)進??系列變換,?成密?解密就是把密?再進??系列變換,還原成明?在這個加密和解密的過程中,往往需要?個或者多個中間的數據,輔助進?這個過程,這樣的數據稱為密鑰。假如7 ^ 5 = 010, 這個7就是名文,這個 010就是密文,中間的這個5就是密鑰。5 ^ 010 = 7,這樣就可以得到明文。
加密解密到如今已經發展成?個獨?的學科:密碼學.?密碼學的奠基?,也正是計算機科學的祖師爺之?艾倫·?席森·圖靈
因為http的內容是明?傳輸的,明?數據會經過路由器、wifi熱點、通信服務運營商、代理服務器等多個物理節點,如果信息在傳輸過程中被劫持,傳輸的內容就完全暴露了。劫持者還可以篡改傳輸的信息且不被雙?察覺,這就是中間?攻擊 ,所以我們才需要對信息進?加密.不?運營商可以劫持,其他的?客也可以?類似的?段進?劫持,來竊取??隱私信息,或者篡改內容.
在互聯網中,明文傳輸是一件非常危險的事情,HTTPS就是在HTTP的基礎上進行了加密,進一步的來保證用戶的信息安全。
常見的加密方式
對稱加密
采?單鑰密碼系統的加密?法,同?個密鑰可以同時?作信息的加密和解密,這種加密?法稱為對
稱加密,也稱為單密鑰加密,特征:加密和解密所?的密鑰是相同的。
常?對稱加密算法(了解):DES、3DES、AES、TDEA、Blowfish、RC2等
特點:算法公開、計算量?、加密速度快、加密效率?
對稱加密其實就是通過同?個"密鑰",把明?加密成密?,并且也能把密?解密成明?.
?
非對稱加密
需要兩個密鑰來進行加密和解密。 一個是公鑰,一個是私鑰。公鑰和私鑰是配對的,最大的缺點就是運算速度非常慢,比對稱加密要慢很多。
明文 由 公鑰A 加密變成密文, 密文由公鑰B進行解密變成明文,也可以反著來,通過私鑰對明文加密變成密文,通過公鑰對密文進行解密,變成明文。
常??對稱加密算法(了解):RSA,DSA,ECDSA
特點:算法強度復雜、安全性依賴于算法與密鑰但是由于其算法復雜,?使得加密解密速度沒有對
稱加密解密的速度快
數據摘要 && 數據指紋
數字指紋(數據摘要),其基本原理是利?單向散列函數(Hash函數)對信息進?運算,?成?串固定?度
的數字摘要。數字指紋并不是?種加密機制,但可以?來判斷數據有沒有被竄改。
摘要常?算法:有MD5、SHA1、SHA256、SHA512等,算法把?限的映射成有限,因此可能會有碰撞(兩個不同的信息,算出的摘要相同,但是概率?常低)
摘要特征:和加密算法的區別是,摘要嚴格意義不是加密,因為沒有解密,只不過從摘要很難反推
原信息,通常?來進?數據對?
HTTPS加密方案
方案一 - 只使用對稱加密
如果通信雙?都各?持有同?個密鑰X,且沒有別?知道,這兩?的通信安全當然是可以被保證的(除?密鑰被破解)
客戶端向服務端發送密鑰的時候,服務器能獲取密鑰,黑客也能獲取這個密鑰,那么可以讓密鑰進行加密,在發送給服務端,但是服務端并不知道對加密內容解密的密鑰是什么,所以還是要先發送密鑰,那么黑客還是可以直接獲取密鑰。這就導致了是先有雞還是先有蛋的問題。
所以方案一是不可取的。
方案二 - 只使用非對稱加密
非對稱加密有兩個密鑰,一個公鑰,一個私鑰。客戶端向服務端發送請求的時候,服務端會把公鑰發送給客戶端,此后客戶端在向服務端發送數據的時候,數據會進行加密,只有私鑰能解,只有服務器有這個私鑰。黑客就算獲得了公鑰,沒有私鑰,也不能將被公鑰加密過的數據解密。當服務端接受到客戶端的信息后,要向客戶端發起響應,這個響應是被私鑰加密過的,黑客是有公鑰的,所以黑客可以將服務端發送給客戶端的數據給解密。
所以方案二只能保證單方向的數據安全性,此方案也不可取。
方案三 - 雙方都是用非對稱加密
服務端擁有公鑰S與對應的私鑰S',客?端擁有公鑰C與對應的私鑰C', 客戶端和服務端交換公鑰。客?端給服務端發信息:先?S對數據加密,再發送,只能由服務器解密,因為只有服務器有私鑰S'服務端給客?端發信息:先?C對數據加密,在發送,只能由客?端解密,因為只有客?端有私鑰C'。這樣貌似能行,雙方協商完畢就可以保證安全性了。但是他的效率非常低。安全性問題還存在。
方案四 - ?對稱加密+對稱加密
客戶端先拿到服務端發送的公鑰S, 然后客戶端自己形成一個對稱密鑰C, 由公鑰S和密鑰C一起加密成XXX,然后發送給服務端, XXX在和私鑰S` 解密成 C,此時服務端就有了對稱密鑰。這樣就保證了數據安全,效率問題也有保證了。這個方案還是存在問題。
雖然上?已經?較接近答案了,但是依舊有安全問題
?案2,?案3,?案四都存在?個問題,如果最開始,中間?就已經開始攻擊了呢?
中間人攻擊 - 針對上面的場景
Man-in-the-MiddleAttack,簡稱“MITM攻擊"
確實,在?案2/3/4中,客?端獲取到公鑰S之后,對客?端形成的對稱秘鑰X?服務端給客?端的公鑰S進?加密,中間?即使竊取到了數據,此時中間?確實?法解出客?端形成的密鑰X,因為只有服務器有私鑰S'但是中間?的攻擊,如果在最開始握?協商的時候就進?了,那就不?定了,假設hacker已經成功成為中間? 。
- 服務器具有?對稱加密算法的公鑰S,私鑰S'
- 中間?具有?對稱加密算法的公鑰M,私鑰M'
- 客?端向服務器發起請求,服務器明?傳送公鑰S給客?端
- 中間?劫持數據報?,提取公鑰S并保存好,然后將被劫持報?中的公鑰S替換成為??的公鑰M,
并將偽造報?發給客?端 - 客?端收到報?,提取公鑰M(??當然不知道公鑰被更換過了),??形成對稱秘鑰X,?公鑰M加
密X,形成報?發送給服務器 - 中間?劫持后,直接???的私鑰M'進?解密,得到通信秘鑰X,再?曾經保存的服務端公鑰S加
密后,將報?推送給服務器 - 服務器拿到報?,???的私鑰S'解密,得到通信秘鑰X
- 雙?開始采?X進?對稱加密,進?通信。但是?切都在中間?的掌握中,劫持數據,進?竊聽甚
?修改,都是可以的
?
上?的攻擊?案,同樣適?于?案2,?案3
問題本質出在哪?了呢?客?端?法確定收到的含有公鑰的數據報?,就是?標服務器發送過來的!
?
CA證書
在訪問網站的時候,可能會有這樣的情況,網站的安全證書已經過期,是否選擇相信之類的情況。其實就是CA證書到期了。
服務端在使?HTTPS前,需要向CA機構申領?份數字證書,數字證書?含有證書申請者信息、公鑰信息等。服務器把證書傳輸給瀏覽器,瀏覽器從證書?獲取公鑰就?了,證書就如?份證,證明服務端公鑰的權威性。
這個證書可以理解為是一個結構化的字符串,里面包含了以下信息:
- 證書發布機構
- 證書有效期
- 公鑰
- 證書所有者
- 簽名
- ……
需要注意的是:申請證書的時候,需要在特定平臺?成查,會同時?成?對?密鑰對?,即公鑰和私
鑰。這對密鑰對?就是?來在?絡通信中進?明?加密以及數字簽名的。其中公鑰會隨著CSR?件,?起發給CA進?權威認證,私鑰服務端??保留,?來后續進?通信(其實主要就是?來交換對稱秘鑰)
可以使用在線生成CSR和密鑰形成CSR之后,后續就是向CA進?申請認證,不過?般認證過程很繁瑣,?絡各種提供證書申請的服務商,?般真的需要,直接找平臺解決就?
方案五 - 非對稱加密 + 對稱加密 + 證書認證
在客?端和服務器剛?建?連接的時候,服務器給客?端返回?個證書,證書包含了之前服務端的公鑰,也包含了?站的?份信息.
?
客?端進?認證
當客?端獲取到這個證書之后,會對證書進?校驗(防?證書是偽造的).
判定證書的有效期是否過期
判定證書的發布機構是否受信任(操作系統中已內置的受信任的證書發布機構).
驗證證書是否被篡改:從系統中拿到該證書發布機構的公鑰,對簽名解密,得到?個hash值(稱為數據摘要),設為hash1.然后計算整個證書的hash值,設為hash2.對?hash1和hash2是否相等.如果相等,則說明證書是沒有被篡改過的。
中間?有沒有可能篡改該證書?
1. 中間?篡改了證書的明?
2. 由于他沒有CA機構的私鑰,所以?法hash之后?私鑰加密形成簽名,那么也就沒法辦法對篡改后
的證書形成匹配的簽名
3. 如果強?篡改,客?端收到該證書后會發現明?和簽名解密后的值不?致,則說明證書已被篡改,
證書不可信,從?終?向服務器傳輸信息,防?信息泄露給中間?
中間?整個掉包證書?
1. 因為中間?沒有CA私鑰,所以?法制作假的證書(為什么?)
2. 所以中間?只能向CA申請真證書,然后???申請的證書進?掉包
3. 這個確實能做到證書的整體掉包,但是別忘記,證書明?中包含了域名等服務端認證信息,如果整
體掉包,客?端依舊能夠識別出來。
4. 永遠記住:中間?沒有CA私鑰,所以對任何證書都?法進?合法修改,包括??的