Go 開發者的密碼學導航:crypto 庫使用指南

Go 號稱 “開箱即用”,這與其標準庫的豐富功能和高質量是分不開的。而在 Go 標準庫中,crypto 庫(包括 crypto 包、crypto 目錄下相關包以及 golang.org/x/crypto 下的補充包) 又是 Go 社區最值得稱道的 Go 庫之一。

crypto 庫由 Go 核心團隊維護,確保了最高級別的安全標準和及時的漏洞修復,爲開發者提供了可靠的安全保障。crypto 還涵蓋了從基礎的對稱加密到複雜的非對稱加密,以及各種哈希函數和數字簽名算法等廣泛的加解密算法支持,以滿足 Go 開發者的各種需求爲目的,而不是與其他密碼學工具包競爭。此外,crypto 庫還經過精心優化,能夠在不同硬件平臺上儘可能地保證高效的執行性能。值得一提的是,crypto 庫還提供了統一的 API 設計,使得不同加密算法的使用方式保持一致,也降低了開發者的學習成本。

可以說 Go crypto 庫 [1] 是 Go 生態中密碼學功能的核心,它爲 Go 開發者提供了一套全面、安全、保持現代化提供安全默認值易於使用的密碼學工具,使得在 Go 應用程序中實現各種密碼學功能需求時變得簡單而可靠。

不過要理解並得心應手的使用 crypto 庫中的相關密碼學包仍然並非易事,這是因爲密碼學涉及數學、密碼分析、計算機安全等多個學科,概念多,算法也十分複雜,而大多程序員對密碼學的瞭解又多停留在使用層面,缺乏對其原理和底層機制的深入認知,甚至連每個包的用途都不甚瞭解。這導致很多開發者瀏覽了 crypto 相關包之後,甚至不知道該使用哪個包。

所以在這篇文章中,我想爲 Go 開發者建立一張 crypto 庫的 “地圖”,這張“地圖” 將幫助我們從宏觀角度理解 crypto 庫的結構,幫助大家快速精準選擇正確的包。並且通過對 crypto 相關包設計的理解,輕鬆掌握 crypto 相關包的使用模式。

注:Go 標準庫 crypto 庫的第一任負責人是 Adam Langley(agl)[2],他開創了 Go crypto 庫,他在招募和培養了 Filippo Valsorda[3] 後離開了 Go 項目,後者成爲了 Go crypto 的負責人。Filippo 在 Go 項目工作若干年後,把負責人交給了 Roland Shoemaker[4],即現任 Go 團隊安全組的負責人。當然 Shoemaker 也是 Filippo 招募到 Go 團隊中的。

下面我們首先來看看 Go crypto 庫的 “整體架構”。

  1. 標準庫 crypto 與 golang.org/x/crypto

Go 的密碼學功能 (即我們統一稱的 crypto 庫) 分爲兩個主要部分:標準庫的 crypto 相關包和擴展庫 golang.org/x/crypto。這種分離設計有其特定的目的和優勢:

Go 標準庫的 crypto 相關包,包含了最基礎、最穩定和使用最廣泛的密碼學算法。這些算法實現經過 Go 團隊的嚴格審查,保證了長期穩定性和向後兼容性。同時,這些包是隨 Go 安裝包分發的,使用時再無需引入額外的依賴。

而 golang.org/x/crypto 則號稱是 Go 標準庫 crypto 相關包的補充庫,雖然它同樣由 Go 團隊維護,但由於不是標準庫,它可以包含更多實驗性或較新的密碼學算法及實現,並可以更快速的迭代和更新。這樣它也可以成爲 Go 標準庫中一些 crypto 相關包的 “孵化器”,就像當年 golang.org/x/net/context 提升爲標準庫 context[5] 一樣。

同時 golang.org/x/crypto 也是 Go 標準庫依賴的爲數極少的外部包之一。比如,下面是 Go 1.23.0[6] 標準庫 go.mod 文件的內容:

module std

go 1.23

require (
 golang.org/x/crypto v0.23.1-0.20240603234054-0b431c7de36a
 golang.org/x/net v0.25.1-0.20240603202750-6249541f2a6c
)

require (
 golang.org/x/sys v0.22.0 // indirect
 golang.org/x/text v0.16.0 // indirect
)

我們看到 Go 標準庫依賴特定版本的 golang.org/x/crypto 模塊。

與標準庫不同的是,如果你要使用 golang.org/x/crypto 模塊中的密碼學包,你就需要單獨引入項目依賴。此外,golang.org/x 下的包通常被視爲實驗性或擴展包,因此它們並不嚴格遵循 Go1 兼容性承諾 [7]。換句話說,這些包在 API 穩定性上沒有與標準庫相同的保證,可能會有非向後兼容的更改。

綜上,我們看到 Go 標準庫 crypto 與 golang.org/x/crypto 的這種分離策略,允許 Go 團隊在保持標準庫穩定性的同時,也能夠靈活地引入新的密碼學算法和技術。

接下來,我們來看看 crypto 庫的整體結構設計原則,這些原則對理解整個 crypto 庫大有裨益。

  1. 整體結構設計原則

Go 的 crypto 庫整體上的結構設計遵循了幾個原則:

2.1 統一接口和類型抽象

首先是統一接口和類型抽象,這在最頂層的 crypto 包中就能充分體現。

crypto 包定義了一個 Hash 類型和一個創建具體哈希實現的方法。這個設計允許統一管理不同的哈希算法,同時保持了良好的可擴展性:

// $GOROOT/src/crypto/crypto.go

type Hash uint

// New returns a new hash.Hash calculating the given hash function. New panics
// if the hash function is not linked into the binary.
func (h Hash) New() hash.Hash {
    if h > 0 && h < maxHash {
        f := hashes[h]
        if f != nil {
            return f()
        }
    }
    panic("crypto: requested hash function #" + strconv.Itoa(int(h)) + " is unavailable")
}

// HashFunc simply returns the value of h so that [Hash] implements [SignerOpts].
func (h Hash) HashFunc() Hash {
    return h
}

// RegisterHash registers a function that returns a new instance of the given
// hash function. This is intended to be called from the init function in
// packages that implement hash functions.
func RegisterHash(h Hash, f func() hash.Hash) {
    if h >= maxHash {
        panic("crypto: RegisterHash of unknown hash function")
    }
    hashes[h] = f
}

var hashes = make([]func() hash.Hash, maxHash)

Hash 類型作爲一個統一的標識符,用於表示不同的哈希算法。New 方法則 “像一個工廠方法”,用於創建具體的哈希實現。新的哈希算法可以很容易地添加到這個系統中,只需定義一個新的常量並提供相應的實現,並將實現通過 RegisterHash 註冊到 hashes 中即可。下面是一個使用 sha256 算法的示例 (僅做演示,並非慣例寫法):

package main

import (
 "crypto"
 _ "crypto/sha256" // register h256 to hashes
)

func main() {
 ht := crypto.SHA256
 h := ht.New()
 h.Write([]byte("hello world"))
 sum := h.Sum(nil)
 println(sum)
}

注:也許是早期標準庫的設計問題,hash 接口目前沒有放到 crypto 下面,而是在標準庫頂層目錄下。crypto 庫中的 hash 實現通過 New 方法返回真正的 hash.Hash 實現。

crypto 包還定義了幾個關鍵接口,這些接口被各個子包實現,從而實現了高度的可擴展性和互操作性,比如下面的 Signer、SignerOpts、Decrypter 接口:

// Signer is an interface for an opaque private key that can be used for
// signing operations. For example, an RSA key kept in a hardware module.
type Signer interface {
    Public() PublicKey
    Sign(rand io.Reader, digest []byte, opts SignerOpts) (signature []byte, err error)
}

// SignerOpts contains options for signing with a [Signer].
type SignerOpts interface {
    HashFunc() Hash
}

// Decrypter is an interface for an opaque private key that can be used for
// asymmetric decryption operations. An example would be an RSA key
// kept in a hardware module.
type Decrypter interface {
    Public() PublicKey
    Decrypt(rand io.Reader, msg []byte, opts DecrypterOpts) (plaintext []byte, err error)
}

以 Signer 接口爲例,這個 Signer 接口爲不同的簽名算法(如 RSA、ECDSA、Ed25519 等)提供了一個統一的抽象。下面是一個使用統一 Signer 接口但不同 Signer 實現的示例:

func signData(signer crypto.Signer, data []byte) ([]byte, error) {
 hash := crypto.SHA256
 h := hash.New()
 h.Write(data)
 digest := h.Sum(nil)

 return signer.Sign(rand.Reader, digest, hash)
}

func main() {
 rsaKey, _ := rsa.GenerateKey(rand.Reader, 2048)
 signature, _ := signData(rsaKey, []byte("Hello, World!"))
 println(signature)

 ecdsaKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 signature, _ = signData(ecdsaKey, []byte("Hello, World!"))
 println(signature)
}

在這個例子中,我們看到了如何使用相同的 signData 函數來處理不同類型的簽名算法,這體現了統一接口帶來的靈活性和一致性。

在 crypto 目錄下的各個子包中,上述原則也有很好的體現,比如 cipher 包就定義了 Block、Stream 等接口,然後 aes、des 等對稱加密包也都提供了創建實現了這些接口的類型的函數,比如 aes.NewCipher 以及 des.NewCipher 等。

2.2 模塊化

每個子包專注於特定的功能,這種模塊化設計使得每個包都相對獨立,便於維護和使用。以 aes 包和 des 包爲例:

// crypto/aes/cipher.go
func NewCipher(key []byte) (cipher.Block, error) {
    // AES specific implementation
}

// crypto/des/cipher.go
func NewCipher(key []byte) (cipher.Block, error) {
    // DES specific implementation
}

這兩個包都實現了相同的 NewCipher 函數,但內部實現完全不同,專注於各自的加密算法。

2.3 易用性與靈活性的平衡

Go crypto 庫中的很多包既提供了可以滿足大多數常見用例的需求、易用性很好的高級 API,同時也提供了更靈活的低級 API,允許開發者在需要時進行更精細的控制或自定義實現。

讓我們以 SHA256 哈希函數爲例來說明這一點:

// 高級API
func highLevelAPI(data []byte) [32]byte {
 return sha256.Sum256(data)
}

// 低級API
func lowLevelAPI(data []byte) [32]byte {
 h := sha256.New()
 h.Write(data)
 return *(*[32]byte)(h.Sum(nil))
}

func main() {
 fmt.Println(lowLevelAPI([]byte("hello world")))
 fmt.Println(highLevelAPI([]byte("hello world")))
}

在這個例子中,sha256.Sum256 是高級 API,而 lowLevelAPI 中使用的那套邏輯則是對低級 API 的組合以實現 Sum256 功能。

2.4 可擴展性

基於 “統一接口和類型抽象” 原則設計的 crypto 庫可以讓用戶輕鬆地集成自己的實現或第三方庫,這種可擴展性便於我們添加新的算法或功能,而不影響現有結構。 比如,我們可以像這下面這樣實現自定義的 cipher.Block:

type MyCustomCipher struct {
    // ...
}

func (c *MyCustomCipher) BlockSize() int {
    // ...
}

func (c *MyCustomCipher) Encrypt(dst, src []byte) {
    // ...
}

func (c *MyCustomCipher) Decrypt(dst, src []byte) {
    // ...
}

之後,這個自定義的 cipher.Block 實現便可以直接用在標準庫提供的分組密碼模式中。

作爲 crypto 庫的擴展和實驗庫,golang.org/x/crypto 也遵循了與標準庫 crypto 相關包一致的設計原則,這裏就不舉例說明了。

有了上述對 crypto 庫的整體設計原則的認知後,我們再來看一下 Go 標準庫 crypto 目錄下的子包結構,瞭解了這個結果,你就會像擁有了 crypto 庫的 “導航”,可以順利方便地找到你想要的密碼學包了。

  1. 子包結構概覽

衆所周知,Go 標準庫 crypto 目錄下不僅有 crypto 包,還有衆多種類的密碼學包,下面這張示意圖對這些包進行了簡單分類:

下面我會按照圖中的類別對各個包做簡單介紹,包括功能、用途、簡單的示例以及是否推薦使用。密碼學一直在發展,很多算法因爲不再 “牢不可破” 而逐漸不再被推薦使用。但 Go 爲了保證 Go1 兼容性,這些包依賴留在了 Go 標準庫中。

我們自上而下,先從哈希函數開始。

3.1 哈希函數

3.1.1 md5

import "crypto/md5"
hash := md5.Sum([]byte("hello world"))

3.1.2 sha1

import "crypto/sha1"
hash := sha1.Sum([]byte("hello world"))

3.1.3 sha256

import "crypto/sha256"
hash := sha256.Sum256([]byte("hello world"))

3.1.4 sha512

import "crypto/sha512"
hash := sha512.Sum512([]byte("hello world"))

3.2 加密和解密

3.2.1 aes

import "crypto/aes"
key := []byte("example key 1234") // 16字節的key
block, _ := aes.NewCipher(key)

3.2.2 des

import "crypto/des"
key := []byte("example!") // 8字節的key
block, _ := des.NewCipher(key)

3.2.3 rc4

import "crypto/rc4"
key := []byte("secret key")
cipher, _ := rc4.NewCipher(key)

3.2.4 cipher

import "crypto/cipher"
// 使用AES-GCM模式
block, _ := aes.NewCipher(key)
aesgcm, _ := cipher.NewGCM(block)

3.3 簽名和驗證

3.3.1 dsa

import "crypto/dsa"
var privateKey dsa.PrivateKey
dsa.GenerateKey(&privateKey, rand.Reader)

3.3.2 ecdsa

import "crypto/ecdsa"
privateKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)

3.3.3 ed25519

import "crypto/ed25519"
publicKey, privateKey, _ := ed25519.GenerateKey(rand.Reader)

3.3.4 rsa

import "crypto/rsa"
privateKey, _ := rsa.GenerateKey(rand.Reader, 2048)

3.4 密鑰交換

3.4.1 ecdh

import "crypto/ecdh"
curve := ecdh.P256()
privateKey, _ := curve.GenerateKey(rand.Reader)

3.5 安全隨機數生成

3.5.1 rand

import "crypto/rand"
randomBytes := make([]byte, 32)
rand.Read(randomBytes)

3.6 證書和協議

3.6.1 tls

import "crypto/tls"
config := &tls.Config{MinVersion: tls.VersionTLS12}

3.6.2 x509

import "crypto/x509"
cert, _ := x509.ParseCertificate(certDER)

3.7. 輔助功能

3.7.1 elliptic

import "crypto/elliptic"
curve := elliptic.P256()

3.7.2 hmac

import "crypto/hmac"
h := hmac.New(sha256.New, []byte("secret key"))
h.Write([]byte("message"))

3.7.3 subtle

import "crypto/subtle"
equal := subtle.ConstantTimeCompare([]byte("a")[]byte("b"))

結合上面兩節,我們看到 crypto 庫的內部依賴結構設計得非常巧妙,以最小化耦合。大多數子包依賴於 crypto 基礎包中定義的接口和類型。crypto/subtle 包提供了一些底層的輔助函數,被多個其他包使用。每個加密算法包(如 crypto/aes,crypto/rsa)通常是獨立的,減少了包間的直接依賴。一些高級功能包(如 crypto/tls)會依賴多個基礎算法包。大多數需要隨機性的包都依賴 crypto/rand 作爲安全隨機源。

此外,crypto 庫與其他 Go 標準庫可緊密集成,包括:

接下來,再來看看 golang.org/x/crypto 擴展庫,我們同樣借鑑上面的分類和介紹方法,看看 crypto 擴展庫中都有哪些有價值的實用密碼學包。

4 golang.org/x/crypto 擴展庫

我們還是從哈希函數開始介紹。

4.1 哈希函數

4.1.1 blake2b 和 blake2s

import "golang.org/x/crypto/blake2b"
hash := blake2b.Sum256([]byte("hello world"))

4.1.2 md4

import "golang.org/x/crypto/md4"
h := md4.New()
h.Write([]byte("hello world"))
hash := h.Sum(nil)

4.1.3 ripemd160

import "golang.org/x/crypto/ripemd160"
h := ripemd160.New()
h.Write([]byte("hello world"))
hash := h.Sum(nil)

4.1.4 sha3

import "golang.org/x/crypto/sha3"
hash := sha3.Sum256([]byte("hello world"))

4.2 加密和解密

4.2.1 blowfish

import "golang.org/x/crypto/blowfish"
cipher, _ := blowfish.NewCipher([]byte("key"))

4.2.2 cast5

import "golang.org/x/crypto/cast5"
cipher, _ := cast5.NewCipher([]byte("16-byte key"))

4.2.3 chacha20

import "golang.org/x/crypto/chacha20"
cipher, _ := chacha20.NewUnauthenticatedCipher(key, nonce)

4.2.4 salsa20

import "golang.org/x/crypto/salsa20"
salsa20.XORKeyStream(dst, src, nonce, key)

4.2.4 tea

import "golang.org/x/crypto/tea"
cipher, _ := tea.NewCipher([]byte("16-byte key"))

4.2.5 twofish

import "golang.org/x/crypto/twofish"
cipher, _ := twofish.NewCipher([]byte("16, 24, or 32 byte key"))

4.2.6 xtea

import "golang.org/x/crypto/xtea"
cipher, _ := xtea.NewCipher([]byte("16-byte key"))

4.2.7 xts

import "golang.org/x/crypto/xts"
cipher, _ := xts.NewCipher(aes.NewCipher, []byte("32-byte key"))

4.3 認證加密

4.3.1 chacha20poly1305

import "golang.org/x/crypto/chacha20poly1305"
aead, _ := chacha20poly1305.New(key)

4.4 密鑰派生和密碼哈希

4.4.1 argon2

import "golang.org/x/crypto/argon2"
hash := argon2.IDKey([]byte("password"), salt, 1, 64*1024, 4, 32)

4.4.2 bcrypt

import "golang.org/x/crypto/bcrypt"
hash, _ := bcrypt.GenerateFromPassword([]byte("password"), bcrypt.DefaultCost)

4.4.3 hkdf

import "golang.org/x/crypto/hkdf"
hkdf := hkdf.New(sha256.New, secret, salt, info)

4.4.4 pbkdf2

import "golang.org/x/crypto/pbkdf2"
dk := pbkdf2.Key([]byte("password"), salt, 4096, 32, sha1.New)

4.4.5 scrypt

import "golang.org/x/crypto/scrypt"
dk, _ := scrypt.Key([]byte("password"), salt, 32768, 8, 1, 32)

4.5 公鑰密碼學

4.5.1 bn256

import "golang.org/x/crypto/bn256"
g1 := new(bn256.G1).ScalarBaseMult(k)

4.5.2 nacl

import "golang.org/x/crypto/nacl/box"
publicKey, privateKey, _ := box.GenerateKey(rand.Reader)

4.6 協議和標準

4.6.1 acme

4.6.2 ocsp

import "golang.org/x/crypto/ocsp"
resp, _ := ocsp.ParseResponse(responseBytes, issuer)

4.6.3 openpgp

import "golang.org/x/crypto/openpgp"
entity, _ := openpgp.NewEntity("name""comment""email", nil)

4.6.4 otr

4.6.5 pkcs12

import "golang.org/x/crypto/pkcs12"
blocks, _ := pkcs12.ToPEM(pfxData, "password")

4.6.6 ssh

import "golang.org/x/crypto/ssh"
config := &ssh.ClientConfig{User: "user", Auth: []ssh.AuthMethod{ssh.Password("password")}}

4.7 其他

4.7.1 poly1305

import "golang.org/x/crypto/poly1305"
var key [32]byte
var out [16]byte
poly1305.Sum(&out, msg, &key)
  1. Go 密碼學庫的現狀與後續方向

Gotime 在 2023 年末和今年年初對 Go 密碼學庫的前負責人 Filippo Valsorda 和現負責人 Roland Shoemaker 進行了三期訪談 (見參考資料),通過這三次訪談我們大約可以梳理出 Go 密碼學庫的現狀與後續方向:

總的來說,Go 密碼學庫 (包括 golang.org/x/crypto) 正在積極發展和改進,同時也在爲後量子密碼學時代做準備。雖然後量子算法的完全集成和廣泛應用還需要一段時間,但 Go 團隊正在積極跟進這一領域的發展,努力在保持兼容性的同時提升安全性和性能。

  1. 小結

在這篇文章中,我們對 Go 生態中密碼學功能的核心:Go crypto 庫 (包括標準庫 crypto 相關包以及 golang.org/x/crypto 相關包) 進行了全面的瞭解,包括兩者的關係、整體結構設計原則以及每個庫的子包概覽。

我們看到:Go crypto 庫以其安全性、全面性、易用性、高性能以及與 Go 生態系統的高度集成而著稱。它不僅涵蓋了廣泛的加密算法和協議,還通過統一且直觀的 API 降低了使用門檻。

相信通過上述的瞭解,大家都已經理解了 Go crypto 庫的架構與設計思想,並建立起了一張 crypto 庫的 “地圖”。按照這幅圖的指示,大家可以根據具體需求,快速找到合適的密碼學包,並利用這些包構建安全可靠的 Go 應用。

  1. 參考資料


參考資料

[1] 

Go crypto 庫: https://pkg.go.dev/crypto

[2] 

Adam Langley(agl): https://www.imperialviolet.org/

[3] 

Filippo Valsorda: https://blog.filippo.io/

[4] 

Roland Shoemaker: https://github.com/rolandshoemaker

[5] 

標準庫 context: https://tonybai.com/2022/11/08/understand-go-context-by-example

[6] 

Go 1.23.0: https://tonybai.com/2024/08/19/some-changes-in-go-1-23/

[7] 

Go1 兼容性承諾: https://go.dev/doc/go1compat

[8] 

密碼存儲: https://tonybai.com/2023/10/25/understand-password-storage-of-web-app-by-example/

[9] 

抵抗硬件暴力破解: https://tonybai.com/2023/10/25/understand-password-storage-of-web-app-by-example/

[10] 

godebug 機制: https://tonybai.com/2024/10/11/go-evolution-dual-insurance-goexperiment-godebug

[11] 

不破壞兼容性: https://tonybai.com/2023/09/10/understand-go-forward-compatibility-and-toolchain-rule

[12] 

後量子密碼算法: https://en.wikipedia.org/wiki/Post-quantum_cryptography

[13] 

What's new in Go's cryptography libraries: Part 1: https://changelog.com/gotime/295

[14] 

What's new in Go's cryptography libraries: Part 2: https://changelog.com/gotime/298

[15] 

What's new in Go's cryptography libraries: Part 3: https://changelog.com/gotime/313?ref=tonybai.com

[16] 

Crypto 101: A Brief Tour of Practical Crypto in Golang: https://cyberspy.io/articles/crypto101/

[17] 

Go for Crypto Developers: https://www.youtube.com/watch?v=2r_KMzXB74w


Gopher Daily(Gopher 每日新聞) - https://gopherdaily.tonybai.com

我的聯繫方式:

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/60eD5gN_QApnGkwITZRMpw