Rust 語言以其記憶體安全特性著稱,能有效避免許多常見的安全性漏洞。本文首先以 Heartbleed 和 Goto Fail 這兩個案例說明 Rust 如何降低這類別漏洞發生的風險,並進一步探討 SSL/TLS 協定和 SHA-1 雜湊函式如何確保資料傳輸的安全性。接著,文章介紹 Rust 的所有權系統、借用檢查等核心概念,這些機制是 Rust 實作記憶體安全的重要根本。最後,文章也涵蓋了 Rust 在命令列工具、資料處理、伺服器端應用、嵌入式系統等不同領域的應用,展現其多元的發展潛力。

1.9 TLS 安全性案例研究

為了證明 Rust 不能解決所有錯誤,我們來看看兩個嚴重的漏洞,它們幾乎影響了所有導向網際網路的裝置,並考慮一下 Rust 是否能夠預防這些漏洞。

1.9.1 Heartbleed

Heartbleed 是一個嚴重的安全漏洞,官方編號為 CVE-2014-0160。它是由於緩衝區未被正確清除導致的。如果你想處理一些秘密資訊,你可能會決定重用一個緩衝區,但如果你沒有在使用後清除緩衝區,資訊就會從一個讀取洩露到下一個讀取。

let buffer = &mut [0u8; 1024];
read_secrets(&user1, buffer);
store_secrets(buffer);
read_secrets(&user2, buffer);
store_secrets(buffer);

Rust 不能夠保護你免於邏輯錯誤,但它確保你的資料永遠不會被同時寫入兩個地方。

1.9.2 Goto Fail

Goto fail 是另一個嚴重的安全漏洞,官方編號為 CVE-2014-1266。它是由於一個函式設計用於驗證加密金鑰對,但最終跳過了所有檢查。以下是原始 SSLVerifySignedServerKeyExchange 函式的一部分:

static OSStatus
SSLVerifySignedServerKeyExchange(SSLContext *ctx,
                                bool isRsa,
                                SSLBuffer signedParams,
                                uint8_t *signature,
                                UInt16 signatureLen)
{
    OSStatus err;
    //...
}

圖表翻譯:

  flowchart TD
    A[開始] --> B[讀取秘密資訊]
    B --> C[儲存秘密資訊]
    C --> D[讀取下一個秘密資訊]
    D --> E[儲存下一個秘密資訊]

這個流程圖展示了 Heartbleed 漏洞可能發生的情況,重用緩衝區而未清除可能導致資訊洩露。

內容解密:

上述程式碼展示瞭如何使用 Rust 重用緩衝區來處理秘密資訊,但如果未正確清除緩衝區,可能會導致資訊洩露。Rust 的安全性檢查可以幫助預防這型別的錯誤,但它不能代替程式設計師的謹慎和小心。

實作安全的資料傳輸:SSL/TLS 協定與 SHA-1 雜湊函式

在網路通訊中,安全性是一個至關重要的議題。為了確保資料傳輸的安全性,SSL/TLS 協定被廣泛使用。SSL/TLS 協定依賴於加密技術和雜湊函式來確保資料的完整性和保密性。在本文中,我們將探討 SSL/TLS 協定中使用的 SHA-1 雜湊函式,並展示如何實作安全的資料傳輸。

SSL/TLS 協定概述

SSL/TLS 協定是一種安全的通訊協定,用於在網路上提供加密和身份驗證。它被廣泛使用於網頁瀏覽、電子郵件、即時通訊等應用中。SSL/TLS 協定的核心是使用公鑰加密和對稱加密來保護資料傳輸。

SHA-1 雜湊函式

SHA-1(Secure Hash Algorithm 1)是一種廣泛使用的雜湊函式,它可以將任意長度的輸入資料對映到一個固定長度的雜湊值。SHA-1 雜湊函式被用於 SSL/TLS 協定中,以確保資料傳輸的完整性和保密性。

// SHA-1 雜湊函式實作
void SHA1_Update(SHA_CTX *ctx, const unsigned char *data, unsigned int len) {
    // 更新雜湊值
    ctx->h0 += len;
    ctx->h1 += len;
    ctx->h2 += len;
    ctx->h3 += len;
    ctx->h4 += len;

    // 處理資料
    for (int i = 0; i < len; i++) {
        ctx->message[i] = data[i];
    }
}

實作安全的資料傳輸

要實作安全的資料傳輸,我們需要使用 SSL/TLS 協定和 SHA-1 雜湊函式。以下是實作步驟:

  1. 初始化 SSL/TLS 連線。
  2. 使用 SHA-1 雜湊函式計算資料的雜湊值。
  3. 將資料和雜湊值一起傳輸給接收方。
  4. 接收方使用 SHA-1 雜湊函式計算接收到的資料的雜湊值。
  5. 比較兩個雜湊值,如果相同,則資料傳輸成功。
// 實作安全的資料傳輸
int main() {
    // 初始化 SSL/TLS 連線
    SSL_CTX *ctx = SSL_CTX_new();
    SSL *ssl = SSL_new(ctx);

    // 使用 SHA-1 雜湊函式計算資料的雜湊值
    unsigned char data[] = "Hello, World!";
    unsigned int len = strlen(data);
    SHA_CTX sha_ctx;
    SHA1_Init(&sha_ctx);
    SHA1_Update(&sha_ctx, data, len);
    unsigned char hash[20];
    SHA1_Final(hash, &sha_ctx);

    // 將資料和雜湊值一起傳輸給接收方
    SSL_write(ssl, data, len);
    SSL_write(ssl, hash, 20);

    // 接收方使用 SHA-1 雜湊函式計算接收到的資料的雜湊值
    unsigned char recv_data[1024];
    int recv_len = SSL_read(ssl, recv_data, 1024);
    SHA_CTX recv_sha_ctx;
    SHA1_Init(&recv_sha_ctx);
    SHA1_Update(&recv_sha_ctx, recv_data, recv_len);
    unsigned char recv_hash[20];
    SHA1_Final(recv_hash, &recv_sha_ctx);

    // 比較兩個雜湊值
    if (memcmp(hash, recv_hash, 20) == 0) {
        printf("資料傳輸成功!\n");
    } else {
        printf("資料傳輸失敗!\n");
    }

    return 0;
}

Rust 在安全性方面的優勢

Rust 是一種相對較新的程式語言,設計上注重安全性和效能。它的出現正是為瞭解決傳統程式語言在記憶體安全性方面的問題。Rust 的核心理念是透過編譯時檢查和所有權系統來確保記憶體安全,避免了許多因為指標錯誤或野指標導致的當機和安全漏洞。

1. 所有權系統(Ownership System)

Rust 的所有權系統是其安全性的根本。這個系統確保每個值都有一個唯一的所有者,並且在任何時候,只能有一個所有者對值負責。這樣可以避免資料競爭和野指標等問題。

2. 借用檢查(Borrow Checker)

Rust 的借用檢查機制是根據所有權系統的進一步擴充套件。它允許你在不違反所有權規則的情況下借用值(即使用值而不取得其所有權)。借用檢查器會在編譯時檢查你的程式碼,以確保你沒有違反借用規則,從而避免了許多可能導致當機或安全漏洞的錯誤。

3. 無空指標(No Null Pointers)

在 Rust 中,你不能直接使用空指標(null pointers)。取而代之的是,Rust 提供了 OptionResult 型別來處理可能缺失的值或操作失敗的情況。這種設計可以避免因為空指標導致的錯誤和當機。

4. 模式匹配(Pattern Matching)

Rust 的模式匹配功能允許你以更安全、更具表達力的方式處理不同的情況和錯誤。透過使用 match 陳述式,你可以明確地處理所有可能的情況,避免遺漏某些情況從而導致的錯誤。

5. 錯誤處理(Error Handling)

Rust 的錯誤處理機制根據 Result 型別和 ? 運算子。這使得你可以以一致且易於理解的方式處理錯誤,確保你的程式碼在面對錯誤時能夠穩健地應對。

Rust 在實際應用中的優勢

  1. 系統程式設計:Rust 的記憶體安全性和效能使其非常適合系統程式設計任務,如作業系統、檔案系統和網路協定實作。
  2. 網路服務:Rust 的非同步 I/O 和高效的網路函式庫使其成為構建高效能網路服務的理想選擇。
  3. 嵌入式系統:Rust 的低階記憶體控制和效能特點使其適用於嵌入式系統開發,如微控制器應用。
  4. WebAssembly:Rust 是構建 WebAssembly 應用的首選語言之一,因為它提供了高效、安全的方式來執行瀏覽器中的程式碼。
內容解密:

上述內容簡要介紹了 Rust 的安全性優勢和實際應用場景。透過 Rust 的所有權系統、借用檢查、無空指標、模式匹配和錯誤處理機制,可以確保開發出的軟體更加安全、可靠。同時,Rust 的高效性和低階記憶體控制能力使其適用於各種領域的開發任務。

圖表翻譯:

  graph LR
    A[Rust] -->|安全性|> B[記憶體安全]
    A -->|效能|> C[高效執行]
    B -->|所有權系統|> D[無空指標]
    C -->|非同步I/O|> E[網路服務]
    D -->|模式匹配|> F[錯誤處理]
    E -->|WebAssembly|> G[瀏覽器執行]
    F -->|Result型別|> G

上述 Mermaid 圖表展示了 Rust 的核心特點及其在不同領域的應用關係。從圖表中可以看出,Rust 的安全性和效能是其核心優勢,而這些優勢又根據其所有權系統、無空指標、模式匹配和錯誤處理機制等特點。同時,圖表也展示了 Rust 在網路服務、嵌入式系統和 WebAssembly 等領域的應用。

Rust 的應用領域

Rust 是一種通用程式語言,雖然它被設計為系統程式語言,但它也可以用於許多其他領域。以下是 Rust 可以應用的幾個領域:

1.10.1 命令列工具

Rust 對於開發命令列工具有三個主要優點:啟動時間最小化、記憶體使用量低和易於佈署。由於 Rust 不需要初始化解譯器(如 Python、Ruby 等)或虛擬機器(如 Java、C# 等),因此程式可以快速啟動。

1.10.2 資料處理

Rust 在文書處理和其他形式的資料操作方面表現出色。開發人員可以控制記憶體使用量和快速啟動時間。截至 2017 年中,Rust 擁有世界上最快的正規表示式引擎。2019 年,Apache Arrow 資料處理專案(該專案是 Python 和 R 資料科學生態系統的基礎)接受了根據 Rust 的 DataFusion 專案。

1.10.3 擴充套件應用程式

Rust 適合用於擴充套件用動態語言編寫的程式。這使得開發人員可以使用 Rust 建立 JNI(Java Native Interface)擴充套件、C 擴充套件或 Erlang/Elixir NIFs(native implemented functions)。Rust 可以減少記憶體洩漏或當機的風險。

1.10.4 資源受限環境

C 語言已經佔據了微控制器領域數十年。然而,物聯網(IoT)的到來意味著可能會有數十億個不安全的裝置暴露在網路中。任何輸入解析程式碼都會被定期掃描以尋找弱點。由於這些裝置的韌體更新非常少,因此確保這些裝置從一開始就能夠安全至關重要。Rust 可以在這方面發揮重要作用。

1.10.5 伺服器端應用程式

大多數用 Rust 編寫的應用程式都執行在伺服器上。這些應用程式可能負責處理網路流量或支援業務營運。還有一層服務位於作業系統和應用程式之間。Rust 用於編寫資料函式庫、監控系統、搜尋器和訊息系統。例如,npm套件登入函式庫是為JavaScript和Node.js社群編寫的,使用Rust編寫。

Rust 的多元應用與社群力量

Rust 的設計初衷並不限制其應用於開發使用者端軟體。事實上,Servo 這個網頁瀏覽器引擎就是一個典型的使用者端應用程式。遊戲開發也是 Rust 的一個重要應用領域。

桌面應用程式

儘管移動裝置和網頁應用程式越來越受歡迎,但桌面應用程式仍然具有重要的地位。這類別應用程式通常複雜、難以開發和維護。Rust 的優雅佈署方式和嚴謹的設計使其成為開發桌面應用程式的理想選擇。未來,Rust 將會是許多桌面應用程式的首選語言。

移動應用程式

Android 和 iOS 等移動作業系統通常為開發者提供了一條「被祝福的路徑」。例如,Android 的首選語言是 Java,而 macOS 則是 Swift。然而,Rust 也可以透過原生介面與移動裝置進行互動,無需額外的執行時成本。

網頁應用程式

JavaScript 是網頁開發的主流語言,但隨著 WebAssembly(Wasm)的出現,情況可能會有所改變。Wasm 提供了一個可以被多種語言編譯到的目標平臺,Rust 就是其中之一。將 Rust 專案移植到瀏覽器中只需要兩條額外的命令列命令。

系統程式設計

系統程式設計是 Rust 的核心領域。許多大型專案都使用 Rust 實作,包括編譯器、遊戲引擎和作業系統。Rust 社群中有許多開發者在從事 parser 生成器、資料函式庫和檔案格式等專案。

社群力量

Rust 的成功不僅僅歸功於其技術優勢,還有其積極和包容的社群。無論您在 Rust 世界的哪個角落,都會受到禮貌和尊重的對待。

Rust 的隱藏特性:社群

Rust 社群有一些特殊的術語和理念,包括「賦權每個人」、「驚人的速度」、「無畏的並發」、「沒有 Rust 2.0」和「零成本抽象」。這些概念體現了 Rust 的設計哲學和價值觀。

Rust程式設計語言的優勢

Rust是一種現代化的程式設計語言,已經成功應用於許多大型軟體專案中。其優勢在於可以編譯為多種平臺,包括PC、瀏覽器、伺服器、行動裝置和物聯網裝置。Rust語言深受玄貓的喜愛,曾多次獲得Stack Overflow的「最受喜愛的程式設計語言」稱號。

Rust的安全性和效率

Rust允許開發者在不怕錯誤的情況下進行實驗。它提供了正確性保證,不像其他工具需要付出執行成本。Rust有三個主要的命令列工具:cargo、rustup和rustc,分別負責管理crate、安裝和編譯Rust原始碼。

Rust的語言基礎

Rust語言基礎包括基本型別、資料結構、控制流程和集合等。開發者可以使用Rust建立命令列工具、編譯程式等。Rust的語言特性使其能夠提供穩定、快速和輕量級的程式碼。

Rust語言的核心概念

Rust語言的核心概念包括:

  • 基本型別:整數、文字等
  • 複雜型別:struct、enum等
  • 函式和方法
  • 控制流程:if、else、match等
  • 集合:向量、陣列、元組等
  • Rust的工具:cargo、rustup、rustc等

Rust語言的學習方法

學習Rust語言可以從基本語法開始,逐步深入到高階特性。開發者可以透過實踐和例子來學習Rust,例如建立一個簡單的grep工具。Rust的社群非常活躍和友好,新手可以在遇到問題時尋求幫助。

建立可執行程式

2.1 建立可執行程式

每個純文字檔案都有一個隱藏的超能力:當它包含正確的符號時,可以被轉換成可以被玄貓解譯的東西。那是程式語言的魔力。本章的目標是讓您熟悉將Rust原始碼轉換成可執行程式的過程。瞭解這個過程比您想象的更有趣!並且它為您設定了一個令人興奮的學習旅程。透過玄貓,您將實作一個虛擬CPU,可以解譯您建立的程式。

2.1.1 使用rustc編譯單個檔案

清單 2.1 是一個簡短但完整的Rust程式。要將其翻譯成可執行程式,我們使用了一種叫做編譯器的軟體。編譯器的角色是將原始碼翻譯成機器碼,以及處理許多書記工作以滿足作業系統(OS)和CPU。Rust編譯器稱為rustc。您可以在ch2/ok.rs檔案中找到清單 2.1 的原始碼。

fn main() {
    println!("OK")
}

要將單個Rust檔案編譯成可執行程式:

  1. 將您的原始碼儲存到一個檔案中。在我們的例子中,我們使用檔名ok.rs。
  2. 確保原始碼包含一個main()函式。
  3. 開啟一個shell視窗,例如Terminal、cmd.exe、Powershell、bash、zsh或其他。
  4. 執行命令rustc <檔案>,其中<檔案>是您要編譯的檔案。

當編譯成功時,rustc不會向主控臺傳送任何輸出。在背後,rustc已經建立了一個可執行檔,使用輸入檔名來選擇輸出檔名。

假設您已經將清單 2.1 儲存到名為 ok.rs 的檔案中,讓我們看看那個過程:

$ rustc ok.rs
$./ok
OK

2.1.2 使用Cargo編譯Rust專案

大多數Rust專案都比單個檔案更大,通常包括相依性。為了準備好,我們將使用比rustc更高階的工具,稱為Cargo。Cargo瞭解如何驅動rustc(以及更多)。

清單 2.1 幾乎是最短的有效Rust程式

對於Windows,請在檔名中包含.exe副檔名(例如ok.exe)。

將單個檔案工作流程遷移到Cargo

從單個檔案工作流程遷移到Cargo有兩種方法。第一種方法是將原始檔案移到一個空目錄中。然後執行cargo init命令。

以下是該過程的詳細概述,假設您從名為 ok.rs 的檔案開始,該檔案由玄貓生成:

  1. 執行 mkdir <project> 來建立一個空目錄(例如 mkdir ok)。
  2. 將您的原始碼移到 <project> 目錄中(例如 mv ok.rs ok)。
  3. 切換到 <project> 目錄(例如 cd ok)。
  4. 執行 cargo init

從這個時候起,您可以發出 cargo run 來執行您的專案原始碼。與rustc不同的一點是編譯後的可執行檔位於 <project>/target 子目錄中。另一個不同之處是 Cargo 提供了更多輸出:

$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.03s

執行Cargo指令的詳細過程

當我們執行cargo run指令時,Cargo會自動呼叫Rust編譯器(rustc)來編譯和連結我們的程式碼。要了解這個過程的細節,可以使用-v旗標(verbose模式)來檢視Cargo的執行細節。

移除目標目錄

在開始之前,我們可以先移除之前編譯產生的目標目錄,以確保我們從頭開始編譯:

$ rm -rf target/

執行Cargo以verbose模式

現在,讓我們使用-v旗標執行Cargo:

$ cargo run -v

這將顯示Cargo編譯和連結程式碼的詳細過程。

編譯過程

Cargo首先會編譯我們的程式碼:

  Compiling ok v0.1.0 (/tmp/ok)

然後,Cargo會呼叫Rust編譯器(rustc)來編譯我們的程式碼:

  Running `rustc
--crate-name ok
--edition=2018
ok.rs
--error-format=json
--json=diagnostic-rendered-ansi
--crate-type bin
--emit=dep-info,link
-C embed-bitcode=no
-C debuginfo=2
-C metadata=55485250d3e77978
-C extra-filename=-55485250d3e77978
--out-dir /tmp/ok/target/debug/deps
-C incremental=/tmp/target/debug/incremental
-L dependency=/tmp/ok/target/debug/deps

這個命令顯示了Rust編譯器的各個引數,包括:

  • --crate-name ok: 指定crate的名稱。
  • --edition=2018: 指定Rust版本。
  • ok.rs: 指定要編譯的檔案。
  • --error-format=json: 指定錯誤訊息的格式。
  • --json=diagnostic-rendered-ansi: 指定診斷訊息的格式。
  • --crate-type bin: 指定crate的型別(可執行檔)。
  • --emit=dep-info,link: 指定要產生的檔案型別(依賴資訊和連結檔)。
  • -C embed-bitcode=no: 指定是否嵌入bitcode。
  • -C debuginfo=2: 指定debug資訊的級別。
  • -C metadata=55485250d3e77978: 指定metadata的值。
  • -C extra-filename=-55485250d3e77978: 指定額外的檔案名稱。
  • --out-dir /tmp/ok/target/debug/deps: 指定輸出目錄。
  • -C incremental=/tmp/target/debug/incremental: 指定增量編譯的目錄。
  • -L dependency=/tmp/ok/target/debug/deps: 指定依賴函式庫的目錄。

結果

最後,Cargo會顯示編譯和連結的結果:

  OK

這表示程式碼已經成功編譯和連結。

內容解密:

在這個過程中,我們可以看到Cargo如何呼叫Rust編譯器來編譯和連結我們的程式碼。透過使用-v旗標,我們可以瞭解到更多關於Cargo和Rust編譯器的執行細節。這對於瞭解和最佳化我們的程式碼非常有幫助。

圖表翻譯:

以下是 Cargo 執行過程的 Mermaid 圖表:

  flowchart TD
    A[開始] --> B[移除目標目錄]
    B --> C[執行Cargo以verbose模式]
    C --> D[編譯過程]
    D --> E[呼叫Rust編譯器]
    E --> F[產生輸出檔案]
    F --> G[顯示結果]

這個圖表顯示了 Cargo 執行過程的各個步驟,從開始到顯示結果。

Rust 語法初探

Rust 的語法設計宗旨是盡可能地簡潔和可預測。它包含了你在其他語言中見過的熟悉概念,例如變數、數字、函式等。例如,Rust 使用大括號 {} 來定義程式碼塊,使用單個等號 = 作為指定運算子,並且對空白字元不敏感。

從技術架構視角來看,Rust 的所有權系統和借用檢查器雖然在記憶體安全和避免資料競爭方面表現出色,但對於習慣了 C/C++ 等傳統語言的開發者而言,需要一定的學習成本。Rust 的編譯器 rustc 與構建工具 Cargo 的協同工作,簡化了從單個檔案到複雜專案的構建過程,這對於提升開發效率至關重要。然而,Cargo 的複雜性以及 verbose 模式的輸出資訊量巨大,對於新手而言可能較難理解。雖然 Rust 提供了類別似 println! 的宏來簡化輸出操作,但其底層機制和錯誤處理方式仍需深入學習。

權衡 Rust 在不同應用領域的表現,其在命令列工具、資料處理和系統程式設計等領域的優勢顯著。但其在桌面應用、移動應用和網頁應用等領域的發展仍處於早期階段,需要更多實際案例和社群支援。雖然 WebAssembly 為 Rust 進入網頁端提供了新的可能性,但 JavaScript 的主導地位仍難以撼動。Rust 社群的活躍和包容性,以及「賦能每個人」等理念,為其長期發展提供了堅實的基礎。但同時也需要注意避免社群內部的技術門檻,才能更好地吸引和留住新的開發者。

展望未來,Rust 在系統程式設計、嵌入式系統和 WebAssembly 等領域的應用前景廣闊。隨著更多大型專案的採用和社群的持續發展,Rust 的生態系統將更加完善,開發工具和函式庫的支援也將更加豐富。然而,Rust 的學習曲線仍然較陡峭,需要投入更多資源來降低學習門檻,才能更好地推廣和普及。玄貓認為,Rust 雖然不能解決所有問題,但在追求安全、高效和可靠的軟體開發領域,它無疑是一股強大的新生力量,值得長期關注和投入。