密码学入门:对称加密与非对称加密的区别、哈希函数的作用、数字签名与证书
说实话,很多开发者一听到「密码学」三个字就头大。我当年刚入行时也一样,觉得这东西离自己很远。直到有一次,我负责的一个小项目因为用了不安全的加密方式,用户数据差点泄露——嗯,从那以后我再也不敢轻视密码学了。
这一章,咱们就聊聊密码学里最核心的几个概念。不扯玄乎的理论,只讲你实际开发中一定会用到的东西。
对称加密:一把钥匙开一把锁
对称加密,说白了就是加密和解密用同一个密钥。就像你家的门锁,用同一把钥匙锁门和开门。
最常见的对称加密算法有 AES、DES、3DES。现在业界主流是 AES-256,我建议你直接选这个。
核心特点:
- 速度快,适合加密大量数据
- 密钥管理是个大问题——你得想办法把密钥安全地传给对方
- 密钥长度越长越安全,但性能也会下降
我在项目中遇到过这样一个场景:两个微服务之间需要传输敏感数据。用对称加密确实快,但密钥怎么分发?总不能明文写在配置文件里吧?后来我们用了密钥管理服务(KMS)来解决这个问题。
// Node.js 中使用 AES-256-GCM 加密示例
const crypto = require('crypto');
function encrypt(text, key) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag().toString('hex');
return { iv: iv.toString('hex'), encrypted, authTag };
}
// 解密时需要用同样的 iv 和 authTag
function decrypt(encryptedData, key) {
const decipher = crypto.createDecipheriv(
'aes-256-gcm',
key,
Buffer.from(encryptedData.iv, 'hex')
);
decipher.setAuthTag(Buffer.from(encryptedData.authTag, 'hex'));
let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
注意:千万不要用 ECB 模式!我曾经见过一个老系统用 ECB 加密,结果同样的明文块会产生同样的密文块,攻击者很容易看出规律。一定要用 GCM 或 CBC 模式。
非对称加密:公钥私钥配对用
非对称加密就聪明多了。它有一对密钥:公钥可以公开,私钥自己藏着。公钥加密的数据只能用私钥解密,反过来私钥签名的数据可以用公钥验证。
常见的算法有 RSA、ECC(椭圆曲线)。RSA 用得最广,但 ECC 在同等安全强度下密钥更短、性能更好。我个人习惯,新项目优先考虑 ECC。
| 特性 | 对称加密 | 非对称加密 |
|---|---|---|
| 密钥数量 | 1个 | 2个(公钥+私钥) |
| 速度 | 快 | 慢(慢100-1000倍) |
| 典型用途 | 加密大量数据 | 密钥交换、数字签名 |
| 常见算法 | AES, ChaCha20 | RSA, ECDSA, Ed25519 |
你想想看,如果只用对称加密,你和对方怎么安全地交换密钥?非对称加密正好解决了这个问题。实际应用中,我们通常混合使用:用非对称加密来安全地传输对称密钥,然后用对称加密来加密实际数据。这就是所谓的「混合加密」,HTTPS 就是这么干的。
哈希函数:不可逆的指纹
哈希函数不是加密,它没有密钥,也不可逆。它的作用是把任意长度的数据,变成固定长度的「指纹」。
好的哈希函数有几个特点:
- 单向性:从哈希值推不出原始数据
- 抗碰撞性:很难找到两个不同的输入产生相同的哈希值
- 雪崩效应:输入改一点点,输出完全不一样
常见的哈希算法有 SHA-256、SHA-3、BLAKE2。MD5 和 SHA-1 已经被证明不安全了,千万别用。
避坑指南:我曾经接手过一个项目,密码存储用的是 MD5 加盐。虽然加了盐,但 MD5 的碰撞攻击成本已经很低了。后来我全部迁移到了 bcrypt,它的计算成本可调,能有效抵抗暴力破解。
// 密码哈希的正确姿势(Node.js)
const bcrypt = require('bcrypt');
async function hashPassword(password) {
const saltRounds = 12; // 成本因子,建议 10-12
const hash = await bcrypt.hash(password, saltRounds);
return hash;
}
async function verifyPassword(password, hash) {
return await bcrypt.compare(password, hash);
}
哈希函数在开发中无处不在:密码存储、数据完整性校验、文件去重、区块链……说白了,只要你想验证数据有没有被篡改,哈希就是你的第一选择。
数字签名与证书:信任的基石
数字签名解决的是「谁发的」和「有没有被改过」这两个问题。它的工作流程是这样的:
- 发送方用私钥对数据的哈希值进行签名
- 接收方用发送方的公钥验证签名
- 如果验证通过,说明数据确实是发送方发的,且没有被篡改
但这里有个问题:你怎么确定你拿到的公钥就是对方的?万一中间人给你一个假公钥呢?
这就是证书的用武之地。证书由权威机构(CA)签发,里面包含了公钥和持有者的身份信息。CA 用自己的私钥给证书签名,你的浏览器或操作系统里预装了 CA 的公钥,所以你可以验证证书的真伪。
一句话总结:数字签名证明「谁说了什么」,证书证明「这个公钥属于谁」。
我记得有一次排查 HTTPS 连接问题,发现是证书链不完整导致的。服务器只发了自己的证书,没发中间 CA 的证书,客户端无法验证信任链。嗯,这种问题在配置 Nginx 或 Apache 时特别容易遇到。
// 使用 Node.js 验证 HTTPS 证书链
const https = require('https');
const fs = require('fs');
const options = {
hostname: 'example.com',
port: 443,
path: '/',
// 自定义 CA 证书(可选)
ca: fs.readFileSync('./custom-ca.pem'),
// 要求验证服务器证书
rejectUnauthorized: true
};
const req = https.get(options, (res) => {
console.log('证书验证通过');
});
实际开发中的选择建议
说了这么多,到底该怎么选?我根据自己的经验给你几个建议:
- 数据加密:优先用 AES-256-GCM,性能和安全兼顾
- 密钥交换:用 ECDH(椭圆曲线 Diffie-Hellman),比 RSA 更高效
- 密码存储:用 bcrypt 或 argon2,别自己造轮子
- 数据完整性:用 SHA-256 或 SHA-3
- 数字签名:用 Ed25519 或 ECDSA
重要提醒:永远不要自己实现加密算法!密码学里到处都是坑,一个看似微小的错误就可能导致整个系统被攻破。用成熟的库,比如 OpenSSL、libsodium、Bouncy Castle。
密码学不是魔法,它只是一套经过精心设计的工具。理解这些基础概念后,你会发现很多安全问题的本质其实很简单。下一章,咱们聊聊 HTTPS 和 TLS 握手协议——到时候你会看到,刚才讲的这些概念全都会用上。