上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
crypto 1.18
加密算法相关知识。
对称加密
对称加密(私钥加密),指加密和解密使用相同密钥的加密算法。
优点在于,加解密的高速度,以及使用长密钥时的难破解性。
不足之处,两方都使用同样钥匙,安全性得不到保证。
常见的有:DES,3DES(Triple DES),Blowfish,RC5,IDEA 等。
美国国家标准局倡导的 AES 即将作为新标准取代 DES。
加密模式 :
ECB
: 电子密码本模式CBC
: 密码分组链接模式CFB
: 加密反馈模式OFB
: 输出反馈模式
电子密码本(Electronic codebook,ECB),将消息分成数个块,并对每个块进行独立加密。
缺点在于同样的明文会被加密成相同的密文,不能很好的隐藏数据模式。
密码分组链接(Cipher-block chaining,CBC),每个明文块先与前一密文块进行异或后,再进行加密。
每个密文块都依赖于前面所有明文块。为保证每条消息的唯一性,在第一个块中需要使用初始化向量。
缺点在于,加密过程是串行的,无法被并行化,而且消息必须被填充到块大小的整数倍。
非对称加密
非对称加密算法需两个密钥:公钥(public key)和私钥(private key)。
公钥与私钥是一对。如果用公钥加密,只有用对应的私钥才能解密。
机密信息交换基本过程:
- 甲方生成一对密钥,分发公钥。
- 乙方用公钥加密,发送密文给甲方。
- 甲方用私钥解密。
保密性比较好,消除了最终用户交换密钥的需要。
算法复杂,安全性依赖于算法与密钥。也因为复杂,使得速度没有对称算法快。
常见的有:RSA、Elgamal、Rabin、ECC 等。
摘要算法
加密过程不需要密钥,且加密数据无法解密。
只有输入相同明文,经相同算法才能得到相同密文。
不存在密钥管理与分发问题,适合于分布式网络上使用。
无论输入消息有多长,计算出的摘要的长度是固定的。
常见的有:MD5、SHA-1 等。
数字证书
数字证书是一个经 证书授权中心 ( CA ) 数字签名,包含 公开密钥 和 拥有者信息 的文件。
最简单的证书,包含公钥、名称,及授权中心的数字签名。
数字证书还有个重要特征,就是只在特定的时间段内有效。
数字证书是权威性电子文档,可由权威公正的第三方机构(CA)签发的证书,也可以由企业级 CA 系统进行签发。CA 有自己的密钥对,并使用其对证书进行签名。用户使用 CA 公钥比对签名即可确认整数的合法性。
颁发过程:
- 用户生成密钥对,将公钥及个人信息发给认证中心。
- 认证中心核实身份,确认请求确实由该用户发送。
- 发给用户数字证书,内含用户信息和公钥,附有认证中心签名信息。
数字证书采用公钥体制,即利用配对的密钥进行加解密。
用户持有私钥,用它进行解密和签名。证书所含公钥为外部共享,用于加密和验证签名。
X.509
数字证书格式遵循 X.509 标准,由国际电信联盟(ITU-T)制定。
-
PEM
:文本格式。 DER
:二进制格式。
# 自签名证书 # 1. 生成私钥。 $ openssl genrsa -out my.key 1024 # 2. 生成证书申请文件(csr)。 $ openssl req -new -key my.key -out my.csr # 3. 用私钥对证书申请进行签名,生成证书。 $ openssl x509 -req -in my.csr -out my.crt -signkey my.key -days 365
aes
高级加密标准 (Advanced Encryption Standard, AES),美国政府采用的区块加密标准。
用来替代 DES,已被广泛使用。由美国国家标准与技术研究院(NIST)于 2001 年发布。
+-----+ +-----+ +-----+ | aes | | des | | rc4 | +-----+ +-----+ +-----+ | | | +----------+----------+ | NewCipher | +--------------+ | cipher.Block | +--------------+ | v +-------+-------+ | | NewCBCEncrypter NewCBCDecrypter | | +-------+-------+ | v +------------------+ | cipher.BlockMode | +------------------+
密钥和明文都要按 BlockSize
对齐,加解密向量 iv
需一致。
package main import ( "crypto/aes" "crypto/cipher" "crypto/rand" "bytes" "log" ) func round(d []byte) []byte { n := len(d) a := aes.BlockSize if n % a != 0 { n += a - (n % a) tmp := make([]byte, n) copy(tmp, d) d = tmp } return d } func test(key, data []byte) []byte { // block key = round(key) block, err := aes.NewCipher(key) if err != nil { log.Fatalln(err) } // enc src := round(data) enc := make([]byte, len(src)) mode := cipher.NewCBCEncrypter(block, key[:aes.BlockSize]) mode.CryptBlocks(enc, src) // dec dec := make([]byte, len(enc)) mode = cipher.NewCBCDecrypter(block, key[:aes.BlockSize]) mode.CryptBlocks(dec, enc) return dec } func main() { // 用随机数据进行测试。 for i := 10; i < 10000; i++ { key := make([]byte, 3) data := make([]byte, i) rand.Read(key) rand.Read(data) r := test(key, data) if !bytes.Equal(r[:i], data) { log.Fatalln(i) } } }
对齐算法:
// round n up to a multiple of a. // a must be a power of 2. func round1(n, a int) int { return (n + a - 1) &^ (a - 1) } func round2(n, a int) int { if n % a != 0 { n += a - (n % a) } return n }
rsa
RSA 公开密钥密码体制。
所谓 “公开密钥密码体制”, 就是加解密使用不同密钥,是一种 “试图通过加密密钥(已知)推导出解密密钥,在计算上不可行” 密码体制。
- 加密算法(E)和解密算法(D)都是公开的。
- 加密密钥(公钥,PK)是公开信息。
- 解密密钥(私钥,SK)需要保密。
- 不能通过 PK 计算出 SK。
基于这种理论,1978 年出现了著名的 RSA 算法。它生成一对 RSA 密钥,其中保密密钥(私钥,SK)由用户保存,公钥对外公开,甚至可在网络服务器中注册。
为提高强度,密钥至少 500 位长,推荐 1024 位,但也使得计算量很大。为减少计算量,传送信息时,常采用传统加密方法与公开密钥加密方法相结合的方式。
- 信息采用 DES 或 AES 对称加密。
- 用 RSA 加密对称密钥和信息摘要。
- 收到信息,用配对 RSA 密钥解密,核对摘要。
操作方式
RSA 算法能同时用于加密和数字签名。
- 公钥加密,私钥解密。
- 私钥签名,公钥验证。
填充模式
三种填充模式:RAW, PKCS 和 OAEP。
相同密钥长度, 密文比明文长。
从熵的角度,OAEP 模式引入更多熵,比 PKCS 更安全。
明文长度受填充模式限制,加密返回块长度等于秘钥长度。
假设秘钥 1024 bit (128 byte),而明文长度小于 128 字节:
RAW
:在明文前填充零,解密后明文包括填充零。PKCS1
: 在明文中随机填充数据,导致同一明文每次加密结果都不同。
用相同方式解密,返回明文与加密前完全相同,无填充。OAEP
: 是 PKCS#1 推出的新填充方式,安全性最高。
# 明文 (in), 加密块 (out)。 PKCS1: in.len < (key_bits / 8) - 11 len(out) == len(key) OAEP: in.len <= (key_bits / 8) - 2 * (hash_bits / 8) - 2 len(out) == len(key)
加解密
注意明文长度不要超过限制。
// PKCS1 package main import ( "bytes" "crypto/rand" "crypto/rsa" "log" ) const ( keybits = 1024 maxsize = keybits/8 - 11 // 117 ) func test(data []byte) []byte { sk, _ := rsa.GenerateKey(rand.Reader, keybits) pk := &sk.PublicKey enc, err := rsa.EncryptPKCS1v15(rand.Reader, pk, data) if err != nil { log.Fatalln(err, len(data)) } dec, err := rsa.DecryptPKCS1v15(rand.Reader, sk, enc) if err != nil { log.Fatalln(err) } return dec } func main() { log.SetFlags(log.Ltime | log.Lshortfile) for i := 1; i < maxsize; i++ { data := make([]byte, i) rand.Read(data) r := test(data) if !bytes.Equal(r, data) { log.Fatalln(i) } } }
// OAEP package main import ( "bytes" "crypto/rand" "crypto/rsa" "crypto/sha512" "log" ) const ( keybits = 2048 hashbits = 512 maxsize = (keybits/8) - 2*(hashbits/8) - 2 // 126 ) func test(data []byte) []byte { sk, _ := rsa.GenerateKey(rand.Reader, keybits) pk := &sk.PublicKey hash := sha512.New() random := rand.Reader enc, err := rsa.EncryptOAEP(hash, random, pk, data, nil) if err != nil { log.Fatalln(err, len(data)) } dec, err := rsa.DecryptOAEP(hash, random, sk, enc, nil) if err != nil { log.Fatalln(err) } return dec } func main() { log.SetFlags(log.Ltime | log.Lshortfile) for i := 1; i <= maxsize; i++ { data := make([]byte, i) rand.Read(data) r := test(data) if !bytes.Equal(r, data) { log.Fatalln(i) } } }
签名
使用私钥对摘要进行签名,公钥验证。
用
encoding/hex
对摘要和签名进行十六进制转码。
package main import ( "crypto" "crypto/rand" "crypto/rsa" "crypto/sha512" "log" "fmt" ) const ( keybits = 1024 maxsize = 1024/8-11 ) func main() { sk, _ := rsa.GenerateKey(rand.Reader, keybits) pk := &sk.PublicKey // 数据 data := make([]byte, maxsize) rand.Read(data) // 摘要 hashed := sha512.Sum512(data) // 签名 hash := crypto.SHA512 // uint sign, err := rsa.SignPKCS1v15(rand.Reader, sk, hash, hashed[:]) if err != nil { log.Fatalln(err) } // 验证 if err := rsa.VerifyPKCS1v15(pk, hash, hashed[:], sign); err != nil { log.Fatalln(err) } fmt.Printf("%x\n%x\n", hashed, sign) }
密钥存储
标准库 x509
和 pem
提供了序列化和编码函数。
package main import ( "crypto/rsa" "crypto/x509" "crypto/rand" "encoding/pem" "bytes" "log" ) func marshal() (bsk, bpk []byte) { sk, _ := rsa.GenerateKey(rand.Reader, 1024) pk := &sk.PublicKey // 序列化。 bsk = x509.MarshalPKCS1PrivateKey(sk) // []byte bpk = x509.MarshalPKCS1PublicKey(pk) // 反序列化。 sk2, _ := x509.ParsePKCS1PrivateKey(bsk) // *rsa.PrivateKey pk2, _ := x509.ParsePKCS1PublicKey(bpk) // *rsa.PublicKey if !sk.Equal(sk2) || !pk.Equal(pk2) { log.Fatal() } return } func encode(sk, pk []byte) (string, string) { // PEM 文本格式编码。 msk := pem.EncodeToMemory(&pem.Block{ Type: "RSA PRIVATE KEY", Bytes: sk, }); // []byte mpk := pem.EncodeToMemory(&pem.Block{ Type: "PUBLIC KEY", Bytes: pk, }); // 解码。 bsk, _ := pem.Decode(msk) bpk, _ := pem.Decode(mpk) if !bytes.Equal(sk, bsk.Bytes) || !bytes.Equal(pk, bpk.Bytes) { log.Fatal() } return string(msk), string(mpk) } func main() { bsk, bpk := marshal() psk, ppk := encode(bsk, bpk) println(psk) println(ppk) } /* -----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQDLm+EYUavvChhoCiolIuIdXJ9Dy70A9qwuoYlVgES2ascvCN+t ty/BXdXcR57Nsn+WcNIf9cViZXhXYGqCs1inJkOm0Yn4K1V0m2SnRYDClQUolgJW lES2zDI3J9OkSUlvARhhgGb0Su5Cob6RCgNO4ZPhs/tdFUbsIb0r3PuREQIDAQAB AoGAelOGoyO+VnkMPMWOmI0iqF1TWln3jbKls8ZdSj8sCSSR0tJUAHxEdro3mIZ3 BEIvoWTa+VShaAJqkKRemXPC2GV9AaCPwV4soyX2dcgRv0Gq+OMc4QLv+/8c7oLF 9oCXSa1TYlGKKTNYwr5LtL0w/Wy2Ik54isGWQfnwIBuJsAECQQDMOqsDq1f4GozJ cNMTpl3le0xHmaVLxNh1ZIazCc2Ji+/jKi5kN+HJwfY45P+yNI2IWgbeluPDawiw oH/FBMqBAkEA/zj1lAwMJ9ocou8wND+x6tVAVCUgIFM/VozlL/Uyf4RsJAxYFge0 s1PraNjR36GJv1u/kwReugyniI6BLynekQJBAKB0j5wIaMsTAP3bWNsdYLRFlP7E JmxLMc25t2K/Fu80NLsDjwNKLGk5rNuyf3phc7lnEfKcFkKYu3EaCIigZQECQA0X vPFlVNJOjB2Hq69ifRwQ6IXoiade3ebwv2kgaQDFqE6JG5O1vX1dlrwAM3QHc2uP p1pBZSKiN4330YS73yECQEABGxK+JHdAeXYmxvRZoZjZY+RoniokmbIM4Wk0OEOJ GO4hrVPn0fzz77ZQx/5Y64drb4w3csClt43N7g7pRo8= -----END RSA PRIVATE KEY----- -----BEGIN PUBLIC KEY----- MIGJAoGBAMub4RhRq+8KGGgKKiUi4h1cn0PLvQD2rC6hiVWARLZqxy8I3623L8Fd 1dxHns2yf5Zw0h/1xWJleFdgaoKzWKcmQ6bRifgrVXSbZKdFgMKVBSiWAlaURLbM Mjcn06RJSW8BGGGAZvRK7kKhvpEKA07hk+Gz+10VRuwhvSvc+5ERAgMBAAE= -----END PUBLIC KEY----- */
rand
加密安全(cryptographically secure)随机数生成器。
// crypto/rand/rand.go // Reader is a global, shared instance of a cryptographically // secure random number generator. // // On Linux, FreeBSD, Dragonfly and Solaris, Reader uses getrandom(2) if // available, /dev/urandom otherwise. // On OpenBSD and macOS, Reader uses getentropy(2). // On other Unix-like systems, Reader reads from /dev/urandom. // On Windows systems, Reader uses the RtlGenRandom API. // On Wasm, Reader uses the Web Crypto API. var Reader io.Reader // Read is a helper function that calls Reader.Read using io.ReadFull. // On return, n == len(b) if and only if err == nil. func Read(b []byte) (n int, err error) { return io.ReadFull(Reader, b) }
// crypto/rand/rand_unix.go const urandomDevice = "/dev/urandom" // Easy implementation: read from /dev/urandom. // This is sufficient on Linux, OS X, and FreeBSD. func init() { if runtime.GOOS == "plan9" { Reader = newReader(nil) } else { Reader = &devReader{name: urandomDevice} } }
类 Unix 系统的随机数据可从两个特殊文件中产生: /dev/urandom
, /dev/random
。其原理是利用当前系统的熵池来计算出一定数量的随机比特,然后将这些比特作为字节流返回。
熵指的是一个系统的混乱程度,熵池容纳当前系统背景噪音数据。噪音源自设备、网络、内存、文件、进程等。如当前环境噪音变化很小(比如刚开机),所产生的随机数据效果就不好。
为什么区分 urandom
和 random
?后者在熵池空时会阻塞程序,而前者不会。
这意味着 urandom
某些时候可能是伪随机。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论