crypto 安全算法相关
crypto是NodeJS提供的安全算法模块,它封装了OpenSSL的各种加密功能,包括哈希算法、对称加密、非对称加密、数字签名等。在Web开发中,密码存储、数据加密、身份验证等场景都离不开这个模块。
模块引入
import crypto from "node:crypto";
哈希算法
哈希算法能够将任意长度的数据映射为固定长度的哈希值,常用于密码存储、数据完整性校验等场景。
计算哈希值
import crypto from "node:crypto";
// 创建哈希对象,指定算法(如md5、sha1、sha256、sha512等)
const hash = crypto.createHash("sha256");
// 添加要计算的数据
hash.update("Hello, World!");
// 获取哈希结果,可指定编码格式(hex、base64等)
const result = hash.digest("hex");
console.log(result); // 输出64位十六进制字符串
update()方法可以多次调用以追加数据。
import crypto from "node:crypto";
const hash = crypto.createHash("sha256");
hash.update("Hello, ");
hash.update("World!");
const result = hash.digest("hex");
console.log(result);
计算文件哈希值
计算文件哈希值我们肯定不能将大文件一次性加载到内存,因此需要使用update()追加调用,crypto.createHash()返回的其实是一个双工流(Duplex Stream),在这里可以结合流式API和管线操作可以高效计算大文件的哈希值。
import crypto from "node:crypto";
import fs from "node:fs";
import { pipeline } from "node:stream/promises";
async function getFileHash(filePath, algorithm = "sha256") {
const hash = crypto.createHash(algorithm);
const readStream = fs.createReadStream(filePath);
await pipeline(readStream, hash);
return hash.digest("hex");
}
const fileHash = await getFileHash("./example.txt");
console.log(fileHash);
查看支持的哈希算法
下面例子代码可以查看当前可用的哈希算法。
import crypto from "node:crypto";
const hashes = crypto.getHashes();
console.log(hashes); // ["sha1", "sha256", "sha512", "md5", ...]
HMAC算法
HMAC(Hash-based Message Authentication Code)是一种继续哈希的消息验证码,常用于消息认证和API签名。
import crypto from "node:crypto";
const secret = "my-secret-key";
const hmac = crypto.createHmac("sha256", secret);
hmac.update("Hello, World!");
const result = hmac.digest("hex");
console.log(result);
对称加密
对称加密使用相同的密钥进行加密和解密,相比非对称加密算法加密速度快,适合加密大量数据。
AES加密与解密
import crypto from "node:crypto";
// AES-256-CBC加密
function encrypt(text, key, iv) {
// key需要32字节,iv需要16字节
const cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
let encrypted = cipher.update(text, "utf8", "hex");
encrypted += cipher.final("hex");
return encrypted;
}
// AES-256-CBC解密
function decrypt(encryptedText, key, iv) {
const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
let decrypted = decipher.update(encryptedText, "hex", "utf8");
decrypted += decipher.final("utf8");
return decrypted;
}
// 生成随机密钥和初始化向量
const key = crypto.randomBytes(32); // 256位密钥
const iv = crypto.randomBytes(16); // 128位IV
const originalText = "Hello, World!";
const encrypted = encrypt(originalText, key, iv);
const decrypted = decrypt(encrypted, key, iv);
console.log("原文:", originalText);
console.log("密文:", encrypted);
console.log("解密:", decrypted);
查看支持的对称加密算法
import crypto from "node:crypto";
const ciphers = crypto.getCiphers();
console.log(ciphers);
非对称加密
非对称加密使用公钥加密、私钥解密,加密和解密密钥不同,但速度较慢,通常用于加密少量数据或进行密钥交换。
生成密钥对
同步生成RSA密钥对。
import crypto from "node:crypto";
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
modulusLength: 2048, // 密钥长度
publicKeyEncoding: {
type: "spki",
format: "pem",
},
privateKeyEncoding: {
type: "pkcs8",
format: "pem",
},
});
console.log("公钥:\n", publicKey);
console.log("私钥:\n", privateKey);
异步方式生成密钥对。
import crypto from "node:crypto";
import { promisify } from "node:util";
const generateKeyPair = promisify(crypto.generateKeyPair);
const { publicKey, privateKey } = await generateKeyPair("rsa", {
modulusLength: 2048,
publicKeyEncoding: {
type: "spki",
format: "pem",
},
privateKeyEncoding: {
type: "pkcs8",
format: "pem",
},
});
console.log("公钥:\n", publicKey);
console.log("私钥:\n", privateKey);
RSA加密与解密
下面代码演示了RSA的加密和解密。
import crypto from "node:crypto";
// 公钥加密
function rsaEncrypt(text, publicKey) {
const encrypted = crypto.publicEncrypt(publicKey, Buffer.from(text));
return encrypted.toString("base64");
}
// 私钥解密
function rsaDecrypt(encryptedText, privateKey) {
const decrypted = crypto.privateDecrypt(privateKey, Buffer.from(encryptedText, "base64"));
return decrypted.toString("utf8");
}
// 生成密钥对
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
modulusLength: 2048,
publicKeyEncoding: { type: "spki", format: "pem" },
privateKeyEncoding: { type: "pkcs8", format: "pem" },
});
const originalText = "Hello, RSA!";
const encrypted = rsaEncrypt(originalText, publicKey);
const decrypted = rsaDecrypt(encrypted, privateKey);
console.log("原文:", originalText);
console.log("密文:", encrypted);
console.log("解密:", decrypted);
数字签名
数字签名用于验证数据的完整性和来源,发送方使用私钥签名,接收方使用公钥验证。
import crypto from "node:crypto";
// 生成密钥对
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
modulusLength: 2048,
publicKeyEncoding: { type: "spki", format: "pem" },
privateKeyEncoding: { type: "pkcs8", format: "pem" },
});
const data = "需要签名的数据";
// 创建签名
const sign = crypto.createSign("SHA256");
sign.update(data);
const signature = sign.sign(privateKey, "hex");
console.log("签名:", signature);
// 验证签名
const verify = crypto.createVerify("SHA256");
verify.update(data);
const isValid = verify.verify(publicKey, signature, "hex");
console.log("验证结果:", isValid);
随机数生成
crypto模块提供了密码学安全的随机数生成方法,适用于生成密钥、令牌等安全敏感的场景。
生成随机字节
我们可以同步或异步生成随机字节。
import crypto from "node:crypto";
// 同步生成
const randomBytes = crypto.randomBytes(16);
console.log(randomBytes.toString("hex")); // 32位十六进制字符串
// 异步生成(回调方式)
crypto.randomBytes(16, (err, buf) => {
if (err) throw err;
console.log(buf.toString("hex"));
});
NodeJS现在也支持Promise风格的随机字节异步生成。
import crypto from "node:crypto";
import { promisify } from "node:util";
const randomBytes = promisify(crypto.randomBytes);
const buf = await randomBytes(16);
console.log(buf.toString("hex"));
生成随机UUID
import crypto from "node:crypto";
const uuid = crypto.randomUUID();
console.log(uuid); // 例如: "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed"
生成指定范围的随机整数
下面代码同步生成[min, max)范围内的随机整数。
import crypto from "node:crypto";
function randomInt(min, max) {
return crypto.randomInt(min, max);
}
console.log(randomInt(1, 100)); // 1-99之间的随机数
异步版本代码如下。
import crypto from "node:crypto";
import { promisify } from "node:util";
const randomInt = promisify(crypto.randomInt);
const num = await randomInt(1, 100);
console.log(num);
密码哈希
对于用户密码的存储,目前使用MD5(已被理论证明不安全)、SHA-256都不是最好的方式,建议使用专门的密码哈希函数如scrypt或pbkdf2,它们通过增加计算成本来抵抗暴力破解。
scrypt
import crypto from "node:crypto";
import { promisify } from "node:util";
const scrypt = promisify(crypto.scrypt);
async function hashPassword(password) {
const salt = crypto.randomBytes(16).toString("hex");
const derivedKey = await scrypt(password, salt, 64);
return `${salt}:${derivedKey.toString("hex")}`;
}
async function verifyPassword(password, storedHash) {
const [salt, hash] = storedHash.split(":");
const derivedKey = await scrypt(password, salt, 64);
return derivedKey.toString("hex") === hash;
}
const hashedPassword = await hashPassword("myPassword123");
console.log("哈希结果:", hashedPassword);
const isValid = await verifyPassword("myPassword123", hashedPassword);
console.log("验证结果:", isValid);
pbkdf2
import crypto from "node:crypto";
import { promisify } from "node:util";
const pbkdf2 = promisify(crypto.pbkdf2);
async function hashPassword(password) {
const salt = crypto.randomBytes(16).toString("hex");
const derivedKey = await pbkdf2(password, salt, 100000, 64, "sha512");
return `${salt}:${derivedKey.toString("hex")}`;
}
async function verifyPassword(password, storedHash) {
const [salt, hash] = storedHash.split(":");
const derivedKey = await pbkdf2(password, salt, 100000, 64, "sha512");
return derivedKey.toString("hex") === hash;
}
const hashedPassword = await hashPassword("myPassword123");
console.log("哈希结果:", hashedPassword);
const isValid = await verifyPassword("myPassword123", hashedPassword);
console.log("验证结果:", isValid);
Diffie-Hellman密钥交换
Diffie-Hellman算法允许双方在不安全的信道上协商出一个共享密钥。
import crypto from "node:crypto";
// Tom生成密钥对
const tom = crypto.createDiffieHellman(2048);
const tomPublicKey = tom.generateKeys();
// Jerry使用相同的素数和生成器
const jerry = crypto.createDiffieHellman(tom.getPrime(), tom.getGenerator());
const jerryPublicKey = jerry.generateKeys();
// 双方计算共享密钥
const tomSecret = tom.computeSecret(jerryPublicKey);
const jerrySecret = jerry.computeSecret(tomPublicKey);
// 双方得到相同的共享密钥
console.log("Tom的共享密钥:", tomSecret.toString("hex"));
console.log("Jerry的共享密钥:", jerrySecret.toString("hex"));
console.log("密钥是否相同:", tomSecret.equals(jerrySecret));