目錄
一、前言
二、登錄認證安全問題
3.1 認證方式選擇
三、常用的加密方案
3.1 MD5加密算法
3.1.1 md5特點
3.1.2 md5原理
3.1.3 md5使用場景
3.2 AES加密算法
3.2.1 AES簡介
3.2.2 AES加解原理
3.2.3 AES算法優缺點
3.2.4 AES算法使用場景
3.3 RSA加密算法
3.3.1 RSA加密算法介紹
3.3.2 RSA加密算法原理
3.3.3 RSA算法優缺點
3.3.4 RSA算法應用場景
3.4 JWT算法
3.4.1 JWT是什么
3.4.2 JWT算法特點
3.4.3?JWT工作原理
3.4.4?JWT優點
3.5 OAuth 2.0
3.5.1 OAuth2.0 介紹
3.5.2?OAuth2.0 流程
3.5.3?OAuth2.0 授權類型
3.5.4?OAuth2.0 核心角色
3.5.5?OAuth2.0 優點
3.6 數字簽名
3.6.1 場景分析一
3.6.2 場景分析二
3.6.3 數字簽名流程
四、常用加密算法使用
4.1 MD5加密算法使用
4.2 AES加密算法使用
4.3 RSA加密算法使用
4.4 JWT加密算法使用
4.4.1 JWT構成
4.4.2 操作演示
4.5 數字簽名使用
五、寫在文末
一、前言
登錄認證功能可以說是所有系統基本功能。比如在springboot構建的微服務工程中,大家熟悉的jwt技術,以及基于oauth 2.0協議的安全認證等,就是用于構建系統安全的基礎技術框架。
認證的目的是為了保護系統資源不被惡意使用,通常來說,最基礎的認證方式即大家熟悉的用戶名和密碼。隨著移動端應用的廣泛使用,用戶對登錄認證的操作便捷性也提出了越來越高的要求,像微信掃掃碼登錄,手機號登錄等方式都是適應時代發展的產物。
二、登錄認證安全問題
在所有需要登錄認證的系統中,首要考慮的問題包括下面兩點:
-
采用什么認證方式?是用戶名+密碼?微信掃碼登錄?還是手機短信驗證碼登錄?
-
如果是用戶名+密碼登錄,密碼采用什么方式進行加密?
3.1 認證方式選擇
?選擇哪種方式作為系統的認證方案呢?這個沒有統一的答案。目前主流的互聯網產品從大類上可以分為C端和B端兩類產品。繼續細分下去,C端產品又可以分成多種類型。比如面向社交的產品,電商交易產品,音視頻等。而B端產品一般面向企業,政府,銀行,券商等業務,主要是企業級內部業務使用。
相對來說,面向C端的產品通常為了考慮用戶的使用體驗,以及快速占領市場為目標,會盡可能讓登錄更簡單,減少用戶的操作難度,這才有今天的掃碼登錄,短信驗證碼登錄的模式。
而B端產品,更多的是業務型驅動,系統的使用人員通常是為了處理某些具體場景下的業務而保持對系統的長期使用,所以在登錄認證方面,為了系統的安全性,適當犧牲一些性能和體驗也能被接受。這種情況下,就可以考慮安全性更好的登錄認證方案。
三、常用的加密方案
互聯網產品發展到今天,為了擁抱用戶端的需求和變化,逐漸出現很多種成熟的密碼加密方案,下面就常用的密碼加密方式展開說明。
3.1 MD5加密算法
MD5,全稱:Message Digest Algorithm 5,是一種常用的哈希算法,可用于將任意長度的數據轉換為固定長度的哈希值(通常為128位,16個字節)。在早些年,MD5被廣泛使用過。但現在它已經不再被視為安全的加密算法,因為它存在一些安全漏洞,如碰撞攻擊和彩虹表攻擊。
3.1.1 md5特點
md5加密具有如下特點:
-
單向加密,即明文經過加密之后形成密文;
-
產生的是固定長度的散列值,無論輸入了多長的數據,經過 MD5 處理后都只會產生一個16字節的散列值;
-
不可逆,經過 MD5 處理后得到的散列結果,無法計算出原始數據,正是因為 MD5 無法從密文還原成明文,它不能用于解密了;
-
運算速度快,MD5 采用的是位運算,速度很快,幾乎不占用 CPU 資源;
-
安全性差,1996年后該算法被證實存在弱點,可被加以破解。2004年,證實 MD5 算法無法防止碰撞,因此不適用于安全性認證,比如 SSL 公開密鑰認證、數字簽名等用途。2011年,RFC 6151 禁止 MD5 用作密鑰散列消息認證碼;
3.1.2 md5原理
MD5 本質上也是一種哈希算法,會輸出一個16字節(128位)固定長度的散列值,該算法的大致流程如上圖,其 算法原理,可簡單描述為:MD5 碼以512位分組來處理輸入的信息,且每一分組又被劃分為16個32位子分組,經過了一系列的處理后,算法的輸出由四個32位分組組成,將這四個32位分組級聯后將生成一個128位散列值。
3.1.3 md5使用場景
盡管MD5的安全性方面相比其他主流加密算法稍顯不足,但是經過多年的發展和沉淀,也有很多適用場景,下面列舉了一些經典的使用場景。
密碼存儲
在許多系統中,用戶的密碼需要存儲。而密碼直接存儲在數據庫中是非常危險的,因此MD5算法可以將密碼轉為不可逆的摘要值再存儲,即使數據庫被攻擊,被泄露的密碼也無法被還原。
數字簽名
數字簽名是利用密鑰將數據進行簽名,以確保數據完整性和可靠性。MD5算法可以生成一個用于數字簽名的摘要值,如果數據在傳輸過程中被篡改,摘要值也會發生改變,從而保證數據完整性。
文件校驗
MD5算法可以對文件進行校驗,確保文件沒有被篡改或損壞。對于需保密的文件,可以用MD5算法對文件進行加密處理,只有經過相應計算才能打開文件。
軟件驗證
MD5算法可用于軟件驗證,比如網站的下載鏈接可以加密,下載后進行MD5值比較,可以確保文件的完整性和安全性。
3.2 AES加密算法
3.2.1 AES簡介
AES加密算法,全稱Advanced Encryption Standard,是一種對稱加密算法,也被稱為Rijndael算法,用于加密和解密數據。它在數據傳輸、文件加密和網絡安全等領域有廣泛的應用。
3.2.2 AES加解原理
AES算法采用對稱加密算法進行加密和解密,使用相同的密鑰進行加密和解密。對稱加密算法是一種加密和解密使用相同密鑰的加密算法。AES算法使用相同的密鑰進行加密和解密,因此它是一種對稱加密算法。
加密過程:
-
首先,需要選擇一個密鑰,這個密鑰用于加密和解密數據;
-
將需要加密的數據分成若干個塊,每個塊的大小為128位;
-
對每個塊進行加密,加密過程采用AES算法進行;
-
將加密后的塊按順序拼接起來,形成加密后的數據;
解密過程:
-
首先,需要選擇一個密鑰,這個密鑰用于加密和解密數據;
-
將加密后的數據分成若干個塊,每個塊的大小為128位;
-
對每個塊進行解密,解密過程采用AES算法進行;
-
將解密后的塊按順序拼接起來,形成解密后的數據;
AES算法采用不同的密鑰長度,包括128位、192位和256位。這些密鑰長度的選擇可以根據需要和安全性要求進行調整。
3.2.3 AES算法優缺點
AES加密算法具有如下優點:
-
安全性高:AES算法是一種可靠的加密算法,在數據傳輸、文件加密和網絡安全等領域有廣泛的應用;
-
效率高:AES算法采用對稱加密算法進行加密和解密,使用相同的密鑰進行加密和解密。對稱加密算法比非對稱加密算法更加高效,因此AES算法具有更高的效率;
-
應用廣泛:AES算法在數據傳輸、文件加密和網絡安全等領域有著廣泛的應用。在數據傳輸過程中,AES算法可以對數據進行加密,保護數據的安全性。在文件加密過程中,AES算法可以對文件進行加密,保護文件的安全性。在網絡安全領域,AES算法可以對網絡數據進行加密,保護網絡的安全性;
AES算法缺點:
-
加密和解密的速度較慢,需要消耗大量的計算資源;
-
對于大文件的加密和解密操作,可能會導致內存不足的問題;
-
對于一些特定的攻擊方式,如側信道攻擊等,可能會導致AES算法的安全性受到影響;
-
密鑰管理較為困難,密鑰的生成和分發需要耗費大量的時間和資源;
-
對于數據的完整性和認證等方面的保護能力較弱,需要結合其他的算法來實現更全面的保護;
3.2.4 AES算法使用場景
AES具有廣泛的使用場景,如下列舉了常用的使用場景:
-
網絡通信:AES可用于保護網絡通信中的敏感數據,如HTTPS協議中使用TLS/SSL加密傳輸數據。
-
數據庫存儲:AES常用于對數據庫中敏感數據進行加密,以保護數據在存儲和備份過程中的安全性。
-
文件和文件夾加密:AES可以用于對文件和文件夾進行加密,確保敏感數據在存儲介質上的安全性。
-
電子郵件加密:AES可以用于對電子郵件內容和附件進行加密,防止郵件被未授權的人讀取。
-
移動設備和應用程序:AES被廣泛用于保護移動設備上的數據,如手機、平板電腦等,以及移動應用程序中的敏感數據。
-
虛擬私人網絡(VPN):AES常用于VPN連接的數據加密,確保遠程訪問和數據傳輸的安全性。
-
加密貨幣和區塊鏈:AES算法在加密貨幣和區塊鏈技術中起到了重要的作用,用于保護數字資產的安全性和隱私性。
-
云計算和數據中心:AES可用于對云計算和數據中心中的敏感數據進行加密,確保數據在傳輸和存儲過程中的安全性。
3.3 RSA加密算法
3.3.1 RSA加密算法介紹
RSA(Rivest-Shamir-Adleman)是一種非對稱加密算法,由Ron Rivest、Adi Shamir和Leonard Adleman在1977年共同提出。RSA算法的安全性基于大數分解的困難性,即將一個大的合數分解為其質數因子的乘積。
所謂非對稱加密算法,即加密和解密使用不同的密鑰。RSA算法的加密和解密密鑰是一對,一個是公鑰,另一個是私鑰。公鑰可被任何人使用加密消息,但只有私鑰的持有者才能解密消息。
3.3.2 RSA加密算法原理
RSA算法主要包括以下步驟:
-
密鑰生成:選擇兩個大素數p和q,計算它們的乘積n=pq,以及一個指示位數的正整數e。然后計算d,使得(ed) mod ((p-1)*(q-1)) = 1。最終生成公鑰為(n, e),私鑰為(n, d);
-
加密:將明文分組成較小的塊,并使用公鑰(n, e)中的公鑰指數e對每個塊進行加密。加密操作是對明文m執行加密操作得到密文c,其中c = m^e mod n;
-
解密:接收到密文c的接收者使用私鑰(n, d)中的私鑰指數d對密文進行解密。解密操作是對密文c執行解密操作得到原始明文m,其中m = c^d mod n;
結合上圖,可以得到使用非對稱加密算法進行加解密的完整流程如下。
加密流程:
-
將明文m轉換為整數M;
-
使用公鑰(n,e)進行加密,計算密文C = M^e(mod n);
-
將密文C發送給接收方;
解密流程:
-
接收方使用私鑰(n,d)進行解密,計算明文M = C^d(mod n);
-
將明文M轉換為字符串,即為原始明文m;
在加密過程中,明文m首先被轉換為整數M,然后使用公鑰進行加密,得到密文C。在解密過程中,接收方使用私鑰進行解密,得到明文M,然后將明文M轉換為字符串,即為原始明文m。
RSA算法中,加密和解密使用的密鑰是不同的。公鑰可以公開,任何人都可以使用來加密消息。私鑰必須保密,只有私鑰的持有者才能解密消息。這種非對稱加密的方式可以保證通信的安全性。
3.3.3 RSA算法優缺點
RSA算法優點:
-
安全性高:RSA算法基于大數分解的困難性,使得攻擊者很難破解密文。因為大數分解是一種計算量極大的數學問題,即使使用最先進的計算機和算法,也需要花費很長的時間才能破解密文。
-
非對稱加密:RSA算法采用非對稱加密方式,可以保證通信的安全性。非對稱加密是一種使用不同的密鑰進行加密和解密的加密方式,公鑰可以公開,任何人都可以使用它來加密消息,但只有私鑰的持有者才能解密消息。這種非對稱加密的方式可以保證通信的安全性。
-
數字簽名:RSA算法可以用于數字簽名,可以保證消息的完整性和真實性。數字簽名是一種用于驗證消息來源和完整性的技術,RSA算法可以用于生成數字簽名,保證簽名的真實性和不可偽造性。
-
算法公開:RSA算法是一種公開的加密算法,任何人都可以使用它進行加密和解密。算法的公開性可以促進技術的發展和應用,也可以避免算法被濫用和誤用。
RSA算法缺點:
-
計算量大:RSA算法的加密和解密計算量很大,特別是在處理大數時,會消耗大量的時間和計算資源。因為RSA算法的安全性依賴于密鑰長度,密鑰長度越長,加密和解密的計算量也就越大。
-
密鑰管理:RSA算法需要管理公鑰和私鑰,保證私鑰的安全性和公鑰的正確性是很重要的。如果私鑰泄露,就會導致通信的安全性受到威脅,如果公鑰被篡改,就會導致消息的真實性和完整性受到威脅。
-
明文長度限制:RSA算法對明文的長度有限制,一般不能超過密鑰長度減去一定的安全邊界。這是因為RSA算法采用的是模運算,明文長度超過一定的范圍,就會導致模運算的結果不準確,從而影響加密和解密的正確性。
-
選擇合適的密鑰長度:RSA算法的安全性依賴于密鑰長度,密鑰長度越長,安全性越高,但計算量也越大,需要在安全性和效率之間做出權衡。選擇合適的密鑰長度是一項關鍵的工作,需要根據具體的應用場景和安全需求來確定。
3.3.4 RSA算法應用場景
RSA加密算法在許多領域和場景中都有著廣泛應用,其中包括下面這些場景:
-
數據傳輸加密:RSA可用于保護敏感數據在傳輸過程中的安全性,特別是在網絡通信、電子郵件和即時通訊等場景中。
-
數字簽名:RSA可用于生成和驗證數字簽名,確保數據的完整性、身份驗證和防止篡改。
-
身份認證:RSA可用于用戶身份認證,如登錄過程中的密碼加密和解密。
-
虛擬私人網絡(VPN):RSA可以用于VPN連接中的密鑰交換和身份驗證,確保遠程訪問和數據傳輸的安全性。
-
數字證書和SSL/TLS:RSA可以用于生成和管理數字證書,用于建立安全的SSL/TLS連接,例如用于安全的網站訪問。
-
數據庫加密:RSA可用于保護數據庫中存儲的敏感數據,確保數據在存儲和備份過程中的安全性。
-
數字貨幣和區塊鏈:RSA算法在加密貨幣和區塊鏈技術中起到了重要的作用,用于生成和驗證數字簽名以及保護數字資產的安全性。
-
移動設備應用程序:RSA可用于移動設備應用程序中的數據加密和數字簽名,確保敏感數據在移動設備上的安全性。
總之,RSA加密算法適用于許多需要數據保護和身份認證的場景,特別是在網絡通信、數據存儲和移動設備等領域中得到廣泛應用。
3.4 JWT算法
3.4.1 JWT是什么
JWT (JSON Web Tokens): JWT是一種基于JSON格式的令牌,用于在客戶端和服務器之間傳遞安全信息。它使用基于密鑰的簽名算法(如HMAC-SHA256)或公鑰/私鑰對來驗證和保護數據的完整性。
通俗地說,JWT的本質就是一個不規則字符串,它可以將用戶相關信息保存到一個Json字符串中,然后進行編碼后得到一個JWT token,并且這個JWT token帶有簽名信息,接收后可以校驗是否被篡改。
3.4.2 JWT算法特點
去中心,無狀態成為很多系統在認證設計時的一個重要考慮因素,而選擇jwt作為認證方案正是考慮了這個特性。具體來說,如下:
-
可擴展性好,JWT的載荷(Payload)可以自定義,可以根據需要添加額外的信息。這使得JWT非常靈活,并且可以適應各種場景和需求。
-
無狀態性,由于JWT中包含了所有必要的信息,服務器不需要在后端存儲任何會話數據。每次請求都可以獨立驗證JWT的完整性和有效性,因此實現了無狀態的身份認證。
-
安全性,JWT使用密鑰對令牌進行簽名,以確保令牌的完整性和真實性。只有擁有正確密鑰的服務器才能生成和驗證JWT,防止令牌被篡改和偽造。
-
自包含性,JWT中包含了所有必要的信息,因此可以避免頻繁地查詢數據庫或緩存來獲取用戶信息。載荷中的聲明(Claims)可以存儲用戶身份、權限和其他相關信息,減少服務器的負載。
-
跨平臺支持,JWT是基于JSON規范的,因此可以在不同的編程語言和平臺之間輕松傳輸和解析。這使得JWT成為一種通用的身份認證和授權機制。
-
分布式系統支持,由于JWT的無狀態性和自包含性,它很適合于構建分布式系統和微服務架構。每個服務都可以獨立驗證JWT,并使用其中的信息進行身份認證和授權。
-
適用于單點登錄,JWT可以用于實現單點登錄(SSO),用戶只需要在一次身份驗證后,就可以使用生成的令牌訪問多個應用程序和服務。
需要注意的是,JWT并不適合存儲敏感數據,因為它的內容可以被解碼。因此,在使用JWT時,需要避免將敏感信息存儲在JWT的載荷中,或者對敏感信息進行額外的加密處理。另外,密鑰管理也是使用JWT時需要特別關注的方面,保證密鑰的安全性和合理的密鑰輪換機制。
3.4.3?JWT工作原理
JWT的工作流程通常如下:
-
客戶端使用身份驗證信息向服務器發送請求;
-
服務器驗證身份驗證信息,并生成一個JWT,并用密鑰對JWT進行簽名;
-
服務器將JWT作為響應的一部分發送給客戶端;
-
客戶端將JWT保存,以便在后續的請求中發送給服務器進行身份驗證;
-
服務器接收到帶有JWT的請求,驗證JWT的簽名,并讀取其中的信息來確認用戶身份和權限;
如下是jwt在實際開發使用中完整的工作流程,以token機制為例進行說明:
-
首先,客戶端(一般是前端)通過Web表單發起請求,調用登錄認證API,傳入用戶名何密碼;
-
后端校驗用戶名和密碼通過后,將用戶其他信息作為 JWT-Payload(負載),將其與頭部分別進行Base64編碼拼接后簽名形成一個JWT,形成的JWT就是一個形同111.zzz.xxx的字符串;
-
后端將JWT字符串作為登錄成功的返回結果返回給前端,前端將返回結果進行存儲,退出登錄時前端刪除保存的JWT;
-
登錄成功后,前端每次請求時,都會將JWT信息通過HTTP Header中的Authorization傳遞給后端;
-
后端檢查是否存在,如存在驗證JWT的有效性。例如,檢查簽名是否正確,檢查Token是否過期,檢查Token的接收方是否是自己;
-
驗證通過后,后端使用JWT中包含的用戶信息進行其他邏輯操作,返回結果;
3.4.4?JWT優點
JWT具備如下優點:
-
無狀態:由于JWT包含了所有必要的信息,服務器不需要保存任何會話數據,可以實現無狀態的身份驗證。
-
可擴展性:JWT的聲明(Claims)可以自定義,可以根據需要添加額外的信息。
-
安全性:JWT使用密鑰對令牌進行簽名,可以防止被篡改和偽造。
然而,需要注意的是,在使用JWT時,必須確保密鑰的安全性。泄露密鑰可能導致被惡意使用者篡改令牌或偽造令牌。因此,密鑰的生成、存儲和管理至關重要。
3.5 OAuth 2.0
3.5.1 OAuth2.0 介紹
嚴格來說,OAuth 2.0并不是一種具體的算法,而是一種用于授權的開放標準。它允許用戶向第三方應用程序提供對其資源的有限訪問權限,而無需共享其登錄憑據。像我們平時使用12306購票付款時,彈出一個微信或支付寶付款的界面,這個操作以及后面的動作就涉及到OAuth2.0的相關內容。
OAuth 2.0是一個授權框架(Authorization Framework),用于允許用戶通過第三方應用程序(客戶端)訪問受保護的資源,而無需直接公開他們的用戶名和密碼。OAuth 2.0的設計目標是簡化和統一授權流程,同時提供更好的安全性和可擴展性。
3.5.2?OAuth2.0 流程
OAuth 2.0的工作流程通常如下:
-
客戶端向授權服務器發起請求,請求訪問某個資源。
-
授權服務器驗證資源所有者的身份,如果驗證成功,則頒發訪問令牌給客戶端。
-
客戶端使用獲得的訪問令牌來請求訪問資源服務器。
-
資源服務器驗證訪問令牌的有效性,如果有效,則返回受保護的資源給客戶端。
以SpringSecurity為例,該框架遵循OAuth2.0協議,提供了多種安全認證方式,可以用來在企業級項目中集成使用,在下面是一張在實際項目中利用SpringSecurity+JWT 身份驗證及動態權限解決方案的完整流程;
3.5.3?OAuth2.0 授權類型
OAuth 2.0中有四種不同的授權流程(Grant Types),如下:
-
授權碼模式(Authorization Code Grant):適用于Web應用程序,并且可以在安全的服務器端進行授權流程。
-
密碼模式(Password Grant):適用于受信任的客戶端,并且資源所有者能夠直接提供其憑據。
-
簡化模式(Implicit Grant):適用于無法在客戶端保持機密信息的情況下,比如JavaScript應用程序或移動應用程序。
-
客戶端憑證模式(Client Credentials Grant):適用于客戶端自身需要訪問受保護資源,而不代表特定的用戶。
3.5.4?OAuth2.0 核心角色
OAuth 2.0的核心概念中主要包括以下角色:
-
資源所有者(Resource Owner):資源所有者是指授權訪問自己資源的用戶,通常是終端用戶。
-
客戶端(Client):客戶端是第三方應用程序,代表資源所有者請求訪問受保護的資源。客戶端可以是Web、移動應用程序或后端服務。
-
授權服務器(Authorization Server):授權服務器是負責驗證資源所有者并頒發訪問令牌(Access Token)的服務器。它可以是獨立的服務或與身份提供者(如Google、Facebook等)結合在一起。
-
資源服務器(Resource Server):資源服務器是存儲和提供受保護資源的服務器,它通過驗證訪問令牌來控制資源的訪問權限。
3.5.5?OAuth2.0 優點
OAuth 2.0具備如下優點:
-
簡化用戶身份驗證:資源所有者不需要直接提供密碼給第三方應用程序,提高了安全性。
-
提供可控制的授權:資源所有者可以選擇授權給特定的客戶端,并定義每個客戶端的授權范圍。
-
適用于多種應用場景:OAuth 2.0可以適用于Web、移動應用程序和后端服務等多種應用場景。
-
支持可擴展性:OAuth 2.0是一種開放標準,可以根據具體需求進行定制和擴展。
-
需要注意的是,正確實施OAuth 2.0需要謹慎處理訪問令牌的生命周期和安全性,并且在開發過程中遵循最佳實踐,以確保應用程序的安全性和用戶的隱私保護。
3.6 數字簽名
數字簽名算法是一種用于驗證和保護數據完整性的加密技術。它通過將數據進行哈希處理,然后使用私鑰對哈希值進行加密生成數字簽名。接收方可以使用發送方的公鑰對數字簽名進行解密驗證,確保數據的完整性和來源的真實性。
3.6.1 場景分析一
如下是一個數據傳輸的場景,A與B通信,A向B發送消息,過程如下:
-
B向A公布自己的公鑰;
-
A使用B的公鑰對需要發送的數據進行加密;
-
B收到A發送的加密數據,使用自己的私鑰進行解密;
在這個A–>B發送消息的過程中,即使A的消息以及B的公鑰都被截獲,仍舊無法獲取A的真實內容,因為私鑰是無法被獲取到的。
在此場景下,保證了消息的安全性,但有一個前提就是,B想接收的就是A的消息。如果此時有一名黑客C,使用B公布的公鑰偽造假消息發送給B,或者截獲A的消息,然后篡改消息,B是無法鑒別的,因此,數字簽名就有必要了。
數字簽概念
使用私鑰進行加簽, 使用公鑰進行驗簽,為了防止消息被篡改,通常采用具有不可逆與高抗碰撞性的算法(常用的如rsa-sha256)對信息hash運算生成一個hash字符串,因為該hash函數具有不可逆性,所以由結果不能反推出原始信息,同時,由于使用的hash函數具有高抗碰撞性,所以不同的信息經過hash之后得到的結果基本上不會相同,所以可以通過此特性來驗證信息是否被篡改過。
3.6.2 場景分析二
如下圖,A向B發送消息,黑客C也向B發送假消息,因為B只想要A的消息,所以過程如下:
-
A公布自己的公鑰,B獲取A的公鑰;
-
A使用自己的私鑰對消息進行加簽(同時,該消息內容也可以使用數據加密),然后將簽名以及消息同時發送給B;
-
B使用A的公鑰對簽名進行解簽,解析處理來的內容與實際的消息內容一樣,可以證明該消息確實是A發送的;
在此過程中,如果C向B發送偽造的消息,B使用A的公鑰對偽造消息的簽名進行驗簽,一定是不通過的。
綜上,數據加密和數字簽名各司其責,一個保證數據安全,一個保證數據正確,所以在消息傳遞過程中,為了保證安全性,可以考慮使用兩者。
3.6.3 數字簽名流程
數字簽名的實現有多種方案,以rsa作為數字簽名算法來說,其實現的流程步驟如下:
-
生成密鑰對:發送方生成一對密鑰,包括私鑰和公鑰。私鑰用于簽名數據,公鑰用于驗證簽名。
-
數據哈希:發送方對要傳輸的數據進行哈希處理,生成固定長度的哈希值。
-
簽名生成:發送方使用私鑰對哈希值進行加密,生成數字簽名。
-
簽名傳輸:發送方將數字簽名和原始數據一起傳輸給接收方。
-
簽名驗證:接收方使用發送方的公鑰對數字簽名進行解密,得到哈希值。
-
數據驗證:接收方對接收到的數據再次進行哈希處理,與解密得到的哈希值進行比對,來驗證數據的完整性和真實性。
常見的數字簽名算法包括RSA、DSA、ECDSA等,它們各自有不同的特點和用途。在實際應用中,數字簽名算法被廣泛用于保護數據的安全和完整性,如數字證書、電子郵件加密、電子商務等領域。
四、常用加密算法使用
接下來結合實際案例對上面提到的幾種常用的加密算法進行詳細的說明。
4.1 MD5加密算法使用
在java中,使用md5進行加密有多種方式,下面介紹幾種常用的方式
方式一:Java自身包實現
public static String md51(String str) {byte[] secretBytes = null;try {secretBytes = MessageDigest.getInstance("md5").digest(str.getBytes());} catch (Exception e) {throw new RuntimeException("沒有這個md5算法!");}String md5code = new BigInteger(1, secretBytes).toString(16);for (int i = 0; i < 32 - md5code.length(); i++) {md5code = "0" + md5code;}return md5code;}
方法式:使用apache加密包commons-codec
需要在pom中添加如下依賴
<dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId><version>1.11</version></dependency>
完整代碼
/*** md5加密二** @param plainText* @return*/public static String md52(String plainText) {try {// md5加密方法使用規則return DigestUtils.md5Hex(plainText.getBytes("UTF-8"));} catch (UnsupportedEncodingException e) {e.printStackTrace();return null;}}
方式三:使用hutool包提供的MD5加密
依賴包
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.26</version></dependency>
加密代碼
/*** md5加密三** @param str* @return*/public static String md53(String str) {return SecureUtil.md5(str);}
測試一下,可以看到對同樣待加密的字符串得到加密后的結果是一樣的
4.2 AES加密算法使用
AES算法采用不同的密鑰長度,包括128位、192位和256位。這些密鑰長度的選擇可以根據需要和安全性要求進行調整。
以AES 256來說,AES 256表示使用256位的密鑰長度,這是目前最安全的AES密鑰長度。AES 256提供了更高的安全性和更強的加密能力,適用于對敏感數據進行保護。
加密過程中,原始數據通過AES算法和密鑰進行加密,生成密文。解密過程中,密文通過相同的AES算法和密鑰進行解密,恢復為原始數據。
AES 256密鑰的加密/解密可以在Java中通過javax.crypto包中的Cipher類來實現。以下是一段使用AES加解密的完整代碼。
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;/*** AES 是一種可逆加密算法,對用戶的敏感信息加密處理* 對原始數據進行AES加密后,在進行Base64編碼轉化;*/
public class AESOperator {/** 加密用的Key 可以用26個字母和數字組成* 此處使用AES-128-CBC加密模式,key需要為16位。*/private String sKey = "0123456789abcdef";private String ivParameter = "0123456789abcdef";private static AESOperator instance = null;private AESOperator() {}public static AESOperator getInstance() {if (instance == null)instance = new AESOperator();return instance;}// 加密public String encrypt(String sSrc) throws Exception {Cipher cipher = Cipher.getInstance("AES / CBC / PKCS5Padding");byte[] raw = sKey.getBytes();SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");//使用CBC模式,需要一個向量iv,可增加加密算法的強度IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes());cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);byte[] encrypted = cipher.doFinal(sSrc.getBytes("utf-8"));//此處使用BASE64做轉碼。return new BASE64Encoder().encode(encrypted);}// 解密public String decrypt(String sSrc) throws Exception {try {byte[] raw = sKey.getBytes("ASCII");SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes());cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);//先用base64解密byte[] encrypted1 = new BASE64Decoder().decodeBuffer(sSrc);byte[] original = cipher.doFinal(encrypted1);String originalString = new String(original, "utf-8");return originalString;} catch (Exception ex) {return null;}}public static void main(String[] args) throws Exception {// 需要加密的字串String originPwd = "Aes@321";System.out.println("原始密碼串:" + originPwd);// 加密long lStart = System.currentTimeMillis();String enString = AESOperator.getInstance().encrypt(originPwd);System.out.println("加密后的字串是:" + enString);long lUseTime = System.currentTimeMillis() - lStart;System.out.println("加密耗時:" + lUseTime + "毫秒");// 解密lStart = System.currentTimeMillis();String DeString = AESOperator.getInstance().decrypt(enString);System.out.println("解密后的字串是:" + DeString);lUseTime = System.currentTimeMillis() - lStart;System.out.println("解密耗時:" + lUseTime + "毫秒");}
}
運行結果
4.3 RSA加密算法使用
在上文中,我們詳細介紹了RSA的加解密流程,利用RSA的加解密算法,可以在項目的認證登錄中使用,具體的實現思路主要包括下面幾點:
-
提供一個生成公鑰的接口給前端使用,前端首先調用該接口拿到公鑰;
-
在上一步中服務端保存公鑰與私鑰(可以存儲在redis中);
-
前端使用公鑰何相同的RSA算法,對登錄賬戶的密碼進行加密,得到加密串;
-
服務器接收前端的賬戶何密碼,使用私鑰對密碼進行解密;
下面的代碼完整描述了上面的過程
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.codec.binary.Base64;import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;/*** RSA工具類 默認長度為2048位*/
@Slf4j
public class RSAUtil {private static final int DEFAULT_RSA_KEY_SIZE = 2048;private static final String KEY_ALGORITHM = "RSA";private static final String SIGNATURE_ALGORITHM = "MD5withRSA";public static void main(String [] args) throws Exception{Map<String,String> result = generateRsaKey(DEFAULT_RSA_KEY_SIZE);String publicKey = result.get("publicKey");System.out.println("本次的公鑰為:"+publicKey);String privateKey = result.get("privateKey");System.out.println("本次的私鑰為:"+publicKey);String pwd = "Aes@111";System.out.println("原始密碼為:"+pwd);//1、獲取公鑰//2、公鑰加密,字符串加密String encryptPwd = encrypt(pwd, publicKey);System.out.println("加密后的密碼為:" + encryptPwd);//3、私鑰解密String decryptPwd = decrypt(encryptPwd, privateKey);System.out.println("解密后的密碼為:" + decryptPwd);}/*** 生成RSA 公私鑰,可選長度為1025,2048位.*/public static Map<String,String> generateRsaKey(int keySize) {Map<String,String> result = new HashMap<>(2);try {KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);// 初始化密鑰對生成器,密鑰大小為1024 2048位keyPairGen.initialize(keySize, new SecureRandom());// 生成一個密鑰對,保存在keyPair中KeyPair keyPair = keyPairGen.generateKeyPair();// 得到公鑰字符串result.put("publicKey", new String(Base64.encodeBase64(keyPair.getPublic().getEncoded())));// 得到私鑰字符串result.put("privateKey", new String(Base64.encodeBase64(keyPair.getPrivate().getEncoded())));} catch (GeneralSecurityException e) {e.printStackTrace();}return result;}/*** RSA私鑰解密* @param str 解密字符串* @param privateKey 私鑰* @return 明文*/public static String decrypt(String str, String privateKey) {//64位解碼加密后的字符串byte[] inputByte;String outStr = "";try {inputByte = Base64.decodeBase64(str.getBytes("UTF-8"));//base64編碼的私鑰byte[] decoded = Base64.decodeBase64(privateKey);RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));//RSA解密Cipher cipher = Cipher.getInstance("RSA");cipher.init(Cipher.DECRYPT_MODE, priKey);outStr = new String(cipher.doFinal(inputByte));} catch (UnsupportedEncodingException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | InvalidKeySpecException | NoSuchAlgorithmException e) {e.printStackTrace();}return outStr;}/*** RSA公鑰加密* @param str 需要加密的字符串* @param publicKey 公鑰* @return 密文* @throws Exception 加密過程中的異常信息*/public static String encrypt(String str, String publicKey) throws Exception {//base64編碼的公鑰byte[] decoded = Base64.decodeBase64(publicKey);RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));//RSA加密Cipher cipher = Cipher.getInstance("RSA");cipher.init(Cipher.ENCRYPT_MODE, pubKey);String outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes("UTF-8")));return outStr;}}
運行上面的代碼,可以看到預期的效果
4.4 JWT加密算法使用
4.4.1 JWT構成
JWT(JSON Web Tokens)是一種基于JSON格式的令牌,用于在客戶端和服務器之間傳遞安全信息。它由三部分組成:頭部(Header)、載荷(Payload)和簽名(Signature)。
JWT的結構形式如下:xxxxx.yyyyy.zzzzz
頭部(Header)
頭部通常由兩個部分組成:令牌類型(通常是JWT)和采用的加密算法(如HMAC-SHA256或RSA)。頭部使用Base64 URL安全編碼進行編碼。
載荷(Payload)
載荷是JWT的主要內容,包含了被稱為聲明(Claims)的信息。聲明分為三種類型:注冊聲明(Registered Claims)、公共聲明(Public Claims)和私有聲明(Private Claims)。常見的注冊聲明包括發行人(Issuer)、主題(Subject)、受眾(Audience)、過期時間(Expiration Time)等。載荷同樣使用Base64 URL安全編碼進行編碼。
簽名(Signature)
簽名是使用密鑰對頭部和載荷進行加密后的字符串,用于驗證JWT的完整性和真實性。簽名可以防止信息被篡改。簽名使用頭部中指定的加密算法進行計算。
JWT的工作流程通常如下:
-
客戶端使用身份驗證信息向服務器發送請求;
-
服務器驗證身份驗證信息,并生成一個JWT,并用密鑰對JWT進行簽名;
-
服務器將JWT作為響應的一部分發送給客戶端;
-
客戶端將JWT保存,以便在后續的請求中發送給服務器進行身份驗證;
-
服務器接收到帶有JWT的請求,驗證JWT的簽名,并讀取其中的信息來確認用戶身份和權限;
4.4.2 操作演示
引入jwt依賴,版本可以根據自己的情況選擇
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.10.3</version></dependency>
生成jwt令牌(token)
public static void main(String[] args) {createToken();}public static void createToken() {Map<String, Object> map = new HashMap<>();Calendar instance = Calendar.getInstance();//默認3000S過期instance.add(Calendar.SECOND,3000);String token = JWT.create().withHeader(map) //header,可以不寫.withClaim("userId", 21) //payload.withClaim("username", "mike") //payload.withClaim("mobile", "13325532123") //payload.withExpiresAt(instance.getTime()) //設置過期時間.sign(Algorithm.HMAC256("!ISN!@#¥%")); //簽名System.out.println(token);}
運行這段代碼,控制臺可以看到生成了一長串加密后的字符串,細心的同學不難發現,這串字符串正是按照上面的xxxxx.yyyyy.zzzzz格式組裝的,即對應著一個JWT token的三部分信息;
解析jwt令牌(token)
public static void parseToken(String token) {JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("!ISN!@#¥%")).build();DecodedJWT verify = jwtVerifier.verify(token);String userId = verify.getClaim("userId").asString();String userName = verify.getClaim("username").asString();String mobile = verify.getClaim("mobile").asString();System.out.println("userId:"+userId);System.out.println("userName:"+userName);System.out.println("mobile:"+mobile);}
運行上面的代碼,可以看到經過解密,可以得到加密之前的相關字段信息
4.5 數字簽名使用
在上文詳細介紹了使用數字簽名的過程和場景,下面通過示例來看下如何使用數字簽名,以tsa算法為例,本次案例用于簽名驗證,使用私鑰用于數字簽名,公鑰用于驗證簽名的有效性。
下面是一段完整的代碼,代碼的核心邏輯為:
-
生成公鑰和私鑰;
-
利用私鑰對待傳輸數據進行加簽;
-
利用公鑰以及傳入參數對數據進行驗簽;
import com.congge.entity.RsaEntity;
import org.apache.tomcat.util.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;public class PluRsaUtil {private static final Logger logger = LoggerFactory.getLogger(RSAUtil.class);public static void main(String[] args) {RsaEntity entity = new RsaEntity();// 使用RSA算法生成 公鑰與私鑰, 生成的公私鑰 是一一對應的。createRSAKey(entity);String body = "123456";String body2 = "12345";// 將入參數據以及私鑰進行數字加簽String sign = sign(body, entity.getPrivateKey());// 根據入參數據以及公鑰進行驗證簽名,若入參數據body被修改或者秘鑰不正確都會導致驗簽失敗;例如加簽使用body,驗簽使用body2則導致驗簽失敗boolean verifyFlag = verify(body2,entity.getPublicKey(), sign);if (verifyFlag) {logger.info("驗簽成功");} else {logger.info("驗簽失敗");}}/*** 生成對應的 與我通信的公鑰和私鑰* @return*/public static void createRSAKey(RsaEntity entity) {try {// 創建KeyPairGenerator 指定算法為RSA,用于生成對應的公鑰和私鑰KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");// 指定字節長度keyPairGenerator.initialize(1024);// 秘鑰生成器KeyPair keyPair = keyPairGenerator.generateKeyPair();// 公鑰RSAPublicKey publicKey = (RSAPublicKey)keyPair.getPublic();// 進行Base64編碼存入String clientPublicKey = Base64.encodeBase64String(publicKey.getEncoded());logger.info("生成的clientPublicKey是: {}", clientPublicKey);entity.setPublicKey(clientPublicKey);// 私鑰RSAPrivateKey privateKey = (RSAPrivateKey)keyPair.getPrivate();// 進行Base64編碼存入String clientPrivateKey = Base64.encodeBase64String(privateKey.getEncoded());logger.info("生成的clientPrivateKey是: {}", clientPrivateKey);entity.setPrivateKey(clientPrivateKey);} catch (Exception e) {logger.error("生成秘鑰失敗");e.printStackTrace();}}/*** 利用私鑰信息生成數字簽名* @param data 入參數據body* @param privateKey 私鑰* @return*/public static String sign(String data, String privateKey) {try {// 入參數據body字節數組byte[] dataBytes = data.getBytes();// 獲取私鑰秘鑰字節數組byte[] keyBytes = Base64.decodeBase64(privateKey);// 使用給定的編碼密鑰創建一個新的PKCS8EncodedKeySpec。// PKCS8EncodedKeySpec 是 PKCS#8標準作為密鑰規范管理的編碼格式PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);// 實例化KeyFactory,指定為加密算法 為 RSAKeyFactory keyFactory = KeyFactory.getInstance("RSA");// 獲得PrivateKey對象PrivateKey privateKey1 = keyFactory.generatePrivate(keySpec);// 用私鑰對信息生成數字簽名,指定簽名算法為 MD5withRSASignature signature = Signature.getInstance("MD5withRSA");// 初始化簽名signature.initSign(privateKey1);// 數據body帶入signature.update(dataBytes);// 對簽名進行Base64編碼return Base64.encodeBase64String(signature.sign());} catch (Exception e) {e.printStackTrace();}return null;}/*** 利用公鑰校驗數字簽名* @param data 入參數據body* @param publicKey 公鑰* @param sign 簽名* @return*/public static boolean verify(String data, String publicKey, String sign) {try {// 入參數據body字節數組byte[] dataBytes = data.getBytes("UTF-8");// 獲取公鑰秘鑰字節數組byte[] keyBytes = Base64.decodeBase64(publicKey);// 使用給定的編碼密鑰創建一個新的X509EncodedKeySpec// X509EncodedKeySpec是基于X.509證書提前的公鑰,一種java秘鑰規范X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(keyBytes);// 實例化KeyFactory,指定為加密算法 為 RSAKeyFactory keyFactory = KeyFactory.getInstance("RSA");// 獲取publicKey對象PublicKey publicKey1 = keyFactory.generatePublic(x509EncodedKeySpec);// 用私鑰對信息生成數字簽名,指定簽名算法為 MD5withRSASignature signature = Signature.getInstance("MD5withRSA");// 帶入公鑰進行驗證signature.initVerify(publicKey1);// 數據body帶入signature.update(dataBytes);// 驗證簽名return signature.verify(Base64.decodeBase64(sign));} catch (Exception e) {e.printStackTrace();return false;}}
}
五、寫在文末
數據安全是所有的互聯網產品在安全建設方面越來越重要的內容,而選擇合適的加密算法對保障數據傳入安全有著非常重要的意義,在實際業務中,需要結合實際情況進行衡量,并非越復雜的加密算法越好,綜合權衡之后選擇合適的加密算法,配合合理的數據傳輸方案設計,從而構筑起系統安全的穩固防線,本篇到此結束感謝觀看。