本文永久链接 – https://tonybai.com/2024/10/19/go-crypto-package-design-deep-dive
Go号称“开箱即用”,这与其标准库的丰富功能和高质量是分不开的。而在Go标准库中,crypto库(包括crypto包、crypto目录下相关包以及golang.org/x/crypto下的补充包)又是Go社区最值得称道的Go库之一。
crypto库由Go核心团队维护,确保了最高级别的安全标准和及时的漏洞修复,为开发者提供了可靠的安全保障。crypto还涵盖了从基础的对称加密到复杂的非对称加密,以及各种哈希函数和数字签名算法等广泛的加解密算法支持,以满足Go开发者的各种需求为目的,而不是与其他密码学工具包竞争。此外,crypto库还经过精心优化,能够在不同硬件平台上尽可能地保证高效的执行性能。值得一提的是,crypto库还提供了统一的API设计,使得不同加密算法的使用方式保持一致,也降低了开发者的学习成本。
可以说Go crypto库是Go生态中密码学功能的核心,它为Go开发者提供了一套全面、安全、保持现代化、提供安全默认值且易于使用的密码学工具,使得在Go应用程序中实现各种密码学功能需求时变得简单而可靠。
不过要理解并得心应手的使用crypto库中的相关密码学包仍然并非易事,这是因为密码学涉及数学、密码分析、计算机安全等多个学科,概念多,算法也十分复杂,而大多程序员对密码学的了解又多停留在使用层面,缺乏对其原理和底层机制的深入认知,甚至连每个包的用途都不甚了解。这导致很多开发者浏览了crypto相关包之后,甚至不知道该使用哪个包。
所以在这篇文章中,我想为Go开发者建立一张crypto库的“地图”,这张“地图”将帮助我们从宏观角度理解crypto库的结构,帮助大家快速精准选择正确的包。并且通过对crypto相关包设计的理解,轻松掌握crypto相关包的使用模式。
注:Go标准库crypto库的第一任负责人是Adam Langley(agl),他开创了Go crypto库,他在招募和培养了Filippo Valsorda后离开了Go项目,后者成为了Go crypto的负责人。Filippo在Go项目工作若干年后,把负责人交给了Roland Shoemaker,即现任Go团队安全组的负责人。当然Shoemaker也是Filippo招募到Go团队中的。
下面我们首先来看看Go 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一样。
同时golang.org/x/crypto也是Go标准库依赖的为数极少的外部包之一。比如,下面是Go 1.23.0标准库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兼容性承诺。换句话说,这些包在API稳定性上没有与标准库相同的保证,可能会有非向后兼容的更改。
综上,我们看到Go标准库crypto与golang.org/x/crypto的这种分离策略,允许Go团队在保持标准库稳定性的同时,也能够灵活地引入新的密码学算法和技术。
接下来,我们来看看crypto库的整体结构设计原则,这些原则对理解整个crypto库大有裨益。
Go的crypto库整体上的结构设计遵循了几个原则:
首先是统一接口和类型抽象,这在最顶层的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等。
每个子包专注于特定的功能,这种模块化设计使得每个包都相对独立,便于维护和使用。以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函数,但内部实现完全不同,专注于各自的加密算法。
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功能。
基于“统一接口和类型抽象”原则设计的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库的“导航”,可以顺利方便地找到你想要的密码学包了。
众所周知,Go标准库crypto目录下不仅有crypto包,还有众多种类的密码学包,下面这张示意图对这些包进行了简单分类:
下面我会按照图中的类别对各个包做简单介绍,包括功能、用途、简单的示例以及是否推荐使用。密码学一直在发展,很多算法因为不再“牢不可破”而逐渐不再被推荐使用。但Go为了保证Go1兼容性,这些包依赖留在了Go标准库中。
我们自上而下,先从哈希函数开始。
import "crypto/md5"
hash := md5.Sum([]byte("hello world"))
import "crypto/sha1"
hash := sha1.Sum([]byte("hello world"))
import "crypto/sha256"
hash := sha256.Sum256([]byte("hello world"))
import "crypto/sha512"
hash := sha512.Sum512([]byte("hello world"))
import "crypto/aes"
key := []byte("example key 1234") // 16字节的key
block, _ := aes.NewCipher(key)
import "crypto/des"
key := []byte("example!") // 8字节的key
block, _ := des.NewCipher(key)
import "crypto/rc4"
key := []byte("secret key")
cipher, _ := rc4.NewCipher(key)
import "crypto/cipher"
// 使用AES-GCM模式
block, _ := aes.NewCipher(key)
aesgcm, _ := cipher.NewGCM(block)
import "crypto/dsa"
var privateKey dsa.PrivateKey
dsa.GenerateKey(&privateKey, rand.Reader)
import "crypto/ecdsa"
privateKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
import "crypto/ed25519"
publicKey, privateKey, _ := ed25519.GenerateKey(rand.Reader)
import "crypto/rsa"
privateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
import "crypto/ecdh"
curve := ecdh.P256()
privateKey, _ := curve.GenerateKey(rand.Reader)
import "crypto/rand"
randomBytes := make([]byte, 32)
rand.Read(randomBytes)
import "crypto/tls"
config := &tls.Config{MinVersion: tls.VersionTLS12}
import "crypto/x509"
cert, _ := x509.ParseCertificate(certDER)
import "crypto/elliptic"
curve := elliptic.P256()
import "crypto/hmac"
h := hmac.New(sha256.New, []byte("secret key"))
h.Write([]byte("message"))
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扩展库中都有哪些有价值的实用密码学包。
我们还是从哈希函数开始介绍。
import "golang.org/x/crypto/blake2b"
hash := blake2b.Sum256([]byte("hello world"))
import "golang.org/x/crypto/md4"
h := md4.New()
h.Write([]byte("hello world"))
hash := h.Sum(nil)
import "golang.org/x/crypto/ripemd160"
h := ripemd160.New()
h.Write([]byte("hello world"))
hash := h.Sum(nil)
import "golang.org/x/crypto/sha3"
hash := sha3.Sum256([]byte("hello world"))
import "golang.org/x/crypto/blowfish"
cipher, _ := blowfish.NewCipher([]byte("key"))
import "golang.org/x/crypto/cast5"
cipher, _ := cast5.NewCipher([]byte("16-byte key"))
import "golang.org/x/crypto/chacha20"
cipher, _ := chacha20.NewUnauthenticatedCipher(key, nonce)
import "golang.org/x/crypto/salsa20"
salsa20.XORKeyStream(dst, src, nonce, key)
import "golang.org/x/crypto/tea"
cipher, _ := tea.NewCipher([]byte("16-byte key"))
import "golang.org/x/crypto/twofish"
cipher, _ := twofish.NewCipher([]byte("16, 24, or 32 byte key"))
import "golang.org/x/crypto/xtea"
cipher, _ := xtea.NewCipher([]byte("16-byte key"))
import "golang.org/x/crypto/xts"
cipher, _ := xts.NewCipher(aes.NewCipher, []byte("32-byte key"))
import "golang.org/x/crypto/chacha20poly1305"
aead, _ := chacha20poly1305.New(key)
import "golang.org/x/crypto/argon2"
hash := argon2.IDKey([]byte("password"), salt, 1, 64*1024, 4, 32)
import "golang.org/x/crypto/bcrypt"
hash, _ := bcrypt.GenerateFromPassword([]byte("password"), bcrypt.DefaultCost)
import "golang.org/x/crypto/hkdf"
hkdf := hkdf.New(sha256.New, secret, salt, info)
import "golang.org/x/crypto/pbkdf2"
dk := pbkdf2.Key([]byte("password"), salt, 4096, 32, sha1.New)
import "golang.org/x/crypto/scrypt"
dk, _ := scrypt.Key([]byte("password"), salt, 32768, 8, 1, 32)
import "golang.org/x/crypto/bn256"
g1 := new(bn256.G1).ScalarBaseMult(k)
import "golang.org/x/crypto/nacl/box"
publicKey, privateKey, _ := box.GenerateKey(rand.Reader)
import "golang.org/x/crypto/ocsp"
resp, _ := ocsp.ParseResponse(responseBytes, issuer)
import "golang.org/x/crypto/openpgp"
entity, _ := openpgp.NewEntity("name", "comment", "email", nil)
import "golang.org/x/crypto/pkcs12"
blocks, _ := pkcs12.ToPEM(pfxData, "password")
import "golang.org/x/crypto/ssh"
config := &ssh.ClientConfig{User: "user", Auth: []ssh.AuthMethod{ssh.Password("password")}}
import "golang.org/x/crypto/poly1305"
var key [32]byte
var out [16]byte
poly1305.Sum(&out, msg, &key)
Gotime在2023年末和今年年初对Go密码学库的前负责人Filippo Valsorda和现负责人Roland Shoemaker进行了三期访谈(见参考资料),通过这三次访谈我们大约可以梳理出Go密码学库的现状与后续方向:
总的来说,Go密码学库(包括golang.org/x/crypto)正在积极发展和改进,同时也在为后量子密码学时代做准备。虽然后量子算法的完全集成和广泛应用还需要一段时间,但Go团队正在积极跟进这一领域的发展,努力在保持兼容性的同时提升安全性和性能。
在这篇文章中,我们对Go生态中密码学功能的核心:Go crypto库(包括标准库crypto相关包以及golang.org/x/crypto相关包)进行了全面的了解,包括两者的关系、整体结构设计原则以及每个库的子包概览。
我们看到:Go crypto库以其安全性、全面性、易用性、高性能以及与Go生态系统的高度集成而著称。它不仅涵盖了广泛的加密算法和协议,还通过统一且直观的API降低了使用门槛。
相信通过上述的了解,大家都已经理解了Go crypto库的架构与设计思想,并建立起了一张crypto库的“地图”。按照这幅图的指示,大家可以根据具体需求,快速找到合适的密码学包,并利用这些包构建安全可靠的Go应用。
Gopher部落知识星球在2024年将继续致力于打造一个高品质的Go语言学习和交流平台。我们将继续提供优质的Go技术文章首发和阅读体验。同时,我们也会加强代码质量和最佳实践的分享,包括如何编写简洁、可读、可测试的Go代码。此外,我们还会加强星友之间的交流和互动。欢迎大家踊跃提问,分享心得,讨论技术。我会在第一时间进行解答和交流。我衷心希望Gopher部落可以成为大家学习、进步、交流的港湾。让我相聚在Gopher部落,享受coding的快乐! 欢迎大家踊跃加入!
著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。
Gopher Daily(Gopher每日新闻) – https://gopherdaily.tonybai.com
我的联系方式:
商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。
© 2024, bigwhite. 版权所有.