crypto模块的目的是为了提供通用的加密和哈希算法。用纯JavaScript代码实现这些功能不是不可能,但速度会非常慢。
Nodejs用C/C++实现这些算法后,通过cypto这个模块暴露为JavaScript接口,这样用起来方便,运行速度也快。
哈希函数
- 固定长度输出:不论输入数据的大小,输出的长度是固定的。
- 哈希函数是单向的,所以从哈希值推断出原始数据是非常困难的。
- 不同的输入数据生成相同的哈希值的可能性非常小。
一些在线解密哈希的网站主要使用暴力破解,因为一个字符在固定算法的加密后值是一定的,所以通过暴力对比,得以反向破解。
const crypto = require('crypto');
// 创建哈希算法 md5, sha1等,以 md5 为例:
const hash = crypto.createHash('md5');
// Hmac 也是一种哈希算法,但它还需要一个密钥
const hmac = crypto.createHmac('sha256', 'secret-key');
// update 方法将一段字符进行哈希转换,可任意多次调用update():
hash.update('Hello, world!');
hash.update('Hello, nodejs!');
hmac.update('Hello, nodejs!');
// hex 以十六进制数据的形式进行展示,也可以使用 base64 格式进行展示
console.log(hash.digest('hex'));
console.log(hmac.digest('base64'));
update()
方法默认字符串编码为UTF-8
,也可以传入Buffer。
对称加密
AES是一种常用的对称加密算法,加解密都用同一个密钥。crypto模块提供了AES支持,但是需要自己封装好函数。
const crypto = require("crypto");
// 加密
function encrypt(key, iv, data) {
let decipher = crypto.createCipheriv('aes-128-cbc', key, iv);
// decipher.setAutoPadding(true);
return decipher.update(data, 'binary', 'hex') + decipher.final('hex');
}
// 解密
function decrypt(key, iv, crypted) {
crypted = Buffer.from(crypted, 'hex').toString('binary');
let decipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
return decipher.update(crypted, 'binary', 'utf8') + decipher.final('utf8');
}
// key, iv必须是16个字节
let key = '1234567890123456';
let iv = '1234567890123456';
let data = 'hello world';
let crypted = encrypt(key, iv, data);
console.log("加密结果",crypted);
let decrypted = decrypt(key, iv, crypted);
console.log("解密结果",decrypted);
// 生成一个随机的 16 字节的初始化向量 (IV)
const iv = Buffer.from(crypto.randomBytes(16));
// 生成一个随机的 32 字节的密钥
const key = crypto.randomBytes(32);
需要确保发送者和接收者都安全地共享密钥,否则有风险被未授权的人获取密钥并解密数据。
非对称加密
例如使用 rsa 算法:
const crypto = require('node:crypto')
// 生成 RSA 密钥对
const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
});
// 要加密的数据
const text = 'test';
// 使用公钥进行加密
const encrypted = crypto.publicEncrypt(publicKey, Buffer.from(text, 'utf-8'));
// 使用私钥进行解密
const decrypted = crypto.privateDecrypt(privateKey, encrypted);
console.log(decrypted.toString());
非对称加密使用一对密钥,分别是公钥和私钥。发送者使用接收者的公钥进行加密,而接收者使用自己的私钥进行解密。公钥可以自由分享给任何人,而私钥必须保密。
总结
密钥
密钥是一种参数,它是在明文转换为密文或将密文转换为明文的算法中输入的参数。密钥分为对称密钥与非对称密钥,分别应用在对称加密和非对称加密上。
对称加密
对称加密又叫做私钥加密,即信息的发送方和接收方使用同一个密钥去加密和解密数据。对称加密的特点是算法公开、加密和解密速度快,适合于对大数据量进行加密,常见的对称加密算法有DES、3DES、TDEA、B1 owfish、RC5和IDEA。
非对称加密
非对称加密也叫做公钥加密。非对称加密与对称加密相比,其安全性更好。对称加密的通信双方使用相同的密钥,如果一方的密钥遭泄露,那么整个通信就会被破解。而非对称加密使用一对密钥,即公钥和私钥,且二者成对出现。私钥被自己保存,不能对外泄露。公钥指的是公共的密钥,任何人都可以获得该密钥。用公钥或私钥中的任何一个进行加密,用另一个进行解密。
摘要 / 哈希 / 散列
摘要算法又称哈希/散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用 16 进制的字符串表示),算法不可逆。
bcrypt
https://www.npmjs.com/package/bcrypt 我们项目中经常使用 bcrypt 这个库。
这里要注意一点,sequelize 在验证时,会验证路由中和数据库中的密码匹配,因此我们需要使用 sequelize 中的 setter 来拦截这个操作,当我们验证好当前密码后,在给数据库设置值的时候才进行加密操作。
模型:
// models/User.js
password: {
type: DataTypes.STRING,
set(val) {
const salt = bcrypt.genSaltSync(10);
const hash = bcrypt.hashSync(val, salt);
this.setDataValue('password', hash);
}
}
登录注册:
// 注册接口
router.post('/register', async (req, res) => {
const { username, password } = req.body;
const exists = await User.findOne({ where: { username } });
if (exists) return res.status(400).send('用户已存在');
// 自动触发加密
const user = await User.create({ username, password });
res.status(201).json({ id: user.id });
});
// 登录接口
router.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = await User.findOne({ where: { username } });
if (!user || !bcrypt.compareSync(password, user.password)) {
return res.status(401).send('认证失败');
}
// 生成Token...
});