Rust 語言結合了命令式和函式式程式設計的優點,提供高階抽象能力,讓開發者能更直觀地編寫程式碼。其列舉和模式匹配機制,有效提升程式碼的表達能力和正確性,編譯器能確保所有可能情況都被處理,避免遺漏。Rust 的套件管理系統 Cargo 簡化了依賴管理,提升開發效率。雖然 Rust 語言的明確性有時被認為略顯冗長,但這也提升了程式碼的可讀性和程式碼審查的效率。而 Rust 友好且活躍的社群,也為開發者提供了豐富的學習資源和支援。設定 Rust 開發環境,需要安裝 Rust 工具鏈、選擇合適的程式碼編輯器(例如 Visual Studio Code 並安裝 rust-analyzer 擴充套件),以及安裝 Docker 或 Podman 以便在容器化環境中進行開發。

1.7 Rust 的主要特點

1.7.3 多正規化程式設計

Rust 深受 ML 系列程式語言的啟發,能夠兼顧命令式程式語言的易學性和函式式程式語言的表達能力。Rust 提供了高階抽象,使程式設計師能夠更直觀地將人類思維轉化為程式碼。

列舉(Enums)與模式匹配

Rust 的列舉(Enums),也被稱為代數資料型別(Algebraic Data Types),提供了無與倫比的表達能力和正確性。當使用 match 關鍵字檢查列舉時,編譯器會確保我們不會遺漏任何可能的情況。

pub enum Status {
    Queued,
    Running,
    Failed,
}

pub fn print_status(status: Status) {
    match status {
        Status::Queued => println!("queued"),
        Status::Running => println!("running"),
    }
}

內容解密:

上述程式碼定義了一個名為 Status 的列舉,具有三種可能的狀態:QueuedRunningFailed。在 print_status 函式中,使用 matchstatus 進行模式匹配。然而,當編譯這段程式碼時,編譯器會報錯,因為我們沒有處理 Failed 狀態。

編譯錯誤訊息如下:

error[E0004]: non-exhaustive patterns: `Failed` not covered
--> src/lib.rs:8:11
|
1 | / pub enum Status {
2 | |     Queued,
3 | |     Running,
4 | |     Failed,
| |     ------ not covered
5 | | }
| |_- `Status` defined here
...
8 |     match status {
|         ^^^^^^ pattern `Failed` not covered
|
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
= note: the matched value is of type `Status`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0004`.
error: could not compile `enums`

這段錯誤訊息告訴我們,match 陳述式並未涵蓋所有可能的列舉值。我們需要新增對 Failed 狀態的處理,或者使用萬用字元(wildcard)來處理未被明確列出的情況。

修正後的程式碼如下:

pub fn print_status(status: Status) {
    match status {
        Status::Queued => println!("queued"),
        Status::Running => println!("running"),
        Status::Failed => println!("failed"),
    }
}

1.7.4 模組化設計

Rust 的套件管理系統(稱為「crates」)非常易用,類別似於 Node.js 的 NPM。這使得依賴管理變得簡單,大大改善了開發體驗。

1.7.5 明確性

Rust 語言非常明確,這使得程式更容易理解,程式碼審查也更加有效。然而,這種明確性有時會被批評為冗長。

1.7.6 社群支援

Rust 的社群以友好和樂於助人著稱。從論壇上的熱心幫助到免費的教育資源,Rust 社群是開源世界中最受歡迎的社群之一。這可能與目前使用 Rust 的公司數量較少有關,大多數社群成員都是對 Rust 感興趣的程式設計師,他們樂於分享相關知識。

1.8 環境設定

在開始編寫程式碼之前,我們需要設定開發環境。主要需要安裝 Rust、程式碼編輯器和 Docker。

1.8.1 安裝 Rust

rustup 是管理 Rust 工具鏈的官方工具。它可以用來更新 Rust 和安裝其他元件,如自動程式碼格式化工具 rustfmt。可以從 https://rustup.rs 取得安裝方法。

1.8.2 安裝程式碼編輯器

目前最推薦的免費程式碼編輯器是微軟的 Visual Studio Code。可以從 https://code.visualstudio.com 下載安裝。安裝後,需要安裝 rust-analyzer 擴充套件以獲得程式碼補全和型別提示功能,可以在 https://marketplace.visualstudio.com/items?itemName=matklad.rust-analyzer 找到。

1.8.3 安裝 Docker 或 Podman

Docker 和 Podman 是用於管理 Linux 容器的工具,可以讓我們在乾淨的環境中工作,使建置和佈署過程更加可重現。建議在 macOS 和 Windows 上使用 Docker,在 Linux 上使用 Podman。可以從官方網站取得安裝說明:

安裝完成後,我們將使用類別似以下的命令:

$ docker run -ti debian:latest

如果使用 Podman,只需將 docker 命令替換為 podman

$ podman run -ti debian:latest

或者,可以在 shell 組態檔中建立別名:

# in .bashrc or .zshrc
alias docker=podman

隨著 Rust 語言的不斷發展,其在系統程式設計、網路服務和嵌入式系統等領域的應用將越來越廣泛。未來,我們可以期待看到更多根據 Rust 的創新專案和技術解決方案。同時,Rust 社群的持續成長和貢獻也將進一步推動語言本身和相關生態系統的發展。

  graph TD;
    A[開始] --> B[安裝 Rust];
    B --> C[安裝程式碼編輯器];
    C --> D[安裝 Docker 或 Podman];
    D --> E[組態開發環境];
    E --> F[開始使用 Rust 開發];

圖表翻譯: 此圖示展示了設定 Rust 開發環境的流程。首先,我們需要安裝 Rust,接著安裝合適的程式碼編輯器,然後安裝 Docker 或 Podman,最後組態開發環境並開始使用 Rust 進行開發。每一步都是開發流程中的重要環節,確保了開發環境的完整性和可用性。

1.9 我們的第一個Rust程式:SHA-1雜湊破解器

是時候動手寫我們的第一個Rust程式了。和本文中的所有程式碼範例一樣,您可以在隨附的Git儲存函式庫中找到完整的程式碼:https://github.com/skerkour/black-hat-rust

建立新專案

首先,我們使用Cargo建立一個新的Rust專案:

$ cargo new sha1_cracker

這將在sha1_cracker資料夾中建立一個新的專案。預設情況下,Cargo會建立一個二進位制(應用程式)專案。如果需要建立函式庫專案,可以使用--lib標誌:

$ cargo new my_lib --lib

SHA-1雜湊函式簡介

SHA-1是一種雜湊函式,過去被許多網站用來儲存使用者的密碼。理論上,雜湊後的密碼無法從其雜湊值中還原。因此,網站可以透過比較雜湊值來驗證使用者是否知道其密碼,而無需以明文儲存密碼。如果網站的資料函式庫被洩露,則無法還原密碼以存取使用者的資料。

圖1.4:雜湊函式的工作原理

此圖示展示了雜湊函式的基本運作方式。

圖表翻譯: 此圖示說明瞭雜湊函式如何將輸入資料轉換為固定長度的雜湊值。雜湊函式具有單向性,即無法從雜湊值還原原始輸入。

現實情況與雜湊破解

然而,現實情況卻大不相同。假設我們入侵了一個使用SHA-1儲存密碼的網站,並希望還原使用者的憑據以存取他們的帳戶。這時就需要使用「雜湊破解器」。雜湊破解器是一種程式,會嘗試多個不同的雜湊值,以找到原始密碼。

為什麼不該使用SHA-1

由於SHA-1相對容易被暴力破解,因此在建立網站時,應使用專門為密碼儲存設計的雜湊函式,如argon2id。這些函式需要更多的資源來進行暴力破解。

我們的SHA-1破解器程式

這個簡單的程式將幫助我們學習Rust的基本知識:

  • 如何使用命令列介面(CLI)引數
  • 如何讀取檔案
  • 如何使用外部函式庫
  • 基本的錯誤處理
  • 資源管理

程式入口點:main函式

與幾乎所有程式語言一樣,Rust程式的入口點是其main函式。

fn main() {
    // ...
}

讀取命令列引數

讀取命令列引數非常簡單:

use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
}

在這裡,我們匯入了標準函式庫中的env模組,並使用env::args()函式來取得命令列引數的迭代器,然後將其收集到一個Vec<String>(字串向量)中。

檢查引數數量

接下來,我們檢查引數的數量是否符合預期:

use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() != 3 {
        println!("用法:");
        println!("sha1_cracker: <wordlist.txt> <sha1_hash>");
        return;
    }
}

使用println!巨集

您可能已經注意到,println!的語法帶有感嘆號。這是因為println!不是一個普通的函式,而是一個巨集。巨集在編譯時被評估和檢查,可以防止諸如格式字串漏洞等安全問題。

錯誤處理

當遇到錯誤時,我們的程式應該如何表現?如何通知使用者?這就是錯誤處理。

Rust的錯誤處理優勢

在十多種我熟悉的程式語言中,Rust的錯誤處理由於其明確性、安全性和簡潔性而成為我的最愛。對於我們的簡單程式,我們將使用Box<dyn Error>來處理錯誤。

use std::{
    env,
    error::Error,
};

const SHA1_HEX_STRING_LENGTH: usize = 40;

fn main() -> Result<(), Box<dyn Error>> {
    let args: Vec<String> = env::args().collect();
    if args.len() != 3 {
        println!("用法:");
        println!("sha1_cracker: <wordlist.txt> <sha1_hash>");
        return Ok(());
    }

    let hash_to_crack = args[2].trim();
    if hash_to_crack.len() != SHA1_HEX_STRING_LENGTH {
        return Err("sha1雜湊值無效".into());
    }

    Ok(())
}

讀取檔案

為了減少需要測試的SHA-1雜湊值數量,我們使用了一種特殊的字典,稱為字詞表(wordlist),其中包含了在被入侵網站中最常見的密碼。

use std::{
    env,
    error::Error,
    fs::File,
    io::{BufRead, BufReader},
};

const SHA1_HEX_STRING_LENGTH: usize = 40;

fn main() -> Result<(), Box<dyn Error>> {
    // ...

    let wordlist_file = File::open(&args[1])?;
    let reader = BufReader::new(wordlist_file);

    for line in reader.lines() {
        let line = line?.trim().to_string();
        println!("{}", line);
    }

    Ok(())
}

使用外部函式庫(Crates)

現在,我們的程式基本結構已經就緒,需要計算SHA-1雜湊值。幸運的是,已經有開發者開發了這段複雜的程式碼,並以外部函式庫的形式分享出來。在Rust中,這些函式庫或套件稱為「crates」。它們可以在https://crates.io線上瀏覽,並透過Cargo(Rust的套件管理器)進行管理。

在Cargo.toml中宣告相依性

在使用crate之前,需要在Cargo的設定檔Cargo.toml中宣告其版本:

[package]
name = "sha1_cracker"
version = "0.1.0"
authors = ["Sylvain Kerkour"]
edition = "2021"

[dependencies]
sha-1 = "0.9"
hex = "0.4"

在程式中匯入crate

然後,我們可以在SHA-1破解器中匯入它:

use sha1::Digest;
use std::{
    env,
    error::Error,
    fs::File,
    io::{BufRead, BufReader},
};

const SHA1_HEX_STRING_LENGTH: usize = 40;

fn main() -> Result<(), Box<dyn Error>> {
    // ...
}

程式碼詳細解析

初始化與引數檢查

let args: Vec<String> = env::args().collect();
if args.len() != 3 {
    println!("用法:");
    println!("sha1_cracker: <wordlist.txt> <sha1_hash>");
    return Ok(());
}

這段程式碼首先收集命令列引數。如果引數數量不是3,則列印用法並傳回。

驗證SHA-1雜湊值

let hash_to_crack = args[2].trim();
if hash_to_crack.len() != SHA1_HEX_STRING_LENGTH {
    return Err("sha1雜湊值無效".into());
}

這裡,我們檢查提供的SHA-1雜湊值是否具有正確的長度(40個字元)。如果不是,則傳回錯誤。

讀取字詞表檔案

let wordlist_file = File::open(&args[1])?;
let reader = BufReader::new(wordlist_file);

for line in reader.lines() {
    let line = line?.trim().to_string();
    println!("{}", line);
}

這段程式碼開啟指定的字詞表檔案,並逐行讀取內容,然後列印出來。

結語

本章節介紹瞭如何使用Rust編寫一個簡單的SHA-1雜湊破解器。透過這個範例,我們學習瞭如何讀取命令列引數、處理檔案、以及使用外部函式庫。此外,我們還瞭解了Rust中錯誤處理的基本方法。這些知識對於開發更複雜的Rust應用程式至關重要。接下來的章節將進一步探討Rust的其他重要特性。

未來,我們可以進一步最佳化這個SHA-1破解器,例如透過平行處理來提高破解效率,或者使用更進階的密碼學技術來改進安全性。同時,也可以探索其他型別的雜湊函式及其破解方法,以拓寬我們對密碼學領域的理解。

SHA-1破解器流程圖
  graph LR;
    F[F]
    A[開始] --> B{檢查引數};
    B -->|正確| C[讀取字詞表];
    B -->|錯誤| D[列印用法並離開];
    C --> E[計算SHA-1雜湊];
    E --> F{比較雜湊值};
    F -->|匹配| G[輸出結果];
    F -->|不匹配| H[繼續下一行];
    H --> C;

圖表翻譯: 此圖示展示了SHA-1破解器的基本流程。首先檢查輸入引數是否正確,如果正確則讀取字詞表並計算每行的SHA-1雜湊值,將其與目標雜湊值進行比較。如果匹配,則輸出結果;如果不匹配,則繼續處理下一行,直到找到匹配或處理完所有行。