为了安全前端经常需要先对登录密码、姓名、身份证号、手机号等信息进行加密然后再通过接口传给后端,因此需要了解一些加密算法,比较常用的有哈希函数、RSA(非对称加密算法)和AES(对称加密算法)
MD5 是一种常见的哈希函数,js可以安装js-md5npm包来使用md5。
Node.js 自带的 crypto 模块是一个强大的加密库,它提供了对称加密、非对称加密、哈希、签名等多种加密功能。而 jsencrypt 和 node-rsa 是 npm 上的第三方库,它们分别提供了对 RSA 加密的简化封装,使得在 Node.js 中使用 RSA 加密变得更加容易。
哈希函数加密
MD5 (Message Digest Algorithm 5)
哈希函数是一种将任意长度的数据映射为固定长度的摘要(或哈希值)的算法。它具有以下特点:
- 不可逆性:
- 哈希函数是单向的,即从输入数据生成哈希值是容易的,但从哈希值反推出输入数据是非常困难的。
- 固定长度输出:
- 无论输入数据的长度是多少,哈希函数生成的哈希值长度是固定的。例如,MD5 生成的哈希值长度是 128 位(16 字节)。
- 抗碰撞性:
- 理想情况下,不同的输入数据应生成不同的哈希值,即很难找到两个不同的输入具有相同的哈希值(碰撞)。
- 应用场景:
- 数据完整性校验:通过比较哈希值来验证数据在传输或存储过程中是否被篡改。
- 数字签名:结合非对称加密,用于验证数据的来源和完整性。
- 密码存储:将密码哈希后存储,增加安全性。
MD5 是一种常见的哈希函数,广泛用于数据完整性校验和数字签名等场景。然而,由于其抗碰撞性较差,MD5 已经被认为是不安全的,不推荐用于安全相关的应用。
对称加密算法 (Symmetric Encryption Algorithm)
对称加密算法是一种使用相同密钥进行加密和解密的算法。它具有以下特点:
- 加密和解密使用相同的密钥:
- 加密和解密过程都使用同一个密钥,因此密钥的保密性至关重要。
- 可逆性:
- 对称加密算法是可逆的,即可以使用密钥将加密后的数据解密回原始数据。
- 应用场景:
- 数据传输加密:保护数据在传输过程中的安全。
- 文件加密:保护存储文件的安全。
常见的对称加密算法包括 AES (Advanced Encryption Standard)、DES (Data Encryption Standard) 和 3DES (Triple DES) 等。
非对称加密 (Asymmetric Encryption)
特点:
- 公钥和私钥:
- 使用一对密钥进行加密和解密,公钥用于加密,私钥用于解密。公钥可以公开,而私钥必须保密。
- 可逆性:
- 数据可以用公钥加密,然后用对应的私钥解密,反之亦然。
常见算法:
- 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公钥把信息加密后传给后端