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都不是最好的方式,建议使用专门的密码哈希函数如scryptpbkdf2,它们通过增加计算成本来抵抗暴力破解。

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));
作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。