返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

crypto 1.18

发布于 2024-10-12 19:15:51 字数 11323 浏览 0 评论 0 收藏 0

加密算法相关知识。

对称加密

对称加密(私钥加密),指加密和解密使用相同密钥的加密算法。
优点在于,加解密的高速度,以及使用长密钥时的难破解性。
不足之处,两方都使用同样钥匙,安全性得不到保证。

常见的有: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)
}

密钥存储

标准库 x509pem 提供了序列化和编码函数。

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 。其原理是利用当前系统的熵池来计算出一定数量的随机比特,然后将这些比特作为字节流返回。


熵指的是一个系统的混乱程度,熵池容纳当前系统背景噪音数据。噪音源自设备、网络、内存、文件、进程等。如当前环境噪音变化很小(比如刚开机),所产生的随机数据效果就不好。

为什么区分 urandomrandom ?后者在熵池空时会阻塞程序,而前者不会。
这意味着 urandom 某些时候可能是伪随机。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文