crypto加密-实战篇之非对称和对称结合加密

前言

在Web应用中,为了安全地传输用户密码,前端使用AES加密密码,然后使用RSA加密AES密钥。这样可以确保密码在传输过程中不被泄露,同时保证了整个过程的安全性。

步骤

  1. 后端生成RSA密钥对

    • 后端生成RSA密钥对(公钥和私钥)。
    • 后端将RSA公钥发送给前端。
  2. 前端生成AES密钥

    • 前端生成一个随机的AES密钥。
    • 前端使用AES密钥加密用户密码。
  3. 前端使用RSA公钥加密AES密钥

    • 前端使用后端提供的RSA公钥加密AES密钥。
  4. 前端将加密后的数据发送到后端

    • 前端将加密后的密码、IV(初始化向量)、AuthTag(认证标签)和加密后的AES密钥一起发送到后端。
  5. 后端解密AES密钥

    • 后端使用RSA私钥解密AES密钥。
  6. 后端使用AES密钥解密密码

    • 后端使用解密后的AES密钥解密用户密码。
graph TD A[用户输入密码] --> B[前端生成AES密钥] B --> C[前端使用AES密钥加密密码] C --> D[前端使用RSA公钥加密AES密钥] D --> E[前端将加密后的数据发送到后端] E --> F[后端使用RSA私钥解密AES密钥] F --> G[后端使用AES密钥解密密码] G --> H[后端验证密码]

详细步骤说明

  1. 后端生成RSA密钥对

    • 后端生成RSA密钥对,并将公钥发送给前端。
    • 示例代码:
      const crypto = require('crypto');
      
      const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
          modulusLength: 2048,
          publicKeyEncoding: { type: 'spki', format: 'pem' },
          privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
      });
      
      console.log('Public Key:', publicKey);
      
  2. 前端生成AES密钥

    • 前端生成一个随机的AES密钥。
    • 示例代码:
      const crypto = require('crypto');
      
      const aesKey = crypto.randomBytes(32); // 256位AES密钥
      
  3. 前端使用AES密钥加密密码

    • 前端使用AES密钥加密用户密码。
    • 示例代码:
      const crypto = require('crypto');
      
      function encryptData(data, aesKey) {
          const iv = crypto.randomBytes(12); // 12字节IV
          const cipher = crypto.createCipheriv('aes-256-gcm', aesKey, iv);
          let encrypted = cipher.update(data, 'utf8', 'hex');
          encrypted += cipher.final('hex');
          const authTag = cipher.getAuthTag().toString('hex');
          return { iv: iv.toString('hex'), encrypted, authTag };
      }
      
      const data = 'my_secret_password';
      const { iv, encrypted, authTag } = encryptData(data, aesKey);
      
  4. 前端使用RSA公钥加密AES密钥

    • 前端使用RSA公钥加密AES密钥。
    • 示例代码:
      const crypto = require('crypto');
      
      const encryptedAESKey = crypto.publicEncrypt(
          {
              key: publicKey,
              padding: crypto.constants.RSA_PKCS1_PADDING
          },
          aesKey
      );
      
      console.log('Encrypted AES Key:', encryptedAESKey.toString('base64'));
      
  5. 前端将加密后的数据发送到后端

    • 前端将加密后的密码、IV、AuthTag和加密后的AES密钥一起发送到后端。
    • 示例代码:
      const encryptedData = {
          encryptedPassword: encrypted,
          iv: iv,
          authTag: authTag,
          encryptedAESKey: encryptedAESKey.toString('base64')
      };
      
      // 发送到后端
      console.log(encryptedData);
      
  6. 后端解密AES密钥

    • 后端使用RSA私钥解密AES密钥。
    • 示例代码:
      const crypto = require('crypto');
      
      const decryptedAESKey = crypto.privateDecrypt(
          {
              key: privateKey,
              padding: crypto.constants.RSA_PKCS1_PADDING
          },
          Buffer.from(encryptedAESKey, 'base64')
      );
      
      console.log('Decrypted AES Key:', decryptedAESKey.toString('hex'));
      
  7. 后端使用AES密钥解密密码

    • 后端使用解密后的AES密钥解密用户密码。
    • 示例代码:
      const crypto = require('crypto');
      
      function decryptData(encryptedData, iv, authTag, aesKey) {
          const decipher = crypto.createDecipheriv('aes-256-gcm', aesKey, Buffer.from(iv, 'hex'));
          decipher.setAuthTag(Buffer.from(authTag, 'hex'));
          let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
          decrypted += decipher.final('utf8');
          return decrypted;
      }
      
      const decryptedPassword = decryptData(encrypted, iv, authTag, decryptedAESKey);
      console.log('Decrypted Password:', decryptedPassword);
      

入库

若还需反解,那就后端在解析完前端的加密后,再次使用后端自己的对称密钥文件进行加密存储!
否则直接 hash 化即可!

针对密码,hash 化处理是最常见的。所以后续都是再讲 hash 化!

将用户设置的密码入库:hash 化的字符串,以及加入的随机字符salt

import crypto from "crypto";

// 使用PBKDF2对密码进行哈希处理
const hashPassword = ( password: string,  saltParam?: string ): { hash: string; salt: string } => {
  const saltValue = saltParam || crypto.randomBytes(4).toString("hex"); // 生成随机盐值(也就是随机字符串),建议 6 位
  const iterations = 100000; // 迭代次数
  const keyLength = 6; // 哈希长度(建议 64 为)
  const digest = "sha512"; // 哈希算法

  const hash = crypto
    .pbkdf2Sync(password, saltValue, iterations, keyLength, digest)
    .toString("hex");

  return {
    hash,
    salt: saltValue,
  };
};

// 将明文密码哈希处理,并入库(注册的时候)
const { hash, salt } = hashPassword("hello2015");
console.log(hash, salt); // 0aafb73d2d7f, d49e7a0a
const record = { // 入库数据
  pwd: 'c50b44be34b6',
  salt:'b0b9f633'
}

校验使用

const inputPwd = 'hello2015'; // 前端传递过的需要校验(是否存在)的数据
const { hash: newHash } = hashPassword(inputPwd, record.salt);
if(newHash === record.pwd){
  console.log("密码验证成功");
}

400

hash

哈希化(Hashing)是一种单向过程,设计上是不可逆的。这意味着你不能从哈希值直接还原出原始数据。,所以即便不小心泄露了也没事。

salt

中文为,目的是在给字符串 hash 化的时候,加上随机字符。
这样即便密码相同,hash 也会不同,从而防止防止彩虹表攻击,即为每个用户生成一个独特的盐值。

盐值本身无意义,所以泄露了也没事!

总结

这是一个典型的非对称加密结合对称加密的流程,常用于安全传输敏感数据(如密码)

1、后端或前端 产出非对称密钥(找个安全的地方存储起来,私钥千万不能暴漏)
2、前端每次加密都需临时生成对称密钥-->去加密数据
3、前端还需要拿非对称的公钥-->去加密第二步产出的对称密钥
4、前端把第2 和第 3 步骤,产出都数据都提交给后端
5、后端拿非对称的私钥 去解密对称密钥,再拿对称密钥解密出密码
6、后端拿解密出的密码去 hash 化,注意保 hash 用到的留盐值
7、最后后端将 hash 化的密码入库,并把盐值也入库

上诉描述的内容完全属于计算机密码学(Cryptography)的知识范畴,而且是现代密码学中非常经典和实用的技术组合。
您提出的方案是一个典型的混合加密系统(Hybrid Cryptosystem),它结合了两种密码学体系的优点

posted @ 2025-08-25 15:38  丁少华  阅读(31)  评论(0)    收藏  举报