本文从密码学原理出发,系统梳理对称加密、非对称加密、数字签名与单向散列函数的核心机制,辅以精简 Go 代码,助你建立完整的加密知识体系。
一、为什么需要加密?
在开放网络中传输数据,如同在明信片上写信------任何人都能读取。加密的核心目标有三:
- 机密性(Confidentiality):确保只有授权方能够读取数据
- 完整性(Integrity):确保数据在传输过程中未被篡改
- 不可否认性(Non-repudiation):确保发送方无法否认其行为
这三者构成了现代密码学的基石。
二、对称加密:共享密钥的艺术
2.1 核心原理
对称加密的逻辑极其直观:加密方与解密方持有同一把密钥。发送方用密钥将明文转换为密文,接收方用同一把密钥还原。
css
明文 + 密钥 → [加密算法] → 密文
密文 + 密钥 → [解密算法] → 明文
它的优势是速度快、效率高 ,适合加密大量数据。但致命弱点是密钥分发问题------如何安全地把密钥交给对方?如果密钥在传输中被截获,整个加密体系瞬间崩塌。
2.2 块密码与填充机制
AES、DES 都属于块密码(Block Cipher),它们只能处理固定长度的数据块。AES 的块大小为 128 位(16 字节),DES 为 64 位(8 字节)。
如果明文长度不是块大小的整数倍,就必须进行填充(Padding) 。最常用的 PKCS#7 填充原理是:
假设缺 5 个字节,就在末尾填充 5 个
0x05;如果刚好整除,则填充一个完整块的块大小值。
这保证了解密后能通过最后一个字节准确判断原始数据边界。
2.3 工作模式:从 ECB 到 GCM
块密码本身只能加密一个块,工作模式决定了如何串联多个块。
| 模式 | 原理 | 安全性 |
|---|---|---|
| ECB | 每个块独立加密,相同明文产生相同密文 | ❌ 极低,已淘汰 |
| CBC | 前一密文块与当前明文块异或后再加密,需 IV | ✅ 高,但需防篡改 |
| GCM | 在 CTR 计数器模式基础上附加认证标签 | ✅ 极高,推荐 |
CBC 模式 的精髓在于链式依赖 :第 n 块的密文会影响第 n+1 块的加密结果。即使明文中有两个完全相同的块,密文也会截然不同。但它需要一个初始向量(IV),且 IV 无需保密但必须随机。
GCM 模式 是现代首选。它在加密的同时计算一个认证标签(Authentication Tag),解密时先验证标签完整性,再解密密文。这意味着攻击者即使截获密文并篡改,解密方也会立即发现。
go
// Go: AES-GCM 加密(推荐)
func encrypt(plaintext, key []byte) ([]byte, error) {
block, _ := aes.NewCipher(key)
gcm, _ := cipher.NewGCM(block)
nonce := make([]byte, gcm.NonceSize())
rand.Read(nonce)
return gcm.Seal(nonce, nonce, plaintext, nil), nil
}
2.4 DES 的衰落
DES 使用 56 位密钥,在 1999 年已被分布式计算在 22 小时内暴力破解。如今它仅作为密码学教材中的历史注脚存在。
三、非对称加密:公钥与私钥的数学舞蹈
3.1 核心原理
非对称加密使用密钥对:公钥公开分发,私钥严格保密。任何人可用公钥加密,但只有私钥持有者能解密。
css
明文 + 公钥 → [加密] → 密文
密文 + 私钥 → [解密] → 明文
RSA 算法的安全性建立在大整数分解难题之上:将两个大素数相乘很容易,但对其乘积进行质因数分解在计算上不可行。
3.2 密钥交换的优雅解决
非对称加密完美解决了对称加密的密钥分发困境。实际通信流程通常是:
- 客户端生成随机对称密钥
- 用服务器的公钥加密该对称密钥
- 服务器用私钥解密获取对称密钥
- 后续通信全部使用对称加密(因为更快)
这就是 TLS 握手过程的核心逻辑。
3.3 长度限制与混合加密
RSA 的加密长度受密钥长度限制。2048 位 RSA 最多只能加密 245 字节 (减去填充开销)。因此它从不直接加密大数据,而是作为数字信封保护对称密钥。
go
// Go: RSA 加密对称密钥
pub, _ := x509.ParsePKIXPublicKey(block.Bytes)
encryptedKey, _ := rsa.EncryptPKCS1v15(rand.Reader, pub.(*rsa.PublicKey), aesKey)
四、数字签名:身份与完整性的双重证明
4.1 为什么需要签名?
加密解决了"别人看不懂",但没解决"是不是你发的"。
攻击者可以截获密文后原样转发(重放攻击),或者伪造消息。数字签名要回答两个问题:消息确实来自发送方 ,且消息未被篡改。
4.2 签名原理:私钥签,公钥验
签名的过程与加密相反:
- 发送方对消息计算哈希值
- 用私钥对哈希值加密 → 这就是签名
- 将消息与签名一并发送
- 接收方用公钥解密签名,得到哈希 A
- 接收方独立计算消息哈希 B
- 对比 A 与 B,一致则验证通过
css
消息 → [哈希] → 摘要
摘要 + 私钥 → [签名] → 签名值
消息 + 签名值 + 公钥 → [验签] → 通过/失败
由于只有私钥持有者能生成有效签名,这就提供了不可否认性。
4.3 哈希与签名的协同
签名不直接加密原始消息(太长),而是加密其哈希值 。这既保证效率,又利用哈希的雪崩效应------消息哪怕只改一个比特,哈希值也会面目全非,导致验签失败。
go
// Go: RSA 签名与验签
hashed := sha256.Sum256(message)
signature, _ := rsa.SignPKCS1v15(rand.Reader, priv, crypto.SHA256, hashed[:])
// 验签
err := rsa.VerifyPKCS1v15(pub.(*rsa.PublicKey), crypto.SHA256, hashed[:], sig)
五、单向散列函数:数据的指纹
5.1 哈希的本质特性
单向散列函数(如 SHA-256)将任意长度输入映射为固定长度输出,具备三个关键性质:
- 单向性:由哈希值反推原始数据在计算上不可行
- 抗碰撞性:很难找到两个不同输入产生相同哈希
- 雪崩效应:输入微小变化导致输出剧烈变化
5.2 密码存储的正确姿势
用户密码绝不应明文存储。正确流程是:
css
用户密码 + 随机盐值 → [bcrypt/Argon2] → 哈希值存储
bcrypt 会自动处理盐值与迭代次数,抵抗彩虹表攻击。Go 的 golang.org/x/crypto/bcrypt 包一行代码即可实现:
go
hash, _ := bcrypt.GenerateFromPassword([]byte("password"), bcrypt.DefaultCost)
bcrypt.CompareHashAndPassword(hash, []byte("password"))
5.3 HMAC:带密钥的哈希
普通哈希无法验证数据来源。HMAC(Hash-based Message Authentication Code)在哈希过程中混入共享密钥:
scss
HMAC = Hash((Key ⊕ opad) || Hash((Key ⊕ ipad) || Message))
接收方用相同密钥独立计算 HMAC,对比即可同时验证完整性 与真实性。这是 API 接口防篡改的标准方案。
go
// Go: HMAC-SHA256
h := hmac.New(sha256.New, key)
h.Write(data)
signature := hex.EncodeToString(h.Sum(nil))
六、体系全景图
┌─────────────────────────────────────────────────────────┐
│ 加密解密系统 │
├──────────────┬──────────────┬──────────────┬──────────┤
│ 对称加密 │ 非对称加密 │ 数字签名 │ 单向散列 │
├──────────────┼──────────────┼──────────────┼──────────┤
│ AES/DES │ RSA │ RSA+SHA256 │ SHA-256 │
│ 同密钥加解密 │ 公钥加密 │ 私钥签名 │ 指纹 │
│ 速度快 │ 私钥解密 │ 公钥验签 │ 不可逆 │
│ 密钥分发难 │ 解决密钥交换 │ 防抵赖篡改 │ 抗碰撞 │
└──────────────┴──────────────┴──────────────┴──────────┘
七、总结
理解加密系统不能停留在"调包"层面:
- 对称加密(AES-GCM)负责高效数据加密
- 非对称加密(RSA)解决密钥交换与身份标识
- 数字签名基于非对称加密,提供不可否认性
- 单向散列(SHA-256)提供数据指纹,HMAC 提供带密钥的完整性校验
这四者相互配合,才构成了 HTTPS、JWT、区块链等现代安全协议的底层骨架。