加密 API 选用原则
- 优先语言标准库。
- 优先知名且经过审计的库。
- 选用第三方库时,先摸清背景。
基础
- 编码:使用
Buffer
完成各类编码格式转换。 - 随机数
- 各类加密技术的基础。
- 真随机数获取极其困难,常用加密专用伪随机数。
- 不要使用
Math.random
!! - 使用
crypto
模块中的random
函数,如crypto.randomBytes
- Hash
- 单向。
- 输出为定长字符串。
- 相同输入,输出相同。
- 不同输入,输出不同。
- 典型应用
- 数据完整性验证
- 口令哈希
- 派生密钥
- 密钥
- 派生密钥:由主密钥生成的密钥。
- 密钥封装:加密并导出密钥。
- 解封密钥:解密并导入密钥。
- 认证加密:加密且防篡改
- 对称加密
- 加解密使用相同密钥
- 速度快
- 可操作大数据块
- 密钥交换是难题
- 非对称加密
- 公钥 + 私钥
- 加密:公钥加密,私钥解密
- 签名:私钥加密,公钥解密
- 速度慢
- 不适合大数据块
- 典型应用
- 密钥交换
- 数字证书
- 数字签名
- 公钥 + 私钥
- 混合加密
- 以上两种组合
- 非对称加密交换密钥
- 对称加密操作数据
- 以上两种组合
- 加密模式
- 块加密,先分组再加密。
- 永远不要使用 ECB 模式。
- 流加密,处理连续或长度未知的字节流,当明文很小时有优势。
- 块加密,先分组再加密。
Hash
- 不推荐的算法
- MD5
- SHA-1
- PBKDF2
- bcrypt
- 消息摘要:SHA-256 及其它同族
crypto.createHash('sha256')
- 对于大数据采用流模式
- 不要用于敏感数据,如口令。
- 消息认证码(HMAC)
- 结合哈希和密钥(密钥需双方事先协商)
crypto.createHmac('sha256', 'secret')
- 口令哈希和 KDF(Key Derivation Function)
- 特点:
- 自带 salt
- 有意用慢算法抵消硬件升级带来的计算速度提升
- argon2(优先)
- argon2i,抗 side-channel 攻击
- argon2d,抗 GPU 攻击
- argon2id(优先),以上组合
- https://www.npmjs.com/package/argon2
- scrypt
crypto.scrypt
- 特点:
对称加密
- 不推荐的算法
- DES,包括 3DES。
- Blowfish
- TWofish 是其后继,但不流行。
- AES-ECB
- 其他算法的 ECB 模式也不推荐。
- AES
- 典型模式:
- AES-CBC,不需要认证时
crypto.createCipheriv('aes-256-cbc', ...)
crypto.createDecipheriv('aes-256-cbc', ...)
- AES-GCM,需要认证时
crypto.createCipheriv('aes-256-gcm', ...)
crypto.createDecipheriv('aes-256-gcm', ...)
- AES-CBC,不需要认证时
- 对于大数据采用流模式
- 典型模式:
- ChaCha20-Poly1305
- ChaCha20 加密 + Poly1305 哈希
- 等同于 AES-GCM
- 适用于无 AES 硬件加速支持的环境
crypto.createCipheriv('chacha20-poly1305', ...)
crypto.createDecipheriv('chacha20-poly1305', ...)
非对称加密
-
ASN.1 数据结构
-
键编码格式:PEM 格式
-
公钥:SPKI
-
// 导入 crypto.createPrivateKey(fs.readFileSync("public.pem"));
-
-
私钥:
-
PKCS #1(旧,RSA only)
-
PKCS #8(新,面向多种算法)
-
//导入 crypto.createPrivateKey(fs.readFileSync("private.pem"));
-
-
导出:
crypto.KeyObject.export(option)
-
-
RSA
-
key
crypto.generateKeyPair('rsa', ...)
-
加解密
-
padding:加密前给明文加入随机数据以提高 RSA 的抗攻击能力。
- 如无理由,总是使用。
- crypto.constants.RSA_PKCS1_OAEP_PADDING(推荐)
- crypto.constants.RSA_PKCS1_PADDING(不推荐)
-
crypto.publicEncrypt( { key: publicKey, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: "sha256", }, plaintext );
-
crypto.privateDecrypt( { key: privateKey, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: "sha256", }, message );
-
-
数字签名
-
padding:
- crypto.constants.RSA_PKCS1_PADDING(缺省)
- crypto.constants.RSA_PKCS1_PSS_PADDING(新版本,推荐)
-
crypto.sign("sha256", message, { key: privateKey, padding: crypto.constants.RSA_PKCS1_PSS_PADDING, });
-
crypto.verify( "sha256", message, { key: publicKey, padding: crypto.constants.RSA_PKCS1_PSS_PADDING, }, signature );
-
-
-
椭圆曲线
- 与 RSA 同等的安全性
- 密钥更短、更快、消耗资源更少
- 不同于 RSA,它的密钥对不支持非对称加密,即:公钥解密私钥或反之。
- 常用曲线
- P-256:secp256r1 / prime256v1,NIST 标准
- Curve25519
- 密钥交换时,X25519
- 数字签名时,Ed25519
- secp256k1,比特币采用
- 典型应用
- 密钥交换(ECDH)
- 数字签名
- key
crypto.generateKeyPair('ec', ...)
- ECDH
- 不同于 RSA(一方用另一方的公钥加密密钥之后,发给对方解密即可)。
- DH 协议
- Alice 和 Bob 各生成一个密钥对
- 两者交换对方的公钥
- 结合自身的私钥生成公共密钥,即:
shareSecret(A 公钥, B 私钥) = shareSecret(B 公钥, A 私钥)
crypto.diffieHellman({对方公钥,自己私钥})
- 公共密钥的一个参考实现:
crypto.diffieHellman
得到 shareSecret- 生成随机 salt
sha256(shareSecret, salt)
为公共密钥,注:salt 记得随密文传给对方。
- 数字签名
- ECDSA(使用 prime256v1)
crypto.sign('sha256', message, privateKey)
crypto.verify('sha256', message, publicKey, signature)
- EdDSA(使用 Ed25519),因其已经内置数字签名算法,故无需外部配置 hash 函数。
crypto.sign(null, message, privateKey)
crypto.verify(null, message, publicKey, signature)
- ECDSA(使用 prime256v1)
Web Crypto
- 应用于浏览器环境
- 编码
ArrayBuffer
atob
和btoa
TextEncoder
- 随机数:
Window.crypto.getRandomValues
Window.crypto.randomUUID
- 密钥
window.crypto.subtle.generateKey(algorithm, extractable, usages)
window.crypto.subtle.importKey(format, data, algorithm, extractable, usages)
window.crypto.subtle.exportKey(format, key)
window.crypto.subtle.wrapKey
window.crypto.subtle.unwrapKey
window.crypto.subtle.deriveKey
- 哈希
- SHA-256:
window.crypto.subtle.digest('SHA-256', ...)
- HMAC:
window.crypto.subtle.sign("HMAC", key, encoded)
- 口令哈希:https://www.npmjs.com/package/hash-wasm
- SHA-256:
- 加解密(同时适用于对称和非对称)
crypto.subtle.encrypt(algorithm, key, data)
crypto.subtle.decrypt(algorithm, key, data)
- 数字签名
window.crypto.subtle.sign
window.crypto.subtle.verify