新的一年,来了解一下前端加密吧

255 阅读7分钟

为了安全前端经常需要先对登录密码、姓名、身份证号、手机号等信息进行加密然后再通过接口传给后端,因此需要了解一些加密算法,比较常用的有哈希函数RSA(非对称加密算法)AES(对称加密算法)

MD5 是一种常见的哈希函数,js可以安装js-md5npm包来使用md5。

Node.js 自带的 crypto 模块是一个强大的加密库,它提供了对称加密、非对称加密、哈希、签名等多种加密功能。而 jsencrypt 和 node-rsa 是 npm 上的第三方库,它们分别提供了对 RSA 加密的简化封装,使得在 Node.js 中使用 RSA 加密变得更加容易。

哈希函数加密

MD5 (Message Digest Algorithm 5) 

哈希函数是一种将任意长度的数据映射为固定长度的摘要(或哈希值)的算法。它具有以下特点:

  1. 不可逆性
    • 哈希函数是单向的,即从输入数据生成哈希值是容易的,但从哈希值反推出输入数据是非常困难的。
  2. 固定长度输出
    • 无论输入数据的长度是多少,哈希函数生成的哈希值长度是固定的。例如,MD5 生成的哈希值长度是 128 位(16 字节)。
  3. 抗碰撞性
    • 理想情况下,不同的输入数据应生成不同的哈希值,即很难找到两个不同的输入具有相同的哈希值(碰撞)。
  • 应用场景:
    • 数据完整性校验:通过比较哈希值来验证数据在传输或存储过程中是否被篡改。
    • 数字签名:结合非对称加密,用于验证数据的来源和完整性。
    • 密码存储:将密码哈希后存储,增加安全性。

MD5 是一种常见的哈希函数,广泛用于数据完整性校验和数字签名等场景。然而,由于其抗碰撞性较差,MD5 已经被认为是不安全的,不推荐用于安全相关的应用。

对称加密算法 (Symmetric Encryption Algorithm)

对称加密算法是一种使用相同密钥进行加密和解密的算法。它具有以下特点:

  1. 加密和解密使用相同的密钥
    • 加密和解密过程都使用同一个密钥,因此密钥的保密性至关重要。
  2. 可逆性
    • 对称加密算法是可逆的,即可以使用密钥将加密后的数据解密回原始数据。
  • 应用场景:
    • 数据传输加密:保护数据在传输过程中的安全。
    • 文件加密:保护存储文件的安全。

常见的对称加密算法包括 AES (Advanced Encryption Standard)、DES (Data Encryption Standard) 和 3DES (Triple DES) 等。

非对称加密 (Asymmetric Encryption)

特点:

  1. 公钥和私钥
    • 使用一对密钥进行加密和解密,公钥用于加密,私钥用于解密。公钥可以公开,而私钥必须保密。
  2. 可逆性
    • 数据可以用公钥加密,然后用对应的私钥解密,反之亦然。

常见算法:

  • RSA (Rivest-Shamir-Adleman)
  • ECC (Elliptic Curve Cryptography)

应用场景:

  • 安全通信:如 SSL/TLS 协议,用于保护互联网通信的安全。
  • 数字签名:验证数据的来源和完整性。
  • 密钥交换:安全地交换对称加密密钥。

总结

  • 哈希函数:单向(不可逆)、固定长度输出、用于数据完整性校验和密码存储等。
  • 对称加密:相同密钥、可逆、用于数据传输加密和文件加密等。
  • 非对称加密:公钥和私钥、可逆、用于安全通信、数字签名和密钥交换等。

哈希函数-md5

import { md5 } from 'js-md5';
md5('string');

对称加密与非对称加密

crypto 模块

crypto 模块是 Node.js 官方提供的,提供了底层的加密 API,由于是 Node.js 的一部分,所以无需额外安装即可使用,支持对称加密算法(如 AES、DES 等)和非对称加密算法(如 RSA、EC 等),但是,对于不熟悉底层加密细节的开发人员来说,直接使用 crypto 模块可能会有些复杂。

jsencrypt

jsencrypt 是一个用于在浏览器和 Node.js 中进行 RSA 加密和解密的库,简化了 RSA 加密的使用过程,使得开发人员可以更容易地实现 RSA 加密功能。jsencrypt 提供了一个简单的 API,可以通过公钥和私钥进行加密和解密操作。它特别适用于在客户端和服务器之间进行安全通信的场景。

node-rsa

node-rsa 是另一个用于 Node.js 的 RSA 加密库。与 jsencrypt 类似,它也提供了一个简洁的 API 来处理 RSA 加密和解密。node-rsa 支持生成密钥对、公钥加密、私钥解密以及签名和验证等操作。它特别适合在 Node.js 应用程序中需要 RSA 加密的场景中使用。

选择哪个库?

  • 如果你需要实现复杂的加密逻辑,并且希望有更多的控制和灵活性,那么可以选择使用 Node.js 自带的 crypto 模块。它提供了底层的加密 API,可以满足各种加密需求。
  • 如果你只需要进行 RSA 加密和解密,并且希望有一个简洁易用的 API,那么可以选择 jsencrypt 或 node-rsa。这两个库都对 RSA 加密进行了简化封装,使得使用起来更加方便。

实践

安装依赖

AES加密安装crypto-js
RSA加密安装jsencrypt或者node-rsa
jsencrypt只能公钥加密->私钥解密,无法私钥加密->公钥解密 node-rsa既可以公钥加密->私钥解密,也可以私钥加密->公钥解密

npm install jsencrypt
npm install node-rsa
npm install @types/node-rsa -D
secret.js封装方法
import CryptoJS from 'crypto-js/crypto-js'
import { JSEncrypt } from 'jsencrypt'
// const KEY = CryptoJS.enc.Utf8.parse('666666'); // '666666' 与后台一致
// const IV = CryptoJS.enc.Utf8.parse('888888'); // '888888' 与后台一致

// AES加密 :字符串 key iv  返回base64
export function AESEncrypt(str, keyStr, ivStr) {
    // 字符串 key iv通常从接口获取
    let key = ''
    let iv = ''

    if (keyStr && ivStr) {
      key = CryptoJS.enc.Utf8.parse(keyStr)
      iv = CryptoJS.enc.Utf8.parse(ivStr)
    }
    const srcs = CryptoJS.enc.Utf8.parse(str)
    var encrypted = CryptoJS.AES.encrypt(srcs, key, {
      iv: iv,
      // mode: CryptoJS.mode.ECB,
      mode: CryptoJS.mode.CBC,  // mode 与后台一致
      padding: CryptoJS.pad.Pkcs7,
    })
    return CryptoJS.enc.Base64.stringify(encrypted.ciphertext)
}

// AES 解密 :字符串 key iv  返回base64
export function AESDecrypt(str, keyStr, ivStr) {
    let key = ''
    let iv = ''

    if (keyStr && ivStr) {
      key = CryptoJS.enc.Utf8.parse(keyStr)
      iv = CryptoJS.enc.Utf8.parse(ivStr)
    }

    const base64 = CryptoJS.enc.Base64.parse(str)
    const src = CryptoJS.enc.Base64.stringify(base64)

    var decrypt = CryptoJS.AES.decrypt(src, key, {
      iv: iv,
      // mode: CryptoJS.mode.ECB,
      // mode: CryptoJS.mode.BCB, // 保持一致
      mode: CryptoJS.mode.CBC, // 保持一致
      padding: CryptoJS.pad.Pkcs7
    })

    var decryptedStr = decrypt.toString(CryptoJS.enc.Utf8)
    return decryptedStr.toString()
}

// ras 公钥加密
export const RSAEncrypt = (str) => {
  const crypt = new JSEncrypt({ default_key_size: 1024 })
  const publickKey = '公钥'
  crypt.setPublicKey(publickKey)
  // 后端生成公钥字符串后放到前端代码里或者从接口返回
  return crypt.encrypt(str)
}

// ras 私钥解密
export const RSADecrypt = (str) => {
  const crypt = new JSEncrypt({ default_key_size: 1024 })
  const privkey = '私钥'
  crypt.setPrivateKey(privkey)
  return crypt.decrypt(str)
}

// rsa 私钥加密
export const RSAEncrypt2 = (str) => {
  const privkkey = '私钥'
  const privkey = `-----BEGIN PRIVATE KEY-----\n${privkkey}\n-----END PRIVATE KEY-----`
  const NodeRSA = require('node-rsa')
  const key = new NodeRSA(privkey)
  return key.encryptPrivate(str, 'base64') // 加密
}

// rsa 公钥解密
export const RSADecrypt2 = (str) => {
  const publickKey = '公钥'
  const publicKey = `-----BEGIN PUBLIC KEY-----\n${publickKey}\n-----END PUBLIC KEY-----` // 公钥封装,加入前后缀
  const NodeRSA = require('node-rsa')
  const key = new NodeRSA(publicKey)
  return key.decryptPublic(str, 'utf8') // 解密
}

对称加密算法和非对称加密算法,怎么选择?

参考HTTPS协议的加密方案:两种算法结合使用,使用非对称加密算法加密对称加密算法的密钥,使用对称加密算法加解密数据,既保证安全,又保证效率(非对称加密比较耗费时间)

使用流程

const getStr = async () => {
  try {
    const res = await api1() // 接口1
    if (res.status === 0 && res.data) {
      const resapi2 = await api2({ a1: RSADecrypt(res.data.a1) }) // 接口2获取rsa加密后的aes密钥
      if (resapi2.status === 0 && resapi2.data) {
        const result = AESDecrypt(res.data.str, RSADecrypt(resapi2.data.key), RSADecrypt(resapi2.data.iv))
        alert(result)
      }
    }
  } catch (e) {
    console.log(e)
  }
}

或者简单一些,后端通过接口直接返回未加密的ras公钥,然后前端使用JSEncrypt公钥把信息加密后传给后端

参考

实例项目

AES对称加密算法

RSA非对称加密算法