網絡編程-加密算法

目錄

一.網絡編程基礎

1. 概述

2. IP地址

?3. 域名

?4. 網絡模型

5. 常用協議

6. 小結

二.TCP編程

1. 什么是Socket?

2. 服務器端

3. 客戶端

4. Socket流

5. 小結

三.UDP編程

1. 概述

2. 服務器端

3. 客戶端

4. 小結

案例:

四.加密算法

1. 什么是編碼?

2. Base64編碼

3. 小結

五.哈希算法

1. 概述

2. 哈希碰撞

3. 常用哈希算法

4. 哈希算法的用途

4.1. 校驗下載文件

4.2. 存儲用戶密碼

5. SHA-1

6. 小結

例題:

六.Hmac算法

1. 概述

2. 小結

案例:

登錄加密,和二次登錄,獲取key驗證

七.對稱加密算法

1. 概述

2. 使用AES加密

2.1. ECB模式

2.2. CBC模式

3. 小結


一.網絡編程基礎

1. 概述

????????計算機網絡是指兩臺或更多的計算機組成的網絡,在同一個網絡中,任意兩臺計算機都可以直接通信,因為所有計算機都需要遵循同一種網絡協議。

????????那什么是互聯網呢?互聯網是網絡的網絡(internet),即把很多計算機網絡連接起來,形成一個全球統一的互聯網。對某個特定的計算機網絡來說,它可能使用網絡協議ABC,而另一個計算機網絡可能使用網絡協議XYZ。如果計算機網絡各自的通訊協議不統一,就沒法把不同的網絡連接起來形成互聯網。因此,為了把計算機網絡接入互聯網,就必須使用TCP/IP協議。

? ?TCP/IP協議泛指互聯網協議,其中最重要的兩個協議是TCP協議IP協議。只有使用TCP/IP協議的計算機才能夠聯入互聯網,使用其他網絡協議(例如NetBIOSAppleTalk協議等)是無法聯入互聯網的。

中國四大主流網絡體系ChinanetCERNET cstnetCHINAGBN

  1. Chinanet是郵電部門經營管理的基于Internet網絡技術的中國公用計算機互聯網,是國際計算機互聯網(Internet)的一部分,是中國的Internet骨干網。
  2. CERNET中國教育和科研計算機網CERNET是由國家投資建設,教育部負責管理,清華大學等高等學校承擔建設和管理運行的全國性學術計算機互聯網絡。
  3. cstnet 1994年中國科學技術網CSTNET首次實現和Internet直接連接,同時建立了中國最高域名服務器,標志著中國正式接入Internet
  4. ChinaGBN(China Golden Bridge Network)也稱做中國國家公用經濟信息通信網。它是中國國民經濟信息化的基礎設施,是建立金橋工程的業務網,支持金關、金稅、金卡等“金”字頭工程的應用。

2. IP地址

????????在互聯網中,一個IP地址用于唯一標識一個網絡接口(Network Interface)。一臺聯入互聯網的計算機肯定有一個IP地址,但也可能有多個IP地址(多個網卡)。

??IP地址分為IPv4IPv6兩種。IPv4采用32位地址,類似101.202.99.12,而IPv6采用128位地址,類似2001:0DA8:100A:0000:0000:1020:F2F3:1428IPv4地址總共有232個(大約42億),而IPv6地址則總共有2128個(大約340萬億億億億),IPv4的地址目前已耗盡,而IPv6的地址是根本用不完的。

????????IP地址又分為公網IP地址內網IP地址。公網IP地址可以直接被訪問,內網IP地址只能在內網訪問。內網IP地址類似于:

    • 192.168.x.x
    • 10.x.x.x

有一個特殊的IP地址,稱之為本機地址,它總是127.0.0.1

1707762444 = 0x65ca630c= 65  ca  63 0c= 101.202.99.12

每臺計算機都需要正確配置IP地址子網掩碼,根據這兩個就可以計算網絡號,如果兩臺計算機計算出的網絡號相同,說明兩臺計算機在同一個網絡,可以直接通信。如果兩臺計算機計算出的網絡號不同,那么兩臺計算機不在同一個網絡,不能直接通信,它們之間必須通過路由器或者交換機這樣的網絡設備間接通信,我們把這種設備稱為網關

網關的作用就是連接多個網絡,負責把來自一個網絡的數據包發到另一個網絡,這個過程叫路由。所以,一臺計算機的一個網卡會有3個關鍵配置:

    • IP地址,例如:10.0.2.15
    • 子網掩碼,例如:255.255.255.0
    • 網關的IP地址,例如:10.0.2.2

有一個特殊的本機域名localhost,它對應的IP地址總是本機地址127.0.0.1

Windows操作系統中,可以通過ipconfig命令查看本地主機的IP地址:

InetAddress localIPAddress = InetAddress.getLocalHost();
System.out.println("本地主機IP:" + localIPAddress.getHostAddress());
System.out.println("本地主機名稱:" + localIPAddress.getHostName());

?如果想檢查當前主機與目標主機之間的網絡是否通暢,可以使用ping命令來進行測試:

?在Java中,如果需要測試網絡是否通暢,可以使用Runtime對象exec()執行ping命令:

Process process = Runtime.getRuntime().exec("ping 192.168.254.162");BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = null;
while((line = reader.readLine()) != null) {System.out.println(line);
}

?3. 域名

因為直接記憶IP地址非常困難,所以我們通常使用域名訪問某個特定的服務。域名解析服務器DNS負責把域名翻譯成對應的IP,客戶端再根據IP地址訪問服務器。

nslookup可以查看域名對應的IP地址:

?4. 網絡模型

由于計算機網絡從底層的傳輸到高層的軟件設計十分復雜,要合理地設計計算機網絡模型,必須采用分層模型,每一層負責處理自己的操作。OSIOpen System Interconnect)網絡模型是ISO組織定義的一個計算機互聯的標準模型,注意它只是一個定義,目的是為了簡化網絡各層的操作,提供標準接口便于實現和維護。這個模型從上到下依次是:

  • 應用層,提供應用程序之間的通信;
  • 表示層:處理數據格式,加解密等等;
  • 會話層:負責建立和維護會話;
  • 傳輸層:負責提供端到端的可靠傳輸;
  • 網絡層:負責根據目標地址選擇路由來傳輸數據;
  • 數據鏈路層和物理層:負責把數據進行分片并且真正通過物理網絡傳輸,例如,無線網、光纖等。

互聯網實際使用的TCP/IP模型并不是對應到OSI7層模型,而是大致對應OSI5層模型

5. 常用協議

???IP協議是一個分組交換協議,它不保證可靠傳輸。而TCP協議傳輸控制協議,它是面向連接的協議,支持可靠傳輸雙向通信TCP協議是建立在IP協議之上的,簡單地說,IP協議只負責發數據包,不保證順序和正確性,而TCP協議負責控制數據包傳輸,它在傳輸數據之前需要先建立連接,建立連接后才能傳輸數據,傳輸完后還需要斷開連接。TCP協議之所以能保證數據的可靠傳輸,是通過接收確認超時重傳這些機制實現的。并且,TCP協議允許雙向通信,即通信雙方可以同時發送和接收數據。

????????TCP協議也是應用最廣泛的協議,許多高級協議都是建立在TCP協議之上的,例如HTTPSMTP等。

??UDP協議(User Datagram Protocol)是一種數據報文協議,它是無連接協議不保證可靠傳輸。因為UDP協議在通信前不需要建立連接,因此它的傳輸效率比TCP高,而且UDP協議比TCP協議要簡單得多。選擇UDP協議時,傳輸的數據通常是能容忍丟失的,例如,一些語音視頻通信的應用會選擇UDP協議。

6. 小結

計算機網絡:
軟件體系:
cs Client/server
bs Browser/server
網絡編程三要素
1.IP地址:
ip:IPV4:32位地址長度,每八位一組,分成四組 x-0~255
ip:IPV6:128位地址長度,每十六位一組,分成八組 x-0~65535
2001:0DA8::1020:F2F3:1428
2.端口號:應用程序在設備中的唯一標識,端口號由二個字節0~65535,0-1023端口號(被廣大服務商占用),1024以上可用
3.協議:
TCP/UDP:
TCP:面向連接的協議
UDP:無連接的協議

二.TCP編程

1. 什么是Socket?

????????在開發網絡應用程序的時候,會遇到Socket這個概念。Socket是一個抽象概念,一個應用程序通過一個Socket來建立一個遠程連接,而Socket內部通過TCP/IP協議把數據傳輸到網絡。

┌───────────┐ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ┌───────────┐
│Application│? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │Application│
├───────────┤ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ├───────────┤
│ ?Socket ? │? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ ?Socket ? │
├───────────┤ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ├───────────┤
│ ? ?TCP ? ?│? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?│ ? ?TCP ? ?│
├───────────┤ ? ? ?┌──────┐ ? ? ? ┌──────┐ ? ? ?├───────────┤
│ ? ?IP ? ? │<────>│Router│<─────>│Router│<────>│ ? ?IP ? ? │
└───────────┘ ? ? ?└──────┘ ? ? ? └──────┘ ? ? ?└───────────┘

SocketTCP和部分IP的功能都是由操作系統提供的,不同的編程語言只是提供了對操作系統調用的簡單的封裝。例如:Java提供的幾個Socket相關的類就封裝了操作系統提供的接口:ServerSocket類、Socket類。

為什么需要Socket進行網絡通信?因為僅僅通過IP地址進行通信是不夠的,同一臺計算機同一時間會運行多個網絡應用程序,例如瀏覽器、QQ、郵件客戶端等。當操作系統接收到一個數據包的時候,如果只有IP地址,它沒法判斷應該發給哪個應用程序,所以,操作系統抽象出Socket接口,每個應用程序需要各自對應到不同的Socket,數據包才能根據Socket正確地發到對應的應用程序。

一個Socket就是由IP地址和端口號(范圍是0~65535)組成,可以把Socket簡單理解為IP地址+端口號。端口號總是由操作系統分配,它是一個065535之間的數字,其中,小于1024的端口屬于特權端口,需要管理員權限,大于1024的端口可以由任意用戶的應用程序打開。

所以,如果需要與指定主機進行通信,完整的通信地址是由一個IP地址+端口號組成:

  • 101.202.99.2:1201
  • 101.202.99.2:1304
  • 101.202.99.2:15000

使用Socket進行網絡編程時,本質上就是兩個進程之間的網絡通信。其中一個進程必須充當服務器端,它會主動監聽某個指定的端口,另一個進程必須充當客戶端,它必須主動連接服務器的IP地址和指定端口,如果連接成功,服務器端和客戶端就成功地建立了一個TCP連接,雙方后續就可以隨時發送和接收數據。

因此,當Socket連接成功地在服務器端和客戶端之間建立后:

  • 對服務器端來說:它的Socket是指定的IP地址和指定的端口號;
  • 對客戶端來說:它的Socket是它所在計算機的IP地址和一個由操作系統分配的隨機端口號。

2. 服務器端

public class Server {public static void main(String[] args) throws IOException {ServerSocket ss = new ServerSocket(6666); // 監聽指定端口System.out.println("server is running...");while (true) {Socket sock = ss.accept();// 使用Socket流進行網絡通信// ...System.out.println("connected from " + sock.getRemoteSocketAddress());}}
}

服務器端通過下述代碼,在指定端口6666監聽。這里我們沒有指定IP地址,表示在計算機的所有網絡接口上進行監聽。

ServerSocket ss = new ServerSocket(6666);

如果ServerSocket監聽成功,我們就使用一個無限循環來處理客戶端的連接,注意到代碼ss.accept()表示每當有新的客戶端連接進來后,就返回一個Socket實例,這個Socket實例就是用來和剛連接的客戶端進行通信的。

  while (true) {Socket sock = ss.accept();System.out.println("connected from " + sock.getRemoteSocketAddress());}

如果沒有客戶端連接進來,accept()方法會阻塞并一直等待。如果有多個客戶端同時連接進來,ServerSocket會把連接扔到隊列里,然后一個一個處理。對于Java程序而言,只需要通過循環不斷調用accept()就可以獲取新的連接。

3. 客戶端

相比服務器端,客戶端程序就要簡單很多。一個典型的客戶端程序如下:

public class Client {public static void main(String[] args) throws IOException {// 連接指定服務器和端口Socket sock = new Socket("localhost", 6666); // 使用Socket流進行網絡通信// ...// 關閉sock.close();System.out.println("disconnected.");}
}

?客戶端程序通過下述代碼,連接到服務器端,注意上述代碼的服務器地址是"localhost",表示本機地址,端口號是6666。如果連接成功,將返回一個Socket實例,用于后續通信。

Socket sock = new Socket("localhost", 6666);

4. Socket流

Socket連接創建成功后,無論是服務器端,還是客戶端,我們都使用Socket實例進行網絡通信。因為TCP是一種基于流的協議,因此,Java標準庫使用InputStreamOutputStream來封裝Socket的數據流,這樣我們使用Socket的流,和普通IO流類似:

// 用于讀取網絡數據:
InputStream in = sock.getInputStream();// 用于寫入網絡數據:
OutputStream out = sock.getOutputStream();

寫入網絡數據時,必須要調用flush()方法。如果不調用flush(),我們很可能會發現,客戶端和服務器都收不到數據,這并不是Java標準庫的設計問題,而是我們以流的形式寫入數據的時候,并不是一寫入就立刻發送到網絡,而是先寫入內存緩沖區,直到緩沖區滿了以后,才會一次性真正發送到網絡,這樣設計的目的是為了提高傳輸效率。如果緩沖區的數據很少,而我們又想強制把這些數據發送到網絡,就必須調用flush()強制把緩沖區數據發送出去。

5. 小結

使用Java進行TCP編程時,需要使用Socket模型:

    • 服務器端用ServerSocket監聽指定端口;
    • 客戶端使用Socket(InetAddress, port)連接服務器;
    • 服務器端用accept()接收連接并返回Socket實例;
    • 雙方通過Socket打開InputStream/OutputStream讀寫數據;
    • 服務器端通常使用多線程同時處理多個客戶端連接,利用線程池可大幅提升效率;
    • flush()方法用于強制輸出緩沖區到網絡。

三.UDP編程

1. 概述

????????和TCP編程相比,UDP編程就簡單得多,因為UDP沒有創建連接,數據包也是一次收發一個,所以沒有流的概念。

Java中使用UDP編程,仍然需要使用Socket,因為應用程序在使用UDP時必須指定網絡接口(IP地址)和端口號。注意:UDP端口和TCP端口雖然都使用0~65535,但他們是兩套獨立的端口,即一個應用程序用TCP協議占用了端口1234,不影響另一個應用程序用UDP協議占用端口1234

2. 服務器端

????????在服務器端,使用UDP也需要監聽指定的端口。Java提供了DatagramSocket來實現這個功能,代碼如下:

DatagramSocket ds = new DatagramSocket(6666); // 監聽指定端口
while (true) { // 無限循環// 數據緩沖區:byte[] buffer = new byte[1024];DatagramPacket packet = new DatagramPacket(buffer, buffer.length);ds.receive(packet); // 收取一個UDP數據包// 收取到的數據存儲在buffer中,由packet.getOffset(), packet.getLength()指定起始位置和長度// 將其按UTF-8編碼轉換為String:String s = new String(packet.getData(), packet.getOffset(), packet.getLength(), StandardCharsets.UTF_8);// 發送數據:byte[] data = "ACK".getBytes(StandardCharsets.UTF_8);packet.setData(data);ds.send(packet);
}

服務器端首先使用如下語句在指定的端口監聽UDP數據包:

DatagramSocket ds = new DatagramSocket(6666);

?如果沒有其他應用程序占據這個端口,那么代表監聽成功。為了能夠反復處理數據,我們使用一個死循環來處理收到的UDP數據包:

while (true) { // 死循環}

要接收一個UDP數據包,需要準備一個byte[]緩沖區,并通過DatagramPacket實現接收:

byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
ds.receive(packet);

假設我們收取到的是一個String,那么,通過DatagramPacket返回的packet.getOffset()packet.getLength()確定數據在緩沖區的起止位置:

String s = new String(packet.getData(), packet.getOffset(), packet.getLength(), StandardCharsets.UTF_8);

當服務器收到一個DatagramPacket后,通常必須立刻回復一個或多個UDP包,因為客戶端地址在DatagramPacket中,每次收到的DatagramPacket可能是不同的客戶端,如果不回復,客戶端就收不到任何UDP包。

發送UDP包也是通過DatagramPacket實現的:

byte[] data = ...
packet.setData(data);
ds.send(packet);

3. 客戶端

和服務器端相比,客戶端使用UDP時,只需要直接向服務器端發送UDP包,然后接收返回的UDP包:

DatagramSocket ds = new DatagramSocket();
ds.setSoTimeout(1000);
ds.connect(InetAddress.getByName("localhost"), 6666); // 連接指定服務器和端口// 發送:
byte[] data = "Hello".getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length);
ds.send(packet);// 接收:
byte[] buffer = new byte[1024];
packet = new DatagramPacket(buffer, buffer.length);
ds.receive(packet);
String resp = new String(packet.getData(), packet.getOffset(), packet.getLength());
ds.disconnect();

客戶端打開一個DatagramSocket使用以下代碼:

DatagramSocket ds = new DatagramSocket();
ds.setSoTimeout(1000);
ds.connect(InetAddress.getByName("localhost"), 6666);

4. 小結

  • 使用UDP協議通信時,服務器和客戶端雙方無需建立連接;
  • 服務器端用DatagramSocket(port)監聽端口;
  • 客戶端使用DatagramSocket.connect()指定遠程地址和端口;
  • 雙方通過receive()send()讀寫數據;
  • DatagramSocket沒有IO流接口,數據被直接寫入byte[]緩沖區;

案例:

從磁盤拿出一張照片,客戶端發出去,服務端接收,并保存。返回給客戶端一個上傳成功。(判斷下大小2mb,并且必須.png結尾)

Server

package homework;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import java.util.UUID;public class Server {public static void main(String[] args) {try (ServerSocket ss = new ServerSocket(9999)) {System.out.println("服務器已啟動,等待客戶端連接...");while (true) {Socket socket = ss.accept();String uuid = UUID.randomUUID().toString().substring(0, 4);try (OutputStream os = new FileOutputStream(uuid + ".png");InputStream is = socket.getInputStream();OutputStream sos = socket.getOutputStream()) {
//                    System.out.println( uuid + ".png");byte[] buffer = new byte[1024];int len;while ((len = is.read(buffer)) != -1) {System.out.println("Arrays.toString(buffer)");os.write(buffer, 0, len);}
//                    os.flush(); // 確保數據寫入sos.write("上傳成功".getBytes());
//                    System.out.println("文件接收完成:" + uuid + ".png");}socket.close();}} catch (Exception e) {e.printStackTrace();}}
}

Client

package homework;import java.io.*;
import java.net.Socket;public class client1 {public static void main(String[] args) throws IOException {String path = "D:\\30f.jpg";InputStream in = new FileInputStream(path);if (path.endsWith(".png") && in.available() < 1024 * 1024 * 2) {try (Socket s = new Socket("127.0.0.1", 9999);OutputStream os = s.getOutputStream();) {System.out.println("已連接到服務器");// 不能大于2mbbyte[] b = new byte[1024];int len;while ((len = in.read(b)) != -1) {os.write(b, 0, len);}s.shutdownOutput();
//            s.shutdownOutput();InputStream is = s.getInputStream();byte[] bytes = new byte[1024];int len1;len1 = is.read(bytes);System.out.println(new String(bytes, 0, len1));} catch (Exception e) {e.printStackTrace();}} else {System.out.println("文件格式錯誤或文件大小要小于2M");}}
}

四.加密算法

1. 什么是編碼?

ASCII碼就是一種編碼,字母A的編碼是十六進制的0x41,字母B0x42,以此類推:

字母
A
B
C
D
…

?????????因為ASCII編碼最多只能有127個字符,要想對更多的文字進行編碼,就需要用占用2個字節的Unicode或者3個字節的UTF-8。例如:中文的"中"字使用Unicode編碼就是0x4e2dUTF-8編碼是0xe4b8ad

?

漢字

Unicode編碼

UTF-8編碼

0x4e2d

0xe4b8ad

0x6587

0xe69687

0x7f16

0xe7bc96

0x7801

0xe7a081

2. Base64編碼

URL編碼是對字符進行編碼,表示成%xx的形式,而Base64編碼是對二進制數據進行編碼,表示成文本格式。

Base64編碼可以把任意長度的二進制數據變為純文本,并且純文本內容中且只包含指定字符內容:A~Za~z0~9+/=。它的原理是把3字節的二進制數據按6bit一組,用4個整數表示,然后查表,把整數用索引對應到字符,得到編碼后的字符串。

6位整數的范圍總是0~63,所以,能用64個字符表示:字符A~Z對應索引0~25,字符a~z對應索引26~51,字符0~9對應索引52~61,最后兩個索引6263分別用字符+/表示。

舉個例子3byte數據分別是e4b8ad,按6bit分組得到十六進制390b222d,分別對應十進制57113445,通過索引計算結果為5Lit4

┌───────────────┬───────────────┬───────────────┐
│      e4       │      b8       │      ad       │
└───────────────┴───────────────┴───────────────┘
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│1│1│1│0│0│1│0│0│1│0│1│1│1│0│0│0│1│0│1│0│1│1│0│1│二進制
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
┌───────────┬───────────┬───────────┬───────────┐
│    39     │    0b     │    22     │    2d     │十六進制
└───────────┴───────────┴───────────┴───────────┘
┌───────────┬───────────┬───────────┬───────────┐
│    57     │    11     │    34     │    45     │十進制
└───────────┴───────────┴───────────┴───────────┘
┌───────────┬───────────┬───────────┬───────────┐
│    5      │    L      │    i      │    t      │十進制
└───────────┴───────────┴───────────┴───────────┘

Java中,二進制數據就是byte[]數組。Java標準庫提供了Base64來對byte[]數組進行編解碼:

public class Main {public static void main(String[] args) {byte[] input = new byte[] { (byte) 0xe4, (byte) 0xb8, (byte) 0xad };String b64encoded = Base64.getEncoder().encodeToString(input);System.out.println(b64encoded);}
}

?編碼后得到字符串結果:5Lit。要對這個字符使用Base64解碼,仍然用Base64這個類:

public class Main {public static void main(String[] args) {byte[] output = Base64.getDecoder().decode("5Lit");System.out.println(Arrays.toString(output)); // [-28, -72, -83]}
}

因為標準的Base64編碼會出現+/=,所以不適合把Base64編碼后的字符串放到URL中。一種針對URLBase64編碼可以在URL中使用的Base64編碼,它僅僅是把+變成-/變成_

public class Main {public static void main(String[] args) {// 原始字節內容byte[] input = new byte[] { 0x01, 0x02, 0x7f, 0x00 };// 分別使用兩種方式進行編碼String b64Encode = Base64.getEncoder().encodeToString(input);String b64UrlEncoded = Base64.getUrlEncoder().encodeToString(input);// 替換“+、/和=”System.out.println(b64Encode); System.out.println(b64UrlEncoded);// 分別使用兩種方式進行重新解碼byte[] output1 = Base64.getDecoder().decode(b64Encode);byte[] output2 = Base64.getUrlDecoder().decode(b64UrlEncoded);// 結果完全一致System.out.println(Arrays.toString(output1));System.out.println(Arrays.toString(output2));}
}

Base64編碼的目的是把二進制數據變成文本格式,這樣在很多文本中就可以處理二進制數據。例如,電子郵件協議就是文本協議,如果要在電子郵件中添加一個二進制文件,就可以用Base64編碼,然后以文本的形式傳送。

Base64編碼的缺點是傳輸效率會降低,因為它把原始數據的長度增加了1/3。和URL編碼一樣,Base64編碼是一種編碼算法,不是加密算法。

如果把Base6464個字符編碼表換成32個、48個或者58個,就可以使用Base32編碼,Base48編碼和Base58編碼。字符越少,編碼的效率就會越低。

3. 小結

URL編碼和Base64編碼都是編碼算法,它們不是加密算法;

URL編碼的目的是把任意文本數據編碼為%前綴表示的文本,便于瀏覽器和服務器處理;

Base64編碼的目的是把任意二進制數據編碼為文本,但編碼后數據量會增加1/3.

五.哈希算法

1. 概述

哈希算法(Hash)又稱摘要算法(Digest),它的作用是:對任意一組輸入數據進行計算,得到一個固定長度的輸出摘要。

哈希算法最重要的特點就是:

  • 相同的輸入一定得到相同的輸出;
  • 不同的輸入大概率得到不同的輸出。

所以,哈希算法的目的:為了驗證原始數據是否被篡改。

Java字符串的hashCode()就是一個哈希算法,它的輸入是任意字符串,輸出是固定的4字節int整數:

"hello".hashCode(); // 0x5e918d2
"hello, java".hashCode(); // 0x7a9d88e8
"hello, bob".hashCode(); // 0xa0dbae2f

兩個相同的字符串永遠會計算出相同的hashCode,否則基于hashCode定位的HashMap就無法正常工作。這也是為什么當我們自定義一個class時,覆寫equals()方法時我們必須正確覆寫hashCode()方法。


2. 哈希碰撞

哈希碰撞是指:兩個不同的輸入得到了相同的輸出。

例如:

"AaAaAa".hashCode(); // 0x7460e8c0
"BBAaBB".hashCode(); // 0x7460e8c0"通話".hashCode(); // 0x11ff03
"重地".hashCode(); // 0x11ff03

碰撞能不能避免?答案是不能。碰撞是一定會出現的,因為輸出的字節長度是固定的,StringhashCode()輸出是4字節整數,最多只有4294967296種輸出,但輸入的數據長度是不固定的,有無數種輸入。所以,哈希算法是把一個無限的輸入集合映射到一個有限的輸出集合,必然會產生碰撞。

碰撞不可怕,我們擔心的不是碰撞,而是碰撞的概率,因為碰撞概率的高低關系到哈希算法的安全性。一個安全的哈希算法必須滿足:

  • 碰撞概率低;
  • 不能猜測輸出。

不能猜測輸出是指:輸入的任意一個bit的變化會造成輸出完全不同,這樣就很難從輸出反推輸入(只能依靠暴力窮舉)。

假設一種哈希算法有如下規律:

hashA("java001") = "123456"
hashA("java002") = "123457"
hashA("java003") = "123458"

那么很容易從輸出123459反推輸入,這種哈希算法就不安全。安全的哈希算法從輸出是看不出任何規律的:

hashB("java001") = "123456"
hashB("java002") = "580271"
hashB("java003") = ???

3. 常用哈希算法

哈希算法,根據碰撞概率,哈希算法的輸出長度越長,就越難產生碰撞,也就越安全。

常用的哈希算法有:

算法

輸出長度(位)

輸出長度(字節)

MD5

128 bits

16 bytes

SHA-1

160 bits

20 bytes

RipeMD-160

160 bits

20 bytes

SHA-256

256 bits

32 bytes

SHA-512

512 bits

64 bytes

Java標準庫提供了常用的哈希算法,通過統一的接口進行調用。以MD5算法為例,看看如何對輸入內容計算哈希:

import java.security.MessageDigest;public class main {public static void main(String[] args)  {// 創建一個MessageDigest實例:MessageDigest md = MessageDigest.getInstance("MD5");// 反復調用update輸入數據:md.update("Hello".getBytes("UTF-8"));md.update("World".getBytes("UTF-8"));// 16 bytes: 68e109f0f40ca72a15e05cc22786f8e6byte[] results = md.digest(); StringBuilder sb = new StringBuilder();for(byte bite : results) {sb.append(String.format("%02x", bite));}System.out.println(sb.toString());}
}

使用MessageDigest時,我們首先根據哈希算法獲取一個MessageDigest實例,然后,反復調用update(byte[])輸入數據。當輸入結束后,調用digest()方法獲得byte[]數組表示的摘要,最后,把它轉換為十六進制的字符串。

運行上述代碼,可以得到輸入HelloWorldMD568e109f0f40ca72a15e05cc22786f8e6


4. 哈希算法的用途

4.1. 校驗下載文件

因為相同的輸入永遠會得到相同的輸出,因此,如果輸入被修改了,得到的輸出就會不同。我們在網站上下載軟件的時候,經常看到下載頁顯示的MD5哈希值:

如何判斷下載到本地的軟件是原始的、未經篡改的文件?我們只需要自己計算一下本地文件的哈希值,再與官網公開的哈希值對比,如果相同,說明文件下載正確,否則,說明文件已被篡改。

4.2. 存儲用戶密碼

哈希算法的另一個重要用途是存儲用戶口令。如果直接將用戶的原始口令存放到數據庫中,會產生極大的安全風險:

  • 數據庫管理員能夠看到用戶明文口令;
  • 數據庫數據一旦泄漏,黑客即可獲取用戶明文口令。

username

password

bob

123456789

alice

sdfsdfsdf

tim

justdoit

不存儲用戶的原始口令,那么如何對用戶進行認證?方法是存儲用戶口令的哈希,例如,MD5。在用戶輸入原始口令后,系統計算用戶輸入的原始口令的MD5并與數據庫存儲的MD5對比,如果一致,說明口令正確,否則,口令錯誤。

因此,數據庫存儲用戶名和口令的表內容應該像下面這樣:

username

password

bob

25f9e794323b453885f5181f1b624d0b

alice

73a90acaae2b1ccc0e969709665bc62f

tim

19f9f30bd097d4c066d758fb01b75032

這樣一來,數據庫管理員看不到用戶的原始口令。即使數據庫泄漏,黑客也無法拿到用戶的原始口令。想要拿到用戶的原始口令,必須用暴力窮舉的方法,一個口令一個口令地試,直到某個口令計算的MD5恰好等于指定值。

使用哈希口令時,還要注意防止彩虹表攻擊

什么是彩虹表呢?上面講到了,如果只拿到MD5,從MD5反推明文口令,只能使用暴力窮舉的方法。然而黑客并不笨,暴力窮舉會消耗大量的算力和時間。但是,如果有一個預先計算好的常用口令和它們的MD5的對照表,這個表就是彩虹表。如果用戶使用了常用口令,黑客從MD5一下就能反查到原始口令:

常用口令

MD5

hello123

f30aa7a662c728b7407c54ae6bfd27d1

12345678

25d55ad283aa400af464c76d713c07ad

passw0rd

bed128365216c019988915ed3add75fb

19700101

570da6d5277a646f6552b8832012f5dc

wbjxxmy

11d7a82f45f6a176fd9d5c100ccab40a

這就是為什么不要使用常用密碼,以及不要使用生日作為密碼的原因。

當然,我們也可以采取特殊措施來抵御彩虹表攻擊:對每個口令額外添加隨機數,這個方法稱之為加鹽salt):

digest = md5(salt + inputPassword)

經過加鹽處理的數據庫表,內容如下:

username

salt

password

bob

H1r0a

a5022319ff4c56955e22a74abcc2c210

alice

7$p2w

e5de688c99e961ed6e560b972dab8b6a

tim

z5Sk9

1eee304b92dc0d105904e7ab58fd2f64


5. SHA-1

SHA-1也是一種哈希算法,它的輸出是160 bits,即20字節。SHA-1是由美國國家安全局開發的,SHA算法實際上是一個系列,包括SHA-0(已廢棄)、SHA-1SHA-256SHA-512等。

Java中使用SHA-1,和MD5完全一樣,只需要把算法名稱改為"SHA-1":

import java.security.MessageDigest;public class main {public static void main(String[] args)  {// 創建一個MessageDigest實例:MessageDigest md = MessageDigest.getInstance("SHA-1");// 反復調用update輸入數據:md.update("Hello".getBytes("UTF-8"));md.update("World".getBytes("UTF-8"));// 20 bytes: db8ac1c259eb89d4a131b253bacfca5f319d54f2byte[] results = md.digest(); StringBuilder sb = new StringBuilder();for(byte bite : results) {sb.append(String.format("%02x", bite));}System.out.println(sb.toString());}
}

類似的,計算SHA-256,我們需要傳入名稱"SHA-256",計算SHA-512,我們需要傳入名稱"SHA-512"。Java標準庫支持的所有哈希算法可以在這里查到。


6. 小結

  • 哈希算法可用于驗證數據完整性,具有防篡改檢測的功能;
  • 常用的哈希算法有MD5SHA-1等;
  • 用哈希存儲口令時要考慮彩虹表攻擊。

例題:

從本地獲取一張圖片,對這個圖片進行md加密
package jiamisuanfa;import java.io.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.UUID;public class Demo06 {public static void main(String[] args) throws IOException, NoSuchAlgorithmException {//題目:// 從本地獲取一張圖片,對這個圖片進行md加密InputStream is = new FileInputStream(new File("D:\\30f.jpg"));MessageDigest md = MessageDigest.getInstance("MD5");byte[] message =  new byte[1024];String salt = UUID.randomUUID().toString().substring(0,6);while (is.read(message) != -1) {md.update(message);}byte[] bytes1 = md.digest();System.out.println("沒有加顏值的字符數組:"+ Arrays.toString(bytes1));System.out.println("沒有顏值加密后的字符串:"+ byteToHex(bytes1));md.update(salt.getBytes());byte[] bytes = md.digest();
//        System.out.println("未加密:"+ Arrays.toString(message));System.out.println("加了顏值后的字符數組:"+ Arrays.toString(bytes));System.out.println("有了顏值加密后的字符串:"+ byteToHex(bytes));}public  static String byteToHex(byte[] bytes){StringBuilder sb = new StringBuilder();for (byte b:bytes){sb.append(String.format("%02x",b));}return sb.toString();}
}

擴展:添加鹽值解決彩虹表攻擊

package jiamisuanfa;import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;public class Demo05 {// 此次的鹽值信息為:f2c01d// 加密后的結果為:[-60,35,-131,2,-124,1,1,1......]// 加密后的字符串為:c456888es6w6778ewr2// 加密后長度為:..// 添加鹽值解決彩虹表攻擊public static void main(String[] args) throws NoSuchAlgorithmException {// 1,創建MessageDigest實例MessageDigest md = MessageDigest.getInstance("MD5");// 2.使用md5進行信息加密byte[] message =  "我本將心像明月".getBytes();md.update(message);// 3,添加鹽值,隨機值// String uuid = UUID.randomUUID().toString().substring(0,6);// System.out.println("此次的鹽值信息為:"+uuid);// md.update(uuid.getBytes());md.update("f2c01d".getBytes());// 3.進行加密byte[] bytes = md.digest();System.out.println("加密后的字節數組:"+ Arrays.toString(bytes));System.out.println("加密后的字符串:"+ byteToHex(bytes));System.out.println("加密后的字符串長度:"+ byteToHex(bytes).length());}public  static String byteToHex(byte[] bytes){StringBuilder sb = new StringBuilder();for (byte b:bytes){sb.append(String.format("%02x",b));}return sb.toString();}
}
RipeMD160,SHA-1,MD5 創建,統一封裝成工具類:
package jiamisuanfa;import org.bouncycastle.jce.provider.BouncyCastleProvider;import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.util.Arrays;
import java.util.UUID;public class HashTools {// 創建信息摘要對象-成員變量private static MessageDigest md;private HashTools() {}// md5public static String md5(String message) throws NoSuchAlgorithmException {// 創建信息摘要對象md = MessageDigest.getInstance("MD5");return handle(message);}// sha-1public static String sha1(String message) throws NoSuchAlgorithmException {md = MessageDigest.getInstance("SHA-1");return handle(message);}public static String ripeMD168(String message) throws NoSuchAlgorithmException {// 安全注冊中心// 注冊BouncyCastle提供的通知類對象BouncyCastleProviderSecurity.addProvider(new BouncyCastleProvider());md = MessageDigest.getInstance("RipeMD160");return handle(message);}private static String handle(String message) {byte[] bytes = message.getBytes();md.update(bytes);// 添加顏值md.update(UUID.randomUUID().toString().substring(0,6).getBytes());// 加密操作byte[] bytes1 = md.digest();// 轉字符串return byteToHex(bytes1);}public static String byteToHex(byte[] bytes){StringBuilder sb = new StringBuilder();for (byte b:bytes){// 將每一個字節數字轉成2位的16進制的字符串數字sb.append(String.format("%02x",b));}return sb.toString();}public static byte[] stringTobytes(String message){byte[] bytes = new byte[message.length()/2];for (int i = 0; i < message.length(); i=i+2){String subString = message.substring(i,i+2);byte b = (byte) Integer.parseInt(subString,16);bytes[i/2] = b;}return bytes;}}

?

package jiamisuanfa;import java.security.NoSuchAlgorithmException;public class Demo07 {public static void main(String[] args) throws NoSuchAlgorithmException {String message = "我本將心趙明月";String md5 = HashTools.md5(message);System.out.println("md5加密后的字符串:"+md5);System.out.println(md5.length());// 16*2String sha1 = HashTools.sha1(message);System.out.println("sha1加密后的字符串:"+sha1);String ripeMD168 = HashTools.ripeMD168(message);System.out.println("ripeMD160加密后的字符串:"+ripeMD168);System.out.println(ripeMD168.length());// 20*2}
}

六.Hmac算法

1. 概述

在前面講到哈希算法時,我們說,存儲用戶的哈希口令時,要加鹽存儲,目的就在于抵御彩虹表攻擊。我們回顧一下哈希算法:digest = hash(input)

正是因為相同的輸入會產生相同的輸出,我們加鹽的目的就在于,使得輸入有所變化:

digest = hash(salt + input)

這個salt可以看作是一個額外的“認證碼”,同樣的輸入,不同的認證碼,會產生不同的輸出。因此,要驗證輸出的哈希,必須同時提供“認證碼”。

Hmac算法就是一種基于密鑰的消息認證碼算法,它的全稱是Hash-based Message Authentication Code,是一種更安全的消息摘要算法。

Hmac算法總是和某種哈希算法配合起來用的。例如,我們使用MD5算法,對應的就是Hmac MD5算法,它相當于“加鹽”的MD5HmacMD5 ≈ md5(secure_random_key, input)

因此,HmacMD5可以看作帶有一個安全的keyMD5。使用HmacMD5而不是用MD5salt,有如下好處:

  • HmacMD5使用的key長度是64字節,更安全;
  • Hmac是標準算法,同樣適用于SHA-1等其他哈希算法;
  • Hmac輸出和原有的哈希算法長度一致。

可見,Hmac本質上就是把key混入摘要的算法。驗證此哈希時,除了原始的輸入數據,還要提供key。為了保證安全,我們不會自己指定key,而是通過Java標準庫的KeyGenerator生成一個安全的隨機的key

下面是使用HmacMD5的參考代碼:

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;public class main {public static void main(String[] args) throws NoSuchAlgorithmException, IllegalStateException, UnsupportedEncodingException, InvalidKeyException {// 獲取HmacMD5秘鑰生成器KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5");// 產生秘鑰SecretKey secreKey = keyGen.generateKey();// 打印隨機生成的秘鑰:byte[] keyArray = secreKey.getEncoded();StringBuilder key = new StringBuilder();for(byte bite:keyArray) {key.append(String.format("%02x", bite));}System.out.println(key);// 使用HmacMD5加密Mac mac = Mac.getInstance("HmacMD5");mac.init(secreKey); // 初始化秘鑰mac.update("HelloWorld".getBytes("UTF-8"));byte[] resultArray = mac.doFinal();StringBuilder result = new StringBuilder();for(byte bite:resultArray) {result.append(String.format("%02x", bite));}System.out.println(result);}
}

MD5相比,使用HmacMD5的步驟是:

  1. 通過名稱HmacMD5獲取KeyGenerator實例;
  2. 通過KeyGenerator創建一個SecretKey實例;
  3. 通過名稱HmacMD5獲取Mac實例;
  4. SecretKey初始化Mac實例;
  5. Mac實例反復調用update(byte[])輸入數據;
  6. 調用Mac實例的doFinal()獲取最終的哈希值。

我們可以用Hmac算法取代原有的自定義的加鹽算法,因此,存儲用戶名和口令的數據庫結構如下:

?

username

secret_key (64 bytes)

password

bob

a8c06e05f92e...5e16

7e0387872a57c85ef6dddbaa12f376de

alice

e6a343693985...f4be

c1f929ac2552642b302e739bc0cdbaac

tim

f27a973dfdc0...6003

af57651c3a8a73303515804d4af43790

?有了Hmac計算的哈希和SecretKey,我們想要驗證怎么辦?這時,SecretKey不能從KeyGenerator生成,而是從一個byte[]數組恢復:

// 原始密碼
String password = "nhmyzgq";// 通過"秘鑰的字節數組",恢復秘鑰
byte[] keyByteArray = {126, 49, 110, 126, -79, -5, 66, 34, -122, 123, 107, -63, 106, 100, -28, 67, 19, 23, 1, 23, 47, 63, 47, 109, 123, -111, -27, -121, 103, -11, 106, -26, 110, -27, 107, 40, 19, -8, 57, 20, -46, -98, -82, 102, -104, 96, 87, -16, 93, -107, 25, -56, -113, 12, -49, 96, 6, -78, -31, -17, 100, 19, -61, -58};// 恢復秘鑰
SecretKey key = new SecretKeySpec(keyByteArray,"HmacMD5");// 加密
Mac mac = Mac.getInstance("HmacMD5");
mac.init(key);
mac.update(password.getBytes());
byte[] resultByteArray = mac.doFinal();StringBuilder resultStr = new StringBuilder();
for(byte b : resultByteArray) {resultStr.append(String.format("%02x", b));
}
System.out.println("加密結果:" + resultStr);

2. 小結

Hmac算法是一種標準的基于密鑰的哈希算法,可以配合MD5SHA-1等哈希算法,計算的摘要長度和原摘要算法長度相同。

案例:

登錄加密,和二次登錄,獲取key驗證

package jiamisuanfa;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class Demo08 {/*密鑰字節信息:[19, -3, 97, -104, -71, 103, -63, -72, -81, -62, 124, -104, -93, -19, -36, -53, -60, -88, -128, 23, 60, 99, -90, 74, -57, -81, 21, 56, -68, 110, 127, -95, -51, -21, -96, -85, -72, -23, -118, -49, -50, 122, -71, 52, -49, 94, 6, -89, 14, 46, -11, -107, 127, 115, 90, 54, -90, -98, -96, -60, 41, -83, -81, 6]
密鑰字符串:13fd6198b967c1b8afc27c98a3eddccbc4a880173c63a64ac7af1538bc6e7fa1cdeba0abb8e98acfce7ab934cf5e06a70e2ef5957f735a36a69ea0c429adaf06
密鑰字節信息長度:64
加密后的字節數組:[-24, 18, -16, 11, -83, -19, 35, 50, 4, 104, 109, 51, -91, -43, 16, -64]
加密后的字符串:e812f00baded233204686d33a5d510c0
加密后的字符串長度:16*/public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {
//        // 產生密鑰生成器對象
//        KeyGenerator generator = KeyGenerator.getInstance("HmacMD5");
//        // 通過密鑰生成器生成密鑰
//        SecretKey key = generator.generateKey();
//        // 獲取密鑰字節信息
//        byte[] keyBytes = key.getEncoded();
//        System.out.println("密鑰字節信息:"+ Arrays.toString(keyBytes));
//        System.out.println("密鑰字符串:"+HashTools.byteToHex(keyBytes));
//        System.out.println("密鑰字節信息長度:"+ keyBytes.length);
//        // 獲取加密對象
//        Mac mas = Mac.getInstance("HmacMD5");
//        // 初始化key值
//        mas.init(key);
//        // 提供需要進行加密的信息添加進來
//        mas.update("我本將心像明月".getBytes());
//        // 加密操作
//        byte[] bytes = mas.doFinal();
//        System.out.println("加密后的字節數組:"+ Arrays.toString(bytes));
//        System.out.println("加密后的字符串:"+ HashTools.byteToHex(bytes));
//        System.out.println("加密后的字符串長度:"+ bytes.length);// 登錄// 二次登錄// 密鑰字符串轉字節數組操作String str = "13fd6198b967c1b8afc27c98a3eddccbc4a880173c63a64ac7af1538bc6e7fa1cdeba0abb8e98acfce7ab934cf5e06a70e2ef5957f735a36a69ea0c429adaf06";byte[] keyBytes = HashTools.stringTobytes(str);System.out.println("密鑰字節信息:"+ Arrays.toString(keyBytes));// 2.還原密鑰keySecretKey key = new SecretKeySpec(keyBytes, "HmacMD5");// 再次加密Mac mas = Mac.getInstance("HmacMD5");// 初始化key值mas.init(key);// 提供需要進行加密的信息添加進來mas.update("我本將心像明月".getBytes());// 加密操作byte[] bytes = mas.doFinal();System.out.println("加密后的字節數組:"+ Arrays.toString(bytes));System.out.println("加密后的字符串:"+ HashTools.byteToHex(bytes));System.out.println("加密后的字符串長度:"+ bytes.length);}
}

七.對稱加密算法

1. 概述

對稱加密算法就是傳統的用一個秘鑰進行加密和解密。例如,我們常用的WinZIPWinRAR對壓縮包的加密和解密,就是使用對稱加密算法.

算法

密鑰長度

工作模式

填充模式

DES

56/64

ECB/CBC/PCBC/CTR/...

NoPadding/PKCS5Padding/...

AES

128/192/256

ECB/CBC/PCBC/CTR/...

NoPadding/PKCS5Padding/PKCS7Padding/...

IDEA

128

ECB

PKCS5Padding/PKCS7Padding/...

?

密鑰長度直接決定加密強度,而工作模式和填充模式可以看成是對稱加密算法的參數和格式選擇。Java標準庫提供的算法實現并不包括所有的工作模式和所有填充模式。

最后,值得注意的是,DES算法由于密鑰過短,可以在短時間內被暴力破解,所以現在已經不安全了。

2. 使用AES加密

AES算法是目前應用最廣泛的加密算法。比較常見的工作模式是ECBCBC

2.1. ECB模式

ECB模式是最簡單的AES加密模式,它需要一個固定長度的密鑰,固定的明文會生成固定的密文。

我們先用ECB模式加密并解密:

package jiamisuanfa;import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;public class Demo09 {public static void main(String[] args) throws IllegalBlockSizeException, NoSuchPaddingException, BadPaddingException, NoSuchAlgorithmException, InvalidKeyException {// 加密byte[] input = "我本將心像明月".getBytes();byte[] keys = "1234567890abcdef".getBytes();byte[] encodeBytes = encodeMessage(input, keys);System.out.println("加密后的字符串:"+ HashTools.byteToHex(encodeBytes));System.out.println("加密后的字符串長度:"+ encodeBytes.length);System.out.println("加密后的結果:"+ Arrays.toString(encodeBytes));// 解密byte[] decodeBytes = decodeMessage(encodeBytes, keys);System.out.println("解密后的字符串:"+ new String(decodeBytes));System.out.println("解密后的字符串長度:"+ decodeBytes.length);System.out.println("解密后的結果:"+ Arrays.toString(decodeBytes));}public static byte[] encodeMessage(byte[] input, byte[] keys) throws IllegalBlockSizeException, BadPaddingException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException {// 1.讀取對稱加密對象,設置加密算法,工作模式,填充模式Cipher cipher  = Cipher.getInstance("AES/ECB/PKCS5Padding");// 2.加密準備密鑰信息SecretKeySpec key = new SecretKeySpec(keys, "AES");// 3.初始化加密對象cipher.init(Cipher.ENCRYPT_MODE, key);// 4.執行加密操作byte[] bytes = cipher.doFinal(input);return bytes;}public static byte[] decodeMessage(byte[] input, byte[] keys) throws IllegalBlockSizeException, BadPaddingException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException {// 1.讀取對稱加密對象,設置加密算法,工作模式,填充模式Cipher cipher  = Cipher.getInstance("AES/ECB/PKCS5Padding");// 2.加密準備密鑰信息SecretKeySpec key = new SecretKeySpec(keys, "AES");// 3.初始化加密對象cipher.init(Cipher.DECRYPT_MODE, key);// 4.執行加密操作byte[] bytes = cipher.doFinal(input);return bytes;}
}

?

Java標準庫提供的對稱加密接口非常簡單,使用時按以下步驟編寫代碼:

  1. 根據算法名稱/工作模式/填充模式獲取Cipher實例;
  2. 根據算法名稱初始化一個SecretKey實例,密鑰必須是指定長度;
  3. 使用SerectKey初始化Cipher實例,并設置加密或解密模式;
  4. 傳入明文或密文,獲得密文或明文。
2.2. CBC模式

ECB模式是最簡單的AES加密模式,這種一對一的加密方式會導致安全性降低。所以,更好的方式是通過CBC模式,它需要一個隨機數作為IV參數,這樣對于同一份明文,每次生成的密文都不同:

package jiamisuanfa;import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;public class Demo11 {public static void main(String[] args) throws InvalidAlgorithmParameterException, IllegalBlockSizeException, NoSuchPaddingException, BadPaddingException, NoSuchAlgorithmException, InvalidKeyException {byte[] input = "我本將心像明月".getBytes();byte[] keys = "1234567890ebadfj".getBytes();byte[] encode = encodeMessage(input, keys);System.out.println("加密后的信息為:"+ Arrays.toString( encode));byte[] decode = decodeMessage(encode, keys);System.out.println("解密后的信息為:"+ new String(decode));}private  static  byte[] encodeMessage(byte[] message,byte[] key) throws IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException {// 1.獲取Cipher對象Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");// 2.還原keySecretKey keys = new SecretKeySpec(key, "AES");// 3.準備iv偏移SecureRandom sr = SecureRandom.getInstanceStrong();byte[] bytes = sr.generateSeed(16);IvParameterSpec iv = new IvParameterSpec(bytes);// 4.初始化操作,設置加密模式,設置key,設置偏移量cipher.init(Cipher.ENCRYPT_MODE, keys, iv);// 5.加密byte[] encode = cipher.doFinal(message);return addByte(encode, bytes);}private static byte[] addByte(byte[] encode, byte[] iv) {byte[] bytes = new byte[encode.length+iv.length];System.arraycopy(encode,0,bytes,0,encode.length);System.arraycopy(iv,0,bytes,encode.length,iv.length);return bytes;}private static byte[] decodeMessage(byte[] decodeMessage,byte[] key) throws InvalidAlgorithmParameterException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {// 1.獲取Cipher對象Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");// 2.還原keySecretKey keys = new SecretKeySpec(key, "AES");// 3.還原iv值byte[] bytes = Arrays.copyOfRange(decodeMessage, decodeMessage.length-16, decodeMessage.length);IvParameterSpec iv = new IvParameterSpec(bytes);// 4.初始化操作,設置加密模式,設置key,設置偏移量cipher.init(Cipher.DECRYPT_MODE, keys, iv);// 5.解密byte[] bytes1 = Arrays.copyOf(decodeMessage, decodeMessage.length-16);return bytes1;}}

3. 小結

  • 對稱加密算法使用同一個密鑰進行加密和解密,常用算法有DESAESIDEA等;
  • 密鑰長度由算法設計決定,AES的密鑰長度是128/192/256位;
  • 使用對稱加密算法需要指定算法名稱、工作模式和填充模式。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/91503.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/91503.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/91503.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【網絡工程師軟考版】網絡安全

任何形式的網絡服務都會導致安全方面的風險&#xff0c;問題是如何將風險降到最低程度&#xff0c;目前的網絡安全措施有數據加密、數字簽名、身份認證、防火墻、特征過濾等。所涉內容&#xff1a;1、網絡安全基礎2、加密技術與哈希算法3、數字簽名4、數字證書5、VPN技術6、防火…

深入淺出設計模式——創建型模式之建造者模式 Builder

文章目錄建造者模式簡介建造者模式結構建造者模式代碼實例定義產品類House定義建造者定義抽象建造者AbstractBuilder定義具體建造者定義指揮者客戶端代碼示例運行結果建造者模式總結代碼倉庫建一棟房子總共分幾步&#xff1f;建造者模式告訴你答案&#xff01;“把大象裝冰箱&a…

OpenVLA: 論文閱讀 -- 開源視覺-語言-行動模型

更多內容&#xff1a;XiaoJ的知識星球 目錄OpenVLA&#xff1a;開源視覺-語言-行動模型1. 介紹2. 相關工作1&#xff09;視覺條件語言模型&#xff08;Visually-Conditioned Language Models&#xff09;2&#xff09;通用型機器人策略&#xff08;Generalist Robot Policies&a…

JavaWeb(蒼穹外賣)--學習筆記15(分頁查詢PageHelper)

前言 終于開始學習做項目了&#xff0c;本篇文章是學習B站黑馬程序員蒼穹外賣的學習筆記&#x1f4d1;。我的學習路線是Java基礎語法-JavaWeb-做項目&#xff0c;管理端的功能學習完之后&#xff0c;就進入到了用戶端微信小程序的開發&#xff0c;這篇文章來看看分頁查詢&#…

金融專題|某跨境支付機構:以榫卯企業云平臺 VPC 功能保障業務主體安全

作者&#xff1a;SmartX 金融團隊 金融機構在信息化建設時面臨諸多數據合規要求&#xff0c;例如&#xff1a;不同業務區域之間互相隔離、數據庫僅能由關聯的應用服務器訪問、僅有特定的服務器允許被外網訪問等。對此&#xff0c;某跨境支付機構以 SmartX 榫卯企業云平臺構建私…

Win10下python環境變量呼出微軟應用商店

以下是三種徹底解決 Windows 10 的 CMD 中運行 python 命令彈出應用商店問題的方法??方法一&#xff1a;調整環境變量優先級?-或者直接刪除微軟應用商店的環境變量%USERPROFILE%\AppData\Local\Microsoft\WindowsApp???操作步驟??打開系統環境變量設置&#xff08;右鍵…

字節跳動“扣子”(Coze)開源:AI智能體生態的技術革命

&#xff08;以下借助 DeepSeek-R1 輔助整理&#xff09; 在2025年7月26日的深夜&#xff0c;GitHub上悄然出現的兩個倉庫——Coze Studio和Coze Loop&#xff0c;在48小時內狂攬超過9,000顆Star。字節跳動以Apache 2.0許可證將自家AI智能體平臺的核心技術徹底開源。 “當所有人…

Camx-usecase ID和pipeline的匹配源碼解讀

組件關系整體流程&#xff1a;camxhal3.cpp:704 open()camxhal3.cpp:1423 configure_streams()chxextensionmodule.cpp:2810 InitializeOverrideSessionchxusecaseutils.cpp:850 GetMatchingUsecase()chxadvancedcamerausecase.cpp:4729 Initialize()chxadvancedcamerausecase.…

日志管理進入「對話式」時代:日志易MCP Server落地實錄

01 背景&#xff1a;MCP協議介紹在AI蓬勃發展的當下&#xff0c;大型語言模型&#xff08;LLM&#xff09;雖展現出強大潛力&#xff0c;卻受困于與外部資源連接的難題。數據分散、接口繁雜&#xff0c;致使AI模型難以靈活對接本地資源與遠程服務&#xff0c;極大限制了其響應質…

django-3模型操作

from django.db import modelsclass Book(models.Model):title models.CharField(max_length200) # 書名author models.CharField(max_length100) # 作者publish_date models.DateField() # 出版日期price models.DecimalField(max_digits10, decimal_places2) # 價格s…

【繪制圖像輪廓】——圖像預處理(OpenCV)

目錄 1 什么是輪廓 2 尋找輪廓 2.1 mode參數 2.2 method參數 3 繪制輪廓 1 什么是輪廓 輪廓是一系列相連的點組成的曲線&#xff0c;代表了物體的基本外形。輪廓是連續的&#xff0c;邊緣不一定連續。輪廓是一個閉合的、封閉的形狀。 輪廓的作用&#xff1a; 形狀分析 目…

嵌入式 Linux 深度解析:架構、原理與工程實踐(增強版)

嵌入式 Linux 深度解析&#xff1a;架構、原理與工程實踐&#xff08;增強版&#xff09; 目錄嵌入式 Linux 深度解析&#xff1a;架構、原理與工程實踐&#xff08;增強版&#xff09;第一章 嵌入式 Linux 基礎概念1.1 定義與核心特征1.2 典型架構棧深度解析第二章 Linux 文件…

xcode swift項目運行、連接真機運行報錯,引入文件夾失敗

最近亂七八糟解決了很多報錯&#xff0c;看著記錄點吧 xcode版本&#xff1a;16 failed to emit precompiled header ‘/Users/yuqing/Library/Developer/Xcode/DerivedData/cloudspace-ios-ejldldcfhouqnretchuzoewmsqkg/Build/Intermediates.noindex/PrecompiledHeaders/spic…

[python][selenium] Web UI自動化8種頁面元素定位方式

測試工程師必備&#xff01;Selenium自動化測試全攻略 | 手寫POM框架數據驅動&#xff0c;輕松搞定UI自動化&#xff01;簡單的加個前置知識&#xff1a; 第一&#xff1a;webdriver.Chrome()這句話&#xff0c;通過WebDriver的構造方法&#xff0c;拿到瀏覽器驅動的對象&…

絲桿支撐座在電子裝配中的關鍵作用

絲桿支撐座是電子裝配過程中不可或缺的組件&#xff0c;主要用于支撐和固定絲桿&#xff0c;確保其穩定性和精度。在高速、高精度裝配場景中&#xff0c;絲桿支撐座的作用尤為突出。穩定性與精度保障&#xff1a;絲桿支撐座采用高品質鋼材制作&#xff0c;具有高剛性和高強度&a…

微信小程序頁面間通信的實現方式

微信小程序中頁面間的通信是指不同頁面之間的數據傳遞、狀態同步或交互操作&#xff0c;常見于多頁面協作場景。根據通信方向和場景不同&#xff0c;主要有以下幾種實現方式&#xff1a;一、基于頁面跳轉的參數傳遞1. 正向傳遞&#xff08;A頁面到B頁面&#xff09;通過URL參數…

uniapp開發微信小程序(新舊版本對比:授權手機號登錄、授權頭像和昵稱)

目錄標題授權手機號新舊版本核心差異對比強制使用新版的情況代碼實現方案特殊處理邏輯企業賬號要求最佳實踐建議授權頭像和昵稱新舊版本核心差異對比強制使用新版的情況代碼實現方案最佳實踐建議注意事項授權手機號 新舊版本核心差異對比 觸發方式 舊版&#xff08;2023年前&…

Java函數式編程之【Stream終止操作】【下】【三】【收集操作collect()與分組分區】【下游收集器】

分組收集器groupingBy()&#xff1a;groupingBy()收集器用于按條件對元素象進行分組&#xff0c;并將結果存儲在Map實例中。其作用與數據庫的SQL語句的group by的用法有異曲同工之妙。 分區收集器partitioningBy()&#xff1a;partitioningBy()可以看作是分組groupingBy()的特殊…

python設計模式-工廠模式

工廠模式的核心思想&#xff1a;封裝對象創建過程、解耦對象使用與創建 。示例代碼&#xff1a;from enum import Enum# 基類&#xff1a;人類 class Person:species Homo sapiensdef __init__(self, name):self.name namedef __str__(self):return f"{self.__class__._…

Rust:anyhow::Result 與其他 Result 類型轉換

當函數返回的不是 anyhow::Result 而是其他 Result 類型時&#xff08;如 std::io::Result、serde_json::Result 或自定義 Result&#xff09;&#xff0c;可通過以下方法統一處理錯誤類型&#xff0c;確保與 anyhow 兼容或實現錯誤傳播&#xff1a;&#x1f6e0;? 一、錯誤類…