HTTP請求報頭: Authorization
HTTP響應報頭: WWW-Authenticate
HTTP認證是基於質詢/回應(challenge/response)的認證模式。
HTTP認證
BASIC認證
BASIC認證概述
當一個客戶端向HTTP服務器進行數據請求時,如果客戶端未被認證,則HTTP服務器將通過基本認證過程對客戶端的用戶名及密碼進行驗證,以決定用戶是否合法。
客戶端在接收到HTTP服務器的身份認證要求后,會提示用戶輸入用戶名及密碼,然后將用戶名及密碼以BASE64加密,加密后的密文將附加於請求信息中, 如當用戶名為anjuta,密碼為:123456時,客戶端將用戶名和密碼用“:”合並,並將合並后的字符串用BASE64加密為密文,並於每次請求數據時,將密文附加於請求頭(Request Header)中。HTTP服務器在每次收到請求包后,根據協議取得客戶端附加的用戶信息(BASE64加密的用戶名和密碼),解開請求包,對用戶名及密碼進行驗證,如果用戶名及密碼正確,則根據客戶端請求,返回客戶端所需要的數據;否則,返回錯誤代碼或重新要求客戶端提供用戶名及密碼。
BASIC認證的過程
基本認證步驟:
1、客戶端訪問一個受http基本認證保護的資源。
2、服務器返回401狀態,要求客戶端提供用戶名和密碼進行認證。(驗證失敗的時候,響應頭會加上WWW-Authenticate: Basic realm="請求域"。)
401 Unauthorized WWW-Authenticate: Basic realm="WallyWorld"
3、客戶端將輸入的用戶名密碼用Base64進行編碼后,采用非加密的明文方式傳送給服務器。
Authorization: Basic xxxxxxxxxx.
4、服務器將Authorization頭中的用戶名密碼解碼並取出,進行驗證,如果認證成功,則返回相應的資源。如果認證失敗,則仍返回401狀態,要求重新進行認證。
BASIC認證的JAVA實現代碼
HttpSession session=request.getSession(); String user=(String)session.getAttribute("user"); String pass; if(user==null){ try{ response.setCharacterEncoding("GBK"); PrintWriter ut=response.getWriter(); String authorization=request.getHeader("authorization"); if(authorization==null||authorization.equals("")){ response.setStatus(401); response.setHeader("WWW-authenticate","Basic realm=\"請輸入管理員密碼\""); out.print("對不起你沒有權限!!"); return; } String userAndPass=new String(new BASE64Decoder().decodeBuffer(authorization.split(" ")[1])); if(userAndPass.split(":").length<2){ response.setStatus(401); response.setHeader("WWW-authenticate","Basic realm=\"請輸入管理員密碼\""); out.print("對不起你沒有權限!!"); return; } user=userAndPass.split(":")[0]; pass=userAndPass.split(":")[1]; if(user.equals("111")&&pass.equals("111")){ session.setAttribute("user",user); RequestDispatcher dispatcher=request.getRequestDispatcher("index.jsp"); dispatcher.forward(request,response); }else{ response.setStatus(401); response.setHeader("WWW-authenticate","Basic realm=\"請輸入管理員密碼\""); out.print("對不起你沒有權限!!"); return; } }catch(Exception ex){ ex.printStackTrace(); } }else{ RequestDispatcher dispatcher=request.getRequestDispatcher("index.jsp"); dispatcher.forward(request,response); }
特記事項:
1、Http是無狀態的,同一個客戶端對同一個realm內資源的每一個訪問會被要求進行認證。
2、客戶端通常會緩存用戶名和密碼,並和authentication realm一起保存,所以,一般不需要你重新輸入用戶名和密碼。
3、以非加密的明文方式傳輸,雖然轉換成了不易被人直接識別的字符串,但是無法防止用戶名密碼被惡意盜用。雖然用肉眼看不出來,但用程序很容易解密。
優點:
基本認證的一個優點是基本上所有流行的網頁瀏覽器都支持基本認證。基本認證很少在可公開訪問的互聯網網站上使用,有時候會在小的私有系統中使用(如路由器
網頁管理接口)。后來的機制HTTP摘要認證是為替代基本認證而開發的,允許密鑰以相對安全的方式在不安全的通道上傳輸。
缺點:
-
雖然基本認證非常容易實現,但該方案建立在以下的假設的基礎上,即:客戶端和服務器主機之間的連接是安全可信的。特別是,如果沒有使用SSL/TLS這樣的傳輸
層安全的協議,那么以明文傳輸的密鑰和口令很容易被攔截。該方案也同樣沒有對服務器返回的信息提供保護。 -
現存的瀏覽器保存認證信息直到標簽頁或瀏覽器被關閉,或者用戶清除歷史記錄。HTTP沒有為服務器提供一種方法指示客戶端丟棄這些被緩存的密鑰。這意味着服務
器端在用戶不關閉瀏覽器的情況下,並沒有一種有效的方法來讓用戶登出。
HTTP OAuth認證
OAuth對於Http來說,就是放在Authorization header中的不是用戶名密碼, 而是一個token。微軟的Skydrive就是使用這樣的方式。
參考:http://www.tuicool.com/articles/qqeuE3
摘要認證
digest authentication(HTTP1.1提出的基本認證的替代方法)
這個認證可以看做是基本認證的增強版本,不包含密碼的明文傳遞。
引入了一系列安全增強的選項;“保護質量”(qop)、隨機數計數器由客戶端增加、以及客戶生成的隨機數。
在HTTP摘要認證中使用 MD5 加密是為了達成"不可逆的",也就是說,當輸出已知的時候,確定原始的輸入應該是相當困難的。如果密碼本身太過簡單,也許可以
通過嘗試所有可能的輸入來找到對應的輸出(窮舉攻擊),甚至可以通過字典或者適當的查找表加快查找速度。
示例及說明
下面的例子僅僅涵蓋了“auth”保護質量的代碼,因為在撰寫期間,所知道的只有Opera和Konqueror網頁瀏覽器支持“auth-int”(帶完整性保護的認證)。
典型的認證過程包括如下步驟:
客戶端請求一個需要認證的頁面,但是不提供用戶名和密碼。通常這是由於用戶簡單的輸入了一個地址或者在頁面中點擊了某個超鏈接。
服務器返回401 "Unauthorized" 響應代碼,並提供認證域(realm),以及一個隨機生成的、只使用一次的數值,稱為密碼隨機數 nonce。
此時,瀏覽器會向用戶提示認證域(realm)(通常是所訪問的計算機或系統的描述),並且提示用戶名和密碼。用戶此時可以選擇取消。
一旦提供了用戶名和密碼,客戶端會重新發送同樣的請求,但是添加了一個認證頭包括了響應代碼。
注意:客戶端可能已經擁有了用戶名和密碼,因此不需要提示用戶,比如以前存儲在瀏覽器里的。
客戶端請求 (無認證):
GET /dir/index.html HTTP/1.0 Host: localhost (跟隨一個新行,形式為一個回車再跟一個換行)
服務器響應:
HTTP/1.0 401 Unauthorized Server: HTTPd/0.9 Date: Sun, 10 Apr 2005 20:26:47 GMT WWW-Authenticate: Digest realm="testrealm@host.com", //認證域 qop="auth,auth-int", //保護質量 nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", //服務器密碼隨機數 opaque="5ccc069c403ebaf9f0171e9517f40e41" Content-Type: text/html Content-Length: 311 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd"> <HTML> <HEAD> <TITLE>Error</TITLE> <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=ISO-8859-1"> </HEAD> <BODY><H1>401 Unauthorized.</H1></BODY> </HTML>
客戶端請求 (用戶名 "Mufasa", 密碼 "Circle Of Life"):
GET /dir/index.html HTTP/1.0 Host: localhost Authorization: Digest username="Mufasa", realm="testrealm@host.com", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", uri="/dir/index.html", qop=auth, nc=00000001,//請求計數 cnonce="0a4f113b", //客戶端密碼隨機數 response="6629fae49393a05397450978507c4ef1", opaque="5ccc069c403ebaf9f0171e9517f40e41" (跟隨一個新行,形式如前所述)。
服務器響應:
HTTP/1.0 200 OK Server: HTTPd/0.9 Date: Sun, 10 Apr 2005 20:27:03 GMT Content-Type: text/html Content-Length: 7984 (隨后是一個空行,然后是所請求受限制的HTML頁面)
response 值由三步計算而成。當多個數值合並的時候,使用冒號作為分割符:
1、對用戶名、認證域(realm)以及密碼的合並值計算 MD5 哈希值,結果稱為 HA1。
2、對HTTP方法以及URI的摘要的合並值計算 MD5 哈希值,例如,"GET" 和 "/dir/index.html",結果稱為 HA2。
3、對HA1、服務器密碼隨機數(nonce)、請求計數(nc)、客戶端密碼隨機數(cnonce)、保護質量(qop)以及 HA2 的合並值計算 MD5 哈希值。結果即為客戶端提供的
response 值。
因為服務器擁有與客戶端同樣的信息,因此服務器可以進行同樣的計算,以驗證客戶端提交的 response 值的正確性。在上面給出的例子中,結果是如下計算的。
(MD5()表示用於計算MD5哈希值的函數;“”表示接下一行;引號並不參與計算)
HA1 = MD5( "Mufasa:testrealm@host.com:Circle Of Life" ) = 939e7578ed9e3c518a452acee763bce9 HA2 = MD5( "GET:/dir/index.html" ) = 39aff3a2bab6126f332b942af96d3366 Response = MD5( "939e7578ed9e3c518a452acee763bce9:\ dcd98b7102dd2f0e8b11d0f600bfb0c093:\ 00000001:0a4f113b:auth:\ 39aff3a2bab6126f332b942af96d3366" ) = 6629fae49393a05397450978507c4ef1
此時客戶端可以提交一個新的請求,重復使用服務器密碼隨機數(nonce)(服務器僅在每次“401”響應后發行新的nonce),但是提供新的客戶端密碼隨機數(cnonce)。在后續的請求中,十六進制請求計數器(nc)必須比前一次使用的時候要大,否則攻擊者可以簡單的使用同樣的認證信息重放老的請求。由服務器來確保在每個發出的密碼隨機數nonce時,計數器是在增加的,並拒絕掉任何錯誤的請求。顯然,改變HTTP方法和/或計數器數值都會導致不同的 response值。
服務器應當記住最近所生成的服務器密碼隨機數nonce的值。也可以在發行每一個密碼隨機數nonce后,記住過一段時間讓它們過期。如果客戶端使用了一個過期的值,服務器應該響應“401”狀態號,並且在認證頭中添加stale=TRUE,表明客戶端應當使用新提供的服務器密碼隨機數nonce重發請求,而不必提示用戶其它用戶名和口令。
Cookie Auth
Cookie認證機制:用戶輸入用戶名和密碼發起請求,服務器認證后給每個Session分配一個唯一的JSESSIONID,並通過Cookie發送給客戶端。
當客戶端發起新的請求的時候,將在Cookie頭中攜帶這個JSESSIONID。這樣服務器能夠找到這個客戶端對應的Session。默認的,當我們關閉瀏覽器的時候,客戶端cookie會被刪除,可以通過修改cookie 的expire time使cookie在一定時間內有效。但是服務器端的session不會被銷毀,除非通過invalidate或超時。
Token Auth
常用的Token Auth(和Cookie Auth區別不大):
- 首次登陸,用戶名和密碼驗證過之后,將sessionId保存在token中,或者將一個key保存在token中,key的值可以設置為用戶唯一性的信息(賬號/密碼/身份認證機制(電話號/身份證號/支付寶賬號/銀行卡信息)...);當然我們在程序中的實現是保存UUID作為ticket。
- 設置token的有效期,並保存在服務器數據庫中;
- 服務器將這個token值返回給客戶端,客戶端拿到 token 值之后,將 token 保存在 cookie 中,以后客戶端再次發送網絡請求(一般不是登錄請求)的時候,就會將這個 token 值附帶到參數中發送給服務器。服務器接收到客戶端的請求之后,會取出token值與保存在本地(數據庫)中的token值做對比!
如果兩個 token 值相同 :說明用戶登錄成功過!當前用戶處於登錄狀態!如果沒有這個token或者過期,則設置token為無效,並讓用戶重新登錄。
這種方式在客戶端變化不大,也要利用cookie,改動的是服務器端
過去:通過sessionId查找Tomcat服務器內存中是否有sessionId對應的session存在,若存在,則表示登陸過,然后從session找出用戶信息;
現在:通過token查找數據庫中是否有相同的token,並且token要處於有效期前,有的話通過token在數據庫中找出用戶信息,否則重新登錄,(其實還包括sessionId的驗證,因為jsp默認創建session)。
如果覺得查詢數據庫比較耗時,可以用memcache或者redis緩存一下。
首先說明一下session何時會被創建:
- 1、 請求JSP頁面時自動創建session,利用request.getSession(true);語句
原因:
由於HTTP是無狀態協議,這意味着每次客戶端檢索網頁時,都要單獨打開一個服務器http連接,如果我同一個瀏覽器,不同頁面打開你的主頁10次,那就要進行10次連接和斷開(TCP3次握手,4次揮手),浪費系統資源,http提供了一種長連接,keep-alive,相同會話的不同請求可以用同一連接,故jsp默認創建session。而session的創建過程中會自動將sessionId寫入cookie的JSESSIONID中的,這樣,只要不關閉瀏覽器,你在同一網站的任意網頁跳轉,由於每次請求都會攜帶同一個sessionId,不會重新創建新的會話,防止創建多個會話浪費系統資源。
否則:黑客利用幾台主機,瘋狂的點擊某一個JSP頁面,如果每次點擊都創建一個新的會話,可能使服務器崩潰。
例子:
登錄函數:
// 用戶登錄操作 public void login(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("gb2312"); String account = request.getParameter("account"); consumerDao = new ConsumerDao(); ConsumerForm consumerForm = consumerDao.getConsumerForm(account); if (consumerForm == null) { request.setAttribute("information", "您輸入的用戶名不存在,請重新輸入!"); } else if (!consumerForm.getPassword().equals(request.getParameter("password"))) { request.setAttribute("information", "您輸入的登錄密碼有誤,請重新輸入!"); } else { request.setAttribute("form", consumerForm); } RequestDispatcher requestDispatcher = request.getRequestDispatcher("dealwith.jsp"); requestDispatcher.forward(request, response); }
登錄主頁:
<body onselectstart="return false"> <table width="800" height="496" border="0" align="center" cellpadding="0" cellspacing="0" background="images/login.jpg"> <tr> <td valign="top"> <table width="658" border="0"> <tr> <td colspan="2"> </td> </tr> <tr> <td width="92" height="358"> </td> <td width="550" valign="bottom"> <form name="form1" method="post" action="ConsumerServlet?method=0&sign=0" onSubmit="return userCheck()"> <table width="291" border="0" align="center" cellpadding="0" cellspacing="0"> <tr> <td width="66" height="30">用戶名:</td> <td width="225"><input name="account" type="text" class="inputinput" id="account" size="30"></td> </tr> <tr> <td height="30">密 碼:</td> <td><input name="password" type="password" class="inputinput" id="password" size="30"></td> </tr> <tr> <td height="30" colspan="2" align="center"><input type="image" class="inputinputinput" src="images/land.gif"> <a href="#" onClick="javascript:document.form1.reset()"><img src="images/reset.gif"></a> <a href="consumer/accountAdd.jsp"><img src="images/register.gif"></a></td> </tr> </table> </form> </td> </tr> </table> </td> </tr> </table> </body>
其中並沒有利用session的語句,但是主界面,當還沒有登錄時:
可見有一個JSESSIONID,並且點擊其他頁面並沒有開啟新的JSESSIONID。但是換一個瀏覽器就會產生新的JSESSIONID,因為不同瀏覽器的會話緩存是不可以互相用的
登錄以后還是這個JSESSIONID:
Session的銷毀只有三種方式:
1.調用了session.invalidate()方法
2.session過期(超時)
3.服務器重新啟動
單個JSP頁面禁用session方式
<%@ page session="false">
- 在servlet中,只要代碼不調用Session相關的API就不會創建session
request.getSession() 等價於 request.getSession(true)
這兩個方法的作用是相同的,查找請求中是否有關聯的JSESSIONID,如果有則返回這個號碼所對應的session對象,如果沒有則生成一個新的session對象。所以說,通過此方法是一定可以獲得一個session對象。
request.getSession(false) 查找請求中是否有關聯的JSESSIONID號,如果有則返回這個號碼所對應的session對象,如果沒有則返回一個null。
注意在創建session的過程中,sessionId是自動寫入cookie的JSESSIONID中的,如果禁用cookie,則通過url回傳
參考的頭條項目:
JSESSIONID和token(即ticket)同時存在,盡量不往session里放東西,將用戶主鍵,過期時間等都存到和ticket一起的表中,優點:1.減少內存占用;2.Tomcat默認30分鍾session銷毀,采用token可以長時間保持登錄狀態,但可能就不是這一個session了;3.除了JSESSIONID驗證一致性,增加一個token驗證,防止黑客暴力破解JSESSIONID,但黑客可以獲得JSESSIONID,估計獲得token也不難,只是會麻煩一點。
牛客網:
淘寶網:
都用了token和session(NOWCODERCLINETID和cookie2,這個只是猜測),只是不是直接保存JSESSIONID,改名字了,如果攻擊者不分析站點,就不能猜到Session名稱,阻擋部分攻擊。
真正的應用:JWT
通過token可以將用戶的基本信息(非隱私的,比如UserId,過期時間,生成的隨機key等)全部加密簽名后放入token中,從而服務器端不需要保存用戶登錄信息,大大減輕服務器壓力。用戶認證完全靠token識別,通過簽名來保證token沒有被修改過(只有服務器才知道秘鑰,比如常見的非對稱加密算法),是服務器下發的token。在后續請求中,服務端只需要對用戶請求中包含的token進行解碼,驗證用戶登錄是否過期。
很多大型網站也都在用,比如 Facebook,Twitter,Google+,Github 等等,比起傳統的身份驗證方法,Token 擴展性更強,也更安全點,非常適合用在 Web 應用或者移動應用上。
Token Auth優點:
- 減輕服務器壓力:通過token可以將用戶的基本信息(非隱私的,比如UserId,過期時間,生成的隨機key等)全部加密簽名后放入token中,從而服務器端不需要保存用戶登錄信息,大大減輕服務器壓力。用戶認證完全靠token識別,通過簽名來保證token沒有被修改過(只有服務器才知道秘鑰,比如常見的非對稱加密算法),是服務器下發的token。
- 支持跨域訪問:因為服務器並沒有保存登錄狀態,完全靠簽名的token識別,那么另一個網站只要有對應的私鑰,就可以對token驗證,前提是傳輸的用戶認證信息通過HTTP頭傳輸;
- 更適用CDN: 可以通過內容分發網絡請求你服務端的所有資料(如:javascript,HTML,圖片等),因為不需要同步服務器上的登錄狀態信息;
- 性能更好: 因為從token中可以獲得userId,不用查詢登錄狀態表;
但是都不能很好的預防會話劫持
什么是Realm
Tomcat提供Realm支持。
Tomcat使用Realm使某些特定的用戶組具有訪問某些特定的Web應用的權限,而沒有權限的用戶不能訪問這個應用。
Tomcat提供了三種不同Realm對訪問某個Web應用程序的用戶進行相應的驗證。
1、JDBCRealm,這個Realm將用戶信息存在數據庫里,通過JDBC從數據庫中獲得用戶信息並進行驗證。
2、JNDIRealm,將用戶信息存在基於LDAP等目錄服務的服務器里,通過JNDI技術從LDAP服務器中獲取用戶信息並進行驗證。
3、MemoryRealm,將用戶信息存在一個xml文件中,對用戶進行驗證時,將會從相應的xml文件中提取用戶信息。manager(Tomcat提供的一個web應用程序)應用在進行驗證時即使用此種Realm。Realm類似於Unix里面的group。在Unix中,一個group對應着系統的一定資源,某個group不能訪問不屬於它的資源。Tomcat用Realm來對不同的應用(類似系統資源)賦給不同的用戶(類似group)。沒有權限的用戶則不能訪問這個應用。
HTTPS傳輸協議原理
HTTPS(HTTP over SSL,實際上是在原有的 HTTP 數據外面加了一層 SSL 的封裝。HTTP 協議原有的 GET、POST 之類的機制,基本上原封不動),是以安全為目標的HTTP通道,簡單講是HTTP的安全版。即HTTP下加入SSL層,HTTPS的安全基礎是SSL,因此加密的詳細內容請看SSL。
兩種基本的加解密算法類型
- 對稱加密:密鑰只有一個,加密解密為同一個密碼,且加解密速度快,典型的對稱加密算法有DES、AES等。
- 非對稱加密:密鑰成對出現(且根據公鑰無法推知私鑰,根據私鑰也無法推知公鑰),加密解密使用不同密鑰(公鑰加密需要私鑰解密,私鑰加密需要公鑰解密),相對對稱加密速度較慢,典型的非對稱加密算法有RSA、DSA等。
HTTPS通信過程
HTTPS通信的優點
客戶端產生的密鑰只有客戶端和服務器端能得到;
加密的數據只有客戶端和服務器端才能得到明文;
客戶端到服務端的通信是安全的。
SSL/TLS協議
SSL/TLS協議希望達到:
(1) 所有信息都是加密傳播,第三方無法竊聽。
(2) 具有校驗機制,一旦被篡改,通信雙方會立刻發現。
(3) 配備身份證書,防止身份被冒充。
歷史
1994年,NetScape公司設計了SSL協議(Secure Sockets Layer)的1.0版,但是未發布。
1995年,NetScape公司發布SSL 2.0版,很快發現有嚴重漏洞。
1996年,SSL 3.0版問世,得到大規模應用。
1999年,互聯網標准化組織ISOC接替NetScape公司,發布了SSL的升級版TLS 1.0版。
2006年和2008年,TLS進行了兩次升級,分別為TLS 1.1版和TLS 1.2版。最新的變動是2011年TLS 1.2的修訂版。
目前,應用最廣泛的是TLS 1.0,接下來是SSL 3.0。但是,主流瀏覽器都已經實現了TLS 1.2的支持。
TLS 1.0通常被標示為SSL 3.1,TLS 1.1為SSL 3.2,TLS 1.2為SSL 3.3。
基本的運行過程
SSL/TLS協議的基本思路是采用公鑰加密法,也就是說,客戶端先向服務器端索要公鑰,然后用公鑰加密信息,服務器收到密文后,用自己的私鑰解密。
如何保證公鑰不被篡改?
解決方法:將公鑰放在數字證書中。只要證書是可信的,公鑰就是可信的。
公鑰加密計算量太大,如何減少耗用的時間?
解決方法:每一次對話(session),客戶端和服務器端都生成一個"對話密鑰"(session key),用它來加密信息。由於"對話密鑰"是對稱加密,所以運算速度非常快,而服務器公鑰只用於加密"對話密鑰"本身,這樣就減少了加密運算的消耗時間。即在客戶端與服務器間傳輸的數據是通過使用對稱算法(如 DES 或 RC4)進行加密的。
因此,SSL/TLS協議的基本過程是這樣的:
(1) 客戶端向服務器端索要並驗證公鑰。
(2) 雙方協商生成"對話密鑰"。
(3) 雙方采用"對話密鑰"進行加密通信。
上面過程的前兩步,又稱為"握手階段"(handshake)。
握手階段的詳細過程
"握手階段"涉及四次通信,我們一個個來看。
1 客戶端發出請求(ClientHello)
首先,客戶端(通常是瀏覽器)先向服務器發出加密通信的請求,這被叫做ClientHello請求。
在這一步,客戶端主要向服務器提供以下信息。
(1) 支持的協議版本,比如TLS 1.0版。
(2) 一個客戶端生成的隨機數,稍后用於生成"對話密鑰"。
(3) 支持的加密方法,比如RSA公鑰加密。
(4) 支持的壓縮方法。
這里需要注意的是,客戶端發送的信息之中不包括服務器的域名。也就是說,理論上服務器只能包含一個網站,否則會分不清應該向客戶端提供哪一個網站的數字證書。這就是為什么通常一台服務器只能有一張數字證書的原因。
對於虛擬主機的用戶來說,這當然很不方便。2006年,TLS協議加入了一個Server Name Indication擴展,允許客戶端向服務器提供它所請求的域名。
2 服務器回應(SeverHello)
服務器收到客戶端請求后,向客戶端發出回應,這叫做SeverHello。服務器的回應包含以下內容。
(1) 確認使用的加密通信協議版本,比如TLS 1.0版本。如果瀏覽器與服務器支持的版本不一致,服務器關閉加密通信。
(2) 一個服務器生成的隨機數,稍后用於生成"對話密鑰"。
(3) 確認使用的加密方法,比如RSA公鑰加密。
(4) 服務器證書。
除了上面這些信息,如果服務器需要確認客戶端的身份,就會再包含一項請求,要求客戶端提供"客戶端證書"。比如,金融機構往往只允許認證客戶連入自己的網絡,就會向正式客戶提供USB密鑰,里面就包含了一張客戶端證書。
3 客戶端回應
客戶端收到服務器回應以后,首先驗證服務器證書。如果證書不是可信機構頒布、或者證書中的域名與實際域名不一致、或者證書已經過期,就會向訪問者顯示一個警告,由其選擇是否還要繼續通信。
如果證書沒有問題,客戶端就會從證書中取出服務器的公鑰。然后,向服務器發送下面三項信息。
(1) 一個隨機數。該隨機數用服務器公鑰加密,防止被竊聽。
(2) 編碼改變通知,表示隨后的信息都將用雙方商定的加密方法和密鑰發送。
(3) 客戶端握手結束通知,表示客戶端的握手階段已經結束。這一項同時也是前面發送的所有內容的hash值,用來供服務器校驗。
上面第一項的隨機數,是整個握手階段出現的第三個隨機數,又稱"pre-master key"。有了它以后,客戶端和服務器就同時有了三個隨機數,接着雙方就用事先商定的加密方法,各自生成本次會話所用的同一把"會話密鑰"。
至於為什么一定要用三個隨機數,來生成"會話密鑰":
"不管是客戶端還是服務器,都需要隨機數,這樣生成的密鑰才不會每次都一樣。由於SSL協議中證書是靜態的,因此十分有必要引入一種隨機因素來保證協商出來的密鑰的隨機性。
對於RSA密鑰交換算法來說,pre-master-key本身就是一個隨機數,再加上hello消息中的隨機,三個隨機數通過一個密鑰導出器最終導出一個對稱密鑰。
pre master的存在在於SSL協議不信任每個主機都能產生完全隨機的隨機數,如果隨機數不隨機,那么pre master secret就有可能被猜出來,那么僅適用pre master secret作為密鑰就不合適了,因此必須引入新的隨機因素,那么客戶端和服務器加上pre master secret三個隨機數一同生成的密鑰就不容易被猜出了,一個偽隨機可能完全不隨機,可是是三個偽隨機就十分接近隨機了,每增加一個自由度,隨機性增加的可不是一。"
此外,如果前一步,服務器要求客戶端證書,客戶端會在這一步發送證書及相關信息。
4 服務器的最后回應
服務器收到客戶端的第三個隨機數pre-master key之后,計算生成本次會話所用的"會話密鑰"。然后,向客戶端最后發送下面信息。
(1)編碼改變通知,表示隨后的信息都將用雙方商定的加密方法和密鑰發送。
(2)服務器握手結束通知,表示服務器的握手階段已經結束。這一項同時也是前面發送的所有內容的hash值,用來供客戶端校驗。
至此,整個握手階段全部結束。接下來,客戶端與服務器進入加密通信,就完全是使用普通的HTTP協議,只不過用"會話密鑰"加密內容。
非對稱加密
需要一對密鑰,一個是私人密鑰,另一個則是公開密鑰。這兩個密鑰是數學相關,用某用戶密鑰加密后所得的信息,只能用該用戶的解密密鑰才能解密。如果知道了其中一個,並不能計算出另外一個。因此如果公開了一對密鑰中的一個,並不會危害到另外一個的秘密性質。稱公開的密鑰為公鑰;不公開的密鑰為私鑰。
如果加密密鑰是公開的,這用於客戶給私鑰所有者上傳加密的數據,這被稱作為公開密鑰加密(狹義)。例如,網絡銀行的客戶發給銀行網站的操作信息的加密數據,采用公鑰加密。
如果解密密鑰是公開的,用私鑰加密的信息,可以用公鑰對其解密,用於客戶驗證持有私鑰一方發布的數據或文件是完整准確的,接收者由此可知這條信息確實來自於擁有私鑰的某人,這被稱作數字簽名,公鑰的形式就是數字證書。
每個人都有一對“鑰匙”(數字身份),其中一個只有她/他本人知道(私鑰),另一個公開的(公鑰)。簽名的時候用私鑰,驗證簽名的時候用公鑰。又因為任何人都可以落款申稱她/他就是使用者本人,因此公鑰必須向接受者信任的人(身份認證機構)來注冊。注冊后身份認證機構給使用者發一數字證書。對文件簽名后,使用者把此數字證書連同文件及簽名一起發給接受者,接受者向身份認證機構求證是否真地是用使用者的密鑰簽發的文件。
在中國,數字簽名是具法律效力的,正在被普遍使用。
使用最廣泛的是RSA算法
RSA算法原理
1977年,三位數學家Rivest、Shamir 和 Adleman 設計了一種算法,可以實現非對稱加密。這種算法用他們三個人的名字命名,叫做RSA算法。
這種算法非常可靠,密鑰越長,它就越難破解。根據已經披露的文獻,目前被破解的最長RSA密鑰是768個二進制位。也就是說,長度超過768位的密鑰,還無法破解(至少沒人公開宣布)。因此可以認為,1024位的RSA密鑰基本安全,2048位的密鑰極其安全。
"對極大整數做因數分解的難度決定了RSA算法的可靠性。換言之,對一極大整數做因數分解愈困難,RSA算法愈可靠。
假如有人找到一種快速因數分解的算法,那么RSA的可靠性就會極度下降。但找到這樣的算法的可能性是非常小的。今天只有短的RSA密鑰才可能被暴力破解。到2008年為止,世界上還沒有任何可靠的攻擊RSA算法的方式。
只要密鑰長度足夠長,用RSA加密的信息實際上是不能被解破的。"
RSA性能是非常低的,原因在於尋找大素數、大數計算、數據分割需要耗費很多的CPU周期,所以一般的HTTPS連接只在握手時使用非對稱加密,通過握手交換對稱加密密鑰(其實只有第三次通信,客戶端向服務器發送隨機數時用公鑰加密),在之后的通信走對稱加密。
互質關系
如果兩個正整數,除了1以外,沒有其他公因子,我們就稱這兩個數是互質關系(coprime)。比如,15和32沒有公因子,所以它們是互質關系。這說明,不是質數也可以構成互質關系。
關於互質關系,不難得到以下結論:
1. 任意兩個質數構成互質關系,比如13和61。
2. 一個數是質數,另一個數只要不是前者的倍數,兩者就構成互質關系,比如3和10。
3. 如果兩個數之中,較大的那個數是質數,則兩者構成互質關系,比如97和57。
4. 1和任意一個自然數是都是互質關系,比如1和99。
5. p是大於1的整數,則p和p-1構成互質關系,比如57和56。
6. p是大於1的奇數,則p和p-2構成互質關系,比如17和15。
歐拉函數
任意給定正整數n,請問在小於等於n的正整數之中,有多少個與n構成互質關系?(比如,在1到8之中,有多少個數與8構成互質關系?)
計算這個值的方法就叫做歐拉函數,以φ(n)表示。在1到8之中,與8形成互質關系的是1、3、5、7,所以 φ(n) = 4。
因為任意一個大於1的正整數,都可以寫成一系列質數的積。
歐拉函數的通用計算公式
歐拉定理
模反元素
詳細看:
http://www.ruanyifeng.com/blog/2013/06/rsa_algorithm_part_one.html
密鑰生成的步驟
假設愛麗絲要與鮑勃進行加密通信,她該怎么生成公鑰和私鑰呢?
第一步,隨機選擇兩個不相等的質數p和q。
愛麗絲選擇了61和53。(實際應用中,這兩個質數越大,就越難破解。)
第二步,計算p和q的乘積n。
愛麗絲就把61和53相乘。
n = 61×53 = 3233
n的長度就是密鑰長度。3233寫成二進制是110010100001,一共有12位,所以這個密鑰就是12位。實際應用中,RSA密鑰一般是1024位,重要場合則為2048位。
第三步,計算n的歐拉函數φ(n)。
根據公式:
φ(n) = (p-1)(q-1)
愛麗絲算出φ(3233)等於60×52,即3120。
第四步,隨機選擇一個整數e,條件是1< e < φ(n),且e與φ(n) 互質。
愛麗絲就在1到3120之間,隨機選擇了17。(實際應用中,常常選擇65537。)
第五步,計算e對於φ(n)的模反元素d。
所謂"模反元素"就是指有一個整數d,可以使得ed被φ(n)除的余數為1。
ed ≡ 1 (mod φ(n))
這個式子等價於
ed - 1 = kφ(n)
於是,找到模反元素d,實質上就是對下面這個二元一次方程求解。
ex + φ(n)y = 1
已知 e=17, φ(n)=3120,
17x + 3120y = 1
這個方程可以用"擴展歐幾里得算法"求解,此處省略具體過程。總之,愛麗絲算出一組整數解為 (x,y)=(2753,-15),即 d=2753。
至此所有計算完成。
第六步,將n和e封裝成公鑰,n和d封裝成私鑰。
在愛麗絲的例子中,n=3233,e=17,d=2753,所以公鑰就是 (3233,17),私鑰就是(3233, 2753)。
實際應用中,公鑰和私鑰的數據都采用ASN.1格式表達(實例)。
結論:如果n可以被因數分解,d就可以算出,也就意味着私鑰被破解。
大整數的因數分解,是一件非常困難的事情。目前,除了暴力破解,還沒有發現別的有效方法。
詳細看:
http://www.ruanyifeng.com/blog/2013/07/rsa_algorithm_part_two.html
誤區
HTTPS無法緩存
在大家的觀念里,出於安全考慮,瀏覽器不會在本地保存HTTPS緩存。 實際上, 只要在HTTP頭中使用特定命令,HTTPS是可以緩存的。
Microsoft 項目經理Eric Lawrence 寫道:
比如,如果頭命令是Cache-Control:max-age=600,那么HTTPS的網頁就將被IE緩存10分鍾,IE的緩存策略與是否使用HTTPS協議無線。其它瀏覽器也有類似的操作方法。
Firefox默認只在內存中緩存HTTPS。但是,只要頭命令中有Cache-Control:Public,緩存就會被寫到硬盤上。下面的圖片顯示,Firefox的硬盤緩存中有HTTPS內容,頭命令正是Cache-Control:Public。
有了HTTPS,Cookie和查詢字符串就安全了
雖然無法直接從HTTPS數據中讀取Cookie和查詢字符串,但是你仍然需要使它們的值變得難以預測。
比如,曾經有一家英國銀行,直接使用順序排列的數值表示session id:
黑客可以先注冊一個賬戶,找到這個cookie,看到這個值的表示方法。然后,改動cookie,從而劫持其他人的session id。至於查詢字符串,也可以通過類似方式泄漏。