现代 JavaScript 加密技术详解:Web Crypto API 与常见算法实践

华为云 DevUI 开发实战征文活动 10w+人浏览 158人参与

🧑 博主简介CSDN博客专家历代文学网(PC端可以访问:https://literature.sinhy.com/#/?__c=1000,移动端可关注公众号 “ 心海云图 ” 微信小程序搜索“历代文学”)总架构师,16年工作经验,精通Java编程高并发设计分布式系统架构设计Springboot和微服务,熟悉LinuxESXI虚拟化以及云原生Docker和K8s,热衷于探索科技的边界,并将理论知识转化为实际应用。保持对新技术的好奇心,乐于分享所学,希望通过我的实践经历和见解,启发他人的创新思维。在这里,我希望能与志同道合的朋友交流探讨,共同进步,一起在技术的世界里不断学习成长。
🤝商务合作:请搜索或扫码关注微信公众号 “ 心海云图

在这里插入图片描述


在这里插入图片描述

现代 JavaScript 加密技术详解:Web Crypto API 与常见算法实践

关于JavaScript中的加解密及哈希操作,现代JavaScript环境主要通过 Web Cryptography API(浏览器)和 Node.js crypto模块(服务端)来提供支持。下面我将为你梳理它支持的编解码类型、MD5等常见算法的使用方法以及一些重要的实践建议。

这里用一个表格来概括 JavaScript crypto 支持的主要算法类型和常见算法:

算法类型常见算法主要用途
哈希(摘要)MD5, SHA-1, SHA-256, SHA-512生成数据唯一指纹,用于数据完整性校验、密码存储(需加盐)等。
对称加密AES (CBC, GCM, ECB 等模式)加密和解密使用相同密钥,适合加密大量数据,如文件加密、传输数据加密。
非对称加密RSA (OAEP 等模式)使用公钥加密、私钥解密,适用于身份验证和安全传输会话密钥。
其他操作随机数生成 (crypto.getRandomValues)生成密码学安全的随机值,用于创建密钥、初始向量 (IV) 等。

🔐 哈希(摘要)算法

哈希算法能将任意长度的数据映射为固定长度的、唯一的哈希值,这个过程是不可逆的,所以没有“解密”一说。MD5就是一种常见的哈希算法。

认识 MD5
  • 特点:输出固定长度为128位(32字符十六进制字符串),具有雪崩效应(输入微小变化导致输出巨大差异)。
  • 安全性注意MD5 已被证明存在碰撞漏洞(不同数据可能生成相同哈希值),不适用于高安全场景(如证书、金融交易)。对于密码存储和数据校验,更推荐使用 SHA-256 等更安全的算法。
如何使用 MD5

你有多种方式可以在 JavaScript 中计算 MD5 哈希值。

  1. 使用 Web Crypto API(现代浏览器原生支持)
    现代浏览器提供了 crypto.subtle 接口,但它不支持 MD5 算法。对于 SHA 系列算法,以下是使用 Web Crypto API 计算 SHA-256 的例子:

    async function sha256Hash(text) {
      const encoder = new TextEncoder();
      // 将字符串编码为Uint8Array
      const data = encoder.encode(text);
      // 使用Web Crypto API计算SHA-256哈希
      const hashBuffer = await crypto.subtle.digest('SHA-256', data);
      const hashArray = Array.from(new Uint8Array(hashBuffer));
      // 将哈希值转换为十六进制字符串
      const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
      return hashHex;
    }
    
    // 使用示例
    sha256Hash('Hello, World!').then(hash => console.log(hash));
    
  2. 使用 CryptoJS 库(支持MD5及其他多种算法)
    如果你需要计算 MD5,或者希望代码在不同环境下有更好的兼容性,可以使用第三方库 CryptoJS

    • 安装与引入
      可以通过npm安装:npm install crypto-js
      或者在HTML中直接通过CDN引入:

      <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js"></script>
      
    • 计算 MD5 示例

      // 使用CryptoJS
      const md5Hash = CryptoJS.MD5('Hello, World!').toString();
      console.log(md5Hash); // 输出MD5哈希值
      
    • 计算 SHA-256 示例

      const sha256Hash = CryptoJS.SHA256('Hello, World!').toString();
      console.log(sha256Hash);
      
  3. 在 Node.js 中使用 crypto 模块
    Node.js 内置了 crypto 模块用于加密操作。

    const crypto = require('crypto');
    
    // 计算字符串的MD5
    function md5Hash(text) {
      return crypto.createHash('md5').update(text).digest('hex');
    }
    
    // 计算字符串的SHA-256
    function sha256Hash(text) {
      return crypto.createHash('sha256').update(text).digest('hex');
    }
    
    console.log(md5Hash('Hello, World!')); 
    console.log(sha256Hash('Hello, World!'));
    
MD5的应用场景与安全讨论
  • 典型应用场景

    • 文件完整性校验:计算文件MD5并与官方提供的哈希值比对,验证文件在传输过程中是否被篡改。
    • 密码存储(需加盐):将用户密码进行MD5哈希后存储。但务必注意,单独使用MD5非常不安全,一定要配合盐值(Salt) 使用。
      // 示例:加盐MD5
      const password = 'user123';
      const salt = 'random_salt_string'; // 盐值应由后端动态生成,并保证足够长且随机
      const saltedPassword = password + salt;
      const hashedPassword = CryptoJS.MD5(saltedPassword).toString();
      
    • 接口签名防篡改:将请求参数、时间戳和密钥拼接后计算MD5,作为签名随请求发送,服务器端按相同规则验证,确保请求在传输过程中未被修改。
  • 安全最佳实践

    • 应对彩虹表攻击:务必使用加盐(Salt) 处理。
    • 避免密钥前端暴露:关键签名操作应在后端生成,前端硬编码密钥极易被破解。
    • 高安全场景:避免使用MD5,改用 SHA-256HMAC,密码存储推荐使用 bcryptPBKDF2

🔑 对称加密(以 AES 为例)

对称加密的特点是加密和解密使用同一个密钥,加解密速度快,适合处理大量数据。

AES 加密模式

AES 有多种工作模式,例如:

  • CBC 模式 (Cipher Block Chaining):需要初始向量 (IV),安全性较高。
  • GCM 模式 (Galois/Counter Mode):同时提供加密和认证功能。
  • ECB 模式 (Electronic Codebook):简单但不安全,不推荐使用。
使用 CryptoJS 进行 AES 加解密

以下是一个使用 CryptoJS 进行 AES-CBC 加密解密的完整示例:

import CryptoJS from 'crypto-js';

// 准备工作:密钥和初始向量(IV)
// 密钥和IV都应该是16字节(对应128位 AES)。生产环境中,密钥应从后端安全获取,切勿硬编码。
const SECRET_KEY = CryptoJS.enc.Utf8.parse('your-16byte-key!!'); 
const IV = CryptoJS.enc.Utf8.parse('your-16byte-iv__'); 

/**
 * AES-CBC 加密函数
 * @param {any} data - 需要加密的数据(可以是对象、字符串等)
 * @returns {string} 加密后的Base64编码字符串
 */
export function encrypt(data) {
  try {
    // 统一将数据转换为字符串
    const jsonData = typeof data === 'string' ? data : JSON.stringify(data);
    // 执行加密
    const encrypted = CryptoJS.AES.encrypt(jsonData, SECRET_KEY, {
      iv: IV,
      mode: CryptoJS.mode.CBC, // 使用CBC模式
      padding: CryptoJS.pad.Pkcs7 // 使用PKCS7填充
    });
    // 返回Base64格式的密文
    return encrypted.toString();
  } catch (error) {
    console.error('加密失败:', error);
    return '';
  }
}

/**
 * AES-CBC 解密函数
 * @param {string} encryptedData - 加密后的Base64字符串
 * @returns {any} 解密后的原始数据(对象或字符串)
 */
export function decrypt(encryptedData) {
  if (!encryptedData) return null;
  try {
    // 执行解密
    const decrypted = CryptoJS.AES.decrypt(encryptedData, SECRET_KEY, {
      iv: IV,
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7
    });
    // 将解密结果转换为UTF8字符串
    const originalText = decrypted.toString(CryptoJS.enc.Utf8);
    
    // 尝试解析为JSON对象(如果原始数据是对象)
    try {
      return JSON.parse(originalText);
    } catch {
      // 解析失败则直接返回字符串
      return originalText;
    }
  } catch (error) {
    console.error('解密失败:', error);
    return null;
  }
}

// 使用示例
const originalInfo = { user: 'Alice', id: 123 };
const encryptedText = encrypt(originalInfo);
console.log('加密结果:', encryptedText);

const decryptedText = decrypt(encryptedText);
console.log('解密结果:', decryptedText);
使用 Web Crypto API 进行 AES 加解密

对于现代浏览器,你可以使用原生的 Web Crypto API 进行 AES-GCM 加密:

const encoder = new TextEncoder();
const decoder = new TextDecoder();

/**
 * 生成一个用于AES-GCM算法的密钥
 */
async function generateAESKey() {
  return await crypto.subtle.generateKey(
    {
      name: "AES-GCM",
      length: 256, // 密钥长度:256位
    },
    true, // 密钥是否可提取
    ["encrypt", "decrypt"] // 密钥用途
  );
}

/**
 * 使用AES-GCM加密数据
 * @param {CryptoKey} key - 生成的密钥
 * @param {string} data - 要加密的明文
 * @returns {Object} 包含密文和初始向量(IV)的对象
 */
async function encryptAES(key, data) {
  const encoded = encoder.encode(data);
  // 生成随机的12字节初始向量(IV)
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const encrypted = await crypto.subtle.encrypt(
    {
      name: "AES-GCM",
      iv: iv
    },
    key,
    encoded
  );
  return { encrypted, iv };
}

/**
 * 使用AES-GCM解密数据
 * @param {CryptoKey} key - 用于解密的密钥
 * @param {ArrayBuffer} encryptedData - 密文数据
 * @param {Uint8Array} iv - 初始向量
 * @returns {string} 解密后的明文
 */
async function decryptAES(key, encryptedData, iv) {
  const decrypted = await crypto.subtle.decrypt(
    {
      name: "AES-GCM",
      iv: iv
    },
    key,
    encryptedData
  );
  return decoder.decode(decrypted);
}

// 使用示例
(async () => {
  const key = await generateAESKey();
  const originalData = "这是一段秘密信息";
  
  const { encrypted, iv } = await encryptAES(key, originalData);
  console.log("加密结果:", new Uint8Array(encrypted));
  
  const decryptedData = await decryptAES(key, encrypted, iv);
  console.log("解密结果:", decryptedData);
})();

🗝️ 非对称加密(以 RSA 为例)

非对称加密使用一对密钥:公钥和私钥。公钥用于加密,私钥用于解密。Web Cryptography API 也支持 RSA 算法。

使用 Web Crypto API 生成密钥对与加解密
/**
 * 生成RSA-OAEP密钥对
 */
async function generateRSAKeyPair() {
  return await crypto.subtle.generateKey(
    {
      name: "RSA-OAEP",
      modulusLength: 2048, // 密钥长度
      publicExponent: new Uint8Array([1, 0, 1]), // 公共指数:65537
      hash: "SHA-256", // 与密钥一起使用的哈希函数
    },
    true, // 密钥是否可提取
    ["encrypt", "decrypt"] // 密钥用途
  );
}

/**
 * 使用公钥加密数据
 * @param {ArrayBuffer} publicKey - 公钥
 * @param {string} data - 要加密的明文
 * @returns {ArrayBuffer} 密文
 */
async function encryptWithRSA(publicKey, data) {
  const encoder = new TextEncoder();
  const encoded = encoder.encode(data);
  return await crypto.subtle.encrypt(
    { name: "RSA-OAEP" },
    publicKey,
    encoded
  );
}

/**
 * 使用私钥解密数据
 * @param {CryptoKey} privateKey - 私钥
 * @param {ArrayBuffer} encryptedData - 密文
 * @returns {string} 解密后的明文
 */
async function decryptWithRSA(privateKey, encryptedData) {
  const decoder = new TextDecoder();
  const decrypted = await crypto.subtle.decrypt(
    { name: "RSA-OAEP" },
    privateKey,
    encryptedData
  );
  return decoder.decode(decrypted);
}

// 使用示例
(async () => {
  // 生成密钥对
  const { publicKey, privateKey } = await generateRSAKeyPair();
  const message = "这是一条秘密消息";
  
  // 加密
  const encrypted = await encryptWithRSA(publicKey, message);
  console.log("RSA加密后的数据:", new Uint8Array(encrypted));
  
  // 解密
  const decrypted = await decryptWithRSA(privateKey, encrypted);
  console.log("RSA解密后的数据:", decrypted);
})();

⚠️ 重要安全提示与实践建议

在JavaScript中进行加解密操作时,务必牢记以下安全准则:

  1. 前端加密不能替代HTTPS:前端代码和传输的密文仍有被截获和篡改的风险。HTTPS 是保障通信安全的基础,前端加密只能作为额外防护手段。
  2. 永远不要硬编码密钥:将密钥直接写在前端代码中极易被他人获取。生产环境中,密钥应通过安全的后端接口动态获取。
  3. 使用强随机数:生成密钥、初始向量(IV)、盐值(Salt)时,务必使用密码学安全的随机数生成器,如 crypto.getRandomValues()
  4. 妥善管理密钥:遵循最小权限原则,定期轮换密钥。公钥和私钥要分开存储,私钥必须保存在安全的服务器端,严禁泄露。
  5. 选择正确的算法
    • 哈希:避免使用MD5、SHA-1,推荐 SHA-256SHA-512。密码存储推荐使用 bcryptPBKDF2Argon2
    • 对称加密:推荐 AES-GCMAES-CBC 模式,并确保使用随机且唯一的IV。
    • 非对称加密:推荐 RSA-OAEP 或基于椭圆曲线的 ECC

💎 总结

总的来说,JavaScript 通过 Web Cryptography API 和 Node.js crypto 模块提供了强大的加密能力。MD5作为一种哈希算法,因其自身安全性问题,不推荐在高安全场景下使用,但了解其原理和实现对于学习其他加密算法仍有帮助。在实际开发中,请务必根据你的具体需求(是要求完整性校验、机密性还是身份认证)选择合适的算法,并严格遵守安全实践。

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

越重天

你的打赏是我精心创作的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值