本文主要详细介绍了在前端开发中常用的加/解密算法,以及前端如何实现。
总的来说:前端加密无论使用哪个加密都一样是有可能性被他人获取到相关的公钥或密钥的(比如:拦截请求、查看源代码等),然后进行加密与后端通讯,所以前端加密所谓的安全性是有限的,最主要的还是在后端!
含义: 加密和解密使用的是相同的秘钥,加密快而且方便,但缺点是密钥容易被偷或被破解。
常见算法: AES、DES、3DES 、RC2、RC4、RC5、RC6 、 Blowfish、国密SM4。
常见应用场景:
含义: 加密和解密用的不是同一个秘钥,一般是用公钥加密,用私钥解密,公钥和私钥成对存在,公钥从私钥中提取产生公开给所有人。安全性要好于对称加密的,但性能就比较差。
常见算法: RSA、DSA、Elgamal、DSS、Merkle-Hellman背包算法、D-H、ECC(椭圆曲线加密算法)、国家商用密码SM2算法。
常见应用场景:
在实际应用中,非对称加密通常用于需要确保数据完整性和安全性的场合,如:数字证书的颁发、SSL/TLS 协议的加密、数字签名、加密小文件、密钥交换、实现安全的远程通信等。
含义: 又称为消息摘要算法,是不可逆的加密算法,即对明文进行加密后,无法通过得到的密文还原回去得到明文。
常见的单项散列函数: MD5、SHA1、SHA256、SHA512 ,以及它们之前加上 Hmac(Keyed-hash message authentication codes) 后的 HmacMD5、HmacSHA1 等。
英文全称为 Advanced Encryption Standard
,即高级加密标准的意思。
AES的推出,用于取代已经被证明不安全的 DES 算法。
AES 属于分组加密算法,因为它会把传入的明文数据以 128 bit 为一组分别处理。
其秘钥长度则可以是 128、192 和 256 bit。
AES 或者说对称加密算法的优点是速度快,缺点就是不安全,因为网站上的代码和秘钥都是明文,别人只要得到了加密结果再结合秘钥就能得到加密的数据了。
AES与DES
DES
优点:DES算法具有极高安全性,到目前为止,除了用穷举搜索法对DES算法进行攻击外,还没有发现更有效的办法。
缺点:分组比较短、密钥太短、密码生命周期短、运算速度较慢。
AES
优点:运算速度快,对内存的需求非常低,适合于受限环境。分组长度和密钥长度设计灵活, AES标准支持可变分组长度;具有很好的抵抗差分密码分析及线性密码分析的能力。
缺点:目前尚未存在对AES 算法完整版的成功攻击,但已经提出对其简化算法的攻击。
AES与DES之间的主要区别 在于加密过程:在DES中,将明文分为两半,然后再进行进一步处理;而在AES中,整个块不进行除法,整个块一起处理以生成密文。
相对而言,AES比DES快得多,与DES相比,AES能够在几秒钟内加密大型文件。
RSA加密算法是一种非对称加密算法,使用"一对"密钥,分别是公钥和私钥,这个公钥和私钥其实就是一组数字,其二进制位长度可以是1024位或者2048位,长度越长其加密强度越大。
目前为止公之于众的能破解的最大长度为795位密钥,只要高于795位,相对就比较安全,所以目前为止这种加密算法一直被广泛使用。
由于RSA算法的原理都是大数计算,使得RSA最快的情况也比对称加密算法慢上好几倍。
RSA的速度是对应同样安全级别的对称加密算法的1/1000左右。
常见场景:
速度一直是RSA的缺陷,一般来说RSA只用于小数据的加密,比如对称加密算法的密钥,又或者数字签名。例如:在用户注册或登录的时候,用公钥对密码进行加密,再去传给后台,后台用私钥对加密的内容进行解密,然后进行密码校验或者保存到数据库。
MD5 长度固定,不论输入的内容有多少字节,最终输出结果都为 128 bit,即 16 字节。
这就是为什么 MD5 以及其它单向散列函数是不可逆的 —— 输出定长代表会有数据丢失。
通常,我们可以用 16 进制字面值来表示它,每 4 bit 以 16 进制字面值显示,得到的是一个长度为 32 位的字符串。
注意,MD5 等单向散列函数具有高度的离散性,即:只要输入的明文不一样,得到的结果就完全不一样,哪怕是仅仅多了一个空格。
使用场景:
MD5和SHA
MD5 :是RSA数据安全公司开发的一种单向散列算法,非可逆,相同的明文产生相同的密文。
SHA(Secure Hash Algorithm) :可以对任意长度的数据运算生成一个固定位数的数值。
SHA/MD5对比:SHA在安全性方面优于MD5,并且可以选择多种不同的密钥长度。 但是,由于内存需求更高,运行速度可能会更慢。 不过,MD5因其速度而得到广泛使用,但是由于存在碰撞攻击风险,因此不再推荐使用。
前端常用的方式有:crypto-js、encryptlong、jsencrypt、bcrypt
crypto-js
crypto-js支持多种算法:MD5、SHA1、SHA2、SHA3、RIPEMD-160
的哈希散列,以及进行 AES、DES、Rabbit、RC4、Triple DES
加解密。
1)常用加密方法
Cryptojs.AES.encrypt
,Cryptojs.DES.encrypt
,Cryptojs.Rabbit.encrypt
,Cryptojs.RC4.encrypt
,Cryptojs.TripleDES.encrypt
2)常用解密方法
.toString(CryptoJS.enc.Utf8)
转为明文。CryptoJS.AES.decrypt
,CryptoJS.DES.decrypt
,CryptoJS.Rabbit.decrypt
,CryptoJS.RC4.decrypt
,CryptoJS.TripleDES.decrypt
3)可选参数对象常用属性
mode
:加密模式paddig
:填充方式\x80
加上零或多个 \x00
直到块的大小。vi
: 偏移向量formatter
:自定义格式enc
: 指定字符编码模式,包含8 个 API:“Hex”, “Latin1”, “Utf8”, “Utf16BE”, “Utf16”, “Utf16LE”, “Base64”, “Base64url”algo
:包含很多算法
jsencrypt
jsencrypt是一个基于rsa加解密的js库。
new jsencrypt()
如下:
jsencrypt 的加解密过程需要用到 OpenSSL 来生成秘钥,OpenSSL 是一个开源的软件,它是对 SSL 协议的实现。
RSA秘钥生成方式
命令 | 含义 |
---|---|
genrsa | 生成并输入一个RSA私钥 |
rsautl | 使用RSA密钥进行加密、解密、签名和验证等运算 |
rsa | 处理RSA密钥的格式转换等问题 |
具体操作:
openssl genrsa -out private.pem 1024 // 生成私钥,密钥长度为1024bit
openssl rsa -in private.pem -pubout -out public.pem // 从私钥中提取公钥,同时会生成了private.pem 和 public.pem两个文件
cat private.pem // 查看私钥
cat public.pem // 查看公钥
jsencrypt使用注意点
jsencrypt加密的对象一定要是字符串,jsencrypt加密长对象或者数据太多,可能会返回false
,或者报错:Message too long for RSA
。
可能导致问题的原因:
解决办法:用encryptlong
encryptlong
encryptlong是基于 jsencrypt 扩展了长文本分段加解密功能,即:需要进行rsa加密的内容比较长时,就用encryptlong进行实现。
new encryptlong()
如下:
bcryptjs
bcrypt是一种用于密码哈希的加密算法,它是基于Blowfish算法的加强版,生成的密文为60位。
bcrypt是一种加盐的单向Hash,不可逆的加密算法,同一种明文,每次加密后的密文都不一样,而且不可反向破解生成明文,破解难度很大。
bcryptjs
对象如下:
应用场景:
bcrypt原理:
bcrypt算法是一种密码哈希函数,它采用了salt和cost两种机制来增强密码的安全性。
bcrypt加密后值说明
bcrypt 密码强度一般在10-12。默认是10,太强会导致速度变慢。
加密得到的字符串的格式:$2a $[cost] $[22 character salt] [31 character hash]
cost
:是代价因子,这里的10 表示是2的10次方,也就是1024轮22 character salt
:16个字节(128bits)的salt经过base64编码得到的22长度的字符31 character hash
:24个字节(192bits)的hash,经过bash64的编码得到的31长度的字符通过npm安装: npm install crypto-js encryptlong jsencrypt bcrypt
import CryptoJS from "crypto-js";
const encryptByRSA = (content, publicKey) => {
const rsa = new JSEncrypt();
rsa.setPublicKey(publicKey);
return rsa.encrypt(content);
};
const decryptFromRSA = (content, privateKey) => {
const rsa = new JSEncrypt();
rsa.setPrivateKey(privateKey);
return rsa.decrypt(content);
};
import CryptoJS from "crypto-js";
const encryptByAES = (content, key, iv) => {
try {
if (typeof content === "object") {
content = JSON.stringify(content);
}
const data = CryptoJS.enc.Utf8.parse(data);
const aes = CryptoJS.AES.encrypt(data, key, {
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
iv: CryptoJS.enc.Utf8.parse(iv)
});
return aes.ciphertext.toString();
} catch (e) {
return content;
}
};
const decryptFromAES = (content, key, iv) => {
try {
const encryptedHexStr = CryptoJS.enc.Hex.parse(content);
const data = CryptoJS.enc.Base64.stringify(encryptedHexStr);
const aes = CryptoJS.AES.decrypt(data, key, {
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
iv: CryptoJS.enc.Utf8.parse(iv)
});
const originalText = aes.toString(CryptoJS.enc.Utf8);
return originalText.toString();
} catch (e) {
return content;
}
};
import JSEncrypt from "jsencrypt";
// 注:如果是对象/数组的话,需要先JSON.stringify转换成字符串
const encryptByRSA = (content, publicKey) => {
const rsa = new JSEncrypt();
rsa.setPublicKey(publicKey);
return rsa.encrypt(content);
};
const decryptFromRSA = (content, privateKey) => {
const rsa = new JSEncrypt();
rsa.setPrivateKey(privateKey);
return rsa.decrypt(content);
};
import JSEncryptLong from "encryptlong";
// 注:如果是对象/数组的话,需要先JSON.stringify转换成字符串
const encryptLongByRSA = (content, publicKey) => {
const rsa = new JSEncryptLong();
rsa.setPublicKey(publicKey);
return rsa.encryptLong(content);
};
const decryptLongFromRSA = (content, privateKey) => {
const rsa = new JSEncryptLong();
rsa.setPrivateKey(privateKey);
return rsa.decryptLong(content);
};
测试示例:
const PUBLIC_KEY = `MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDKX1Fs2JUD25zrAEwPnjnZC0azrl1XjGzGrJ64eb1lr9QVVOO2zGKZdqDLZD4Ut4Mp6GHMaqqFXKm+zN7IAXu+mqZbUrqUziHE5YGC02wObiZEzfa6V9a8ZvqpB+Z8KO+hAkkjzjMl+E+hDORpZmez3SMzetn7mcCeLw8/vmxz3QIDAQAB`;
const PRIVATE_KEY = `MIICXgIBAAKBgQDKX1Fs2JUD25zrAEwPnjnZC0azrl1XjGzGrJ64eb1lr9QVVOO2zGKZdqDLZD4Ut4Mp6GHMaqqFXKm+zN7IAXu+mqZbUrqUziHE5YGC02wObiZEzfa6V9a8ZvqpB+Z8KO+hAkkjzjMl+E+hDORpZmez3SMzetn7mcCeLw8/vmxz3QIDAQABAoGBAJBr6b4V6nJwXdHPyngy4PGl/HTqcK60BkTamALqzmEtU9tNU5z2yz7dy+6awTsjo7Vao8CwNrUp5fHGXw65EEc1/3Iu2Fiix0XF7RP4NFSoxbBmzQW1nUK/5DFi4VR1uhEmdbgLwGabsdqzeUqhRKkRGAPVCotBjaDBOu0J3Mu5AkEA+SM7Ctu7evOvZwjWrp9a5MGxJ9yLLabbIuWL+420jr2G6ojaTZ2ROA2DWWQPx4JqWxDHttomrb38dk2emP2WAwJBAM/yU58YRQ+dTeuTzNYC1JdWcs35n9+hoVP7y+x29CmcqDTPp3nRBbbq88yMb2nZdlwthWi7BurNHsRJFqj0GJ8CQF5gJCuW1UxcJ2PGi1yW7R2e6fcJqoden8B2aDKgmXdBAGyz7s5cE/jB1bH1H60aECPzFVSFCwXh5FMEUEHwPfUCQQC7JqZ57lbhebrSRcA58GwzFFvY40wu8gIHWvwqgti2xsZgWW+qZCPXf9gSBWaUhmJPDa0fGAxesGN7VyhswNuTAkEAzCFNqL/zwHXcwh9YyHTdk/bRWIJq49jTA+vbgGv0szKIvGRKoRbub3NEUiI80TDsCAvbJ6R80J7RjnpmShOwcA==`;
const constent = {
phone: "8000000001",
firstName: "vickie",
id: "1234XXXX4567",
data: {
timestamp: 1572321851823,
inter1: ["123123123", "123123123", "123123123", "123123123", "123123123"],
inter2: ["123123123", "123123123", "123123123", "123123123", "123123123"],
inter3: ["123123123", "123123123", "123123123", "123123123", "123123123"],
inter4: ["123123123", "123123123", "123123123", "123123123", "123123123"],
inter5: ["123123123", "123123123", "123123123", "123123123", "123123123"],
inter6: ["123123123", "123123123", "123123123", "123123123", "123123123"],
stream: {},
caton: {},
card: []
}
};
const encryptResult = encryptLongByRSA(JSON.stringify(constent), PUBLIC_KEY);
const decryptResult = decryptLongFromRSA(encryptResult, PRIVATE_KEY);
import bcrypt from 'bcryptjs';
// 同步实现hash加密:每一次生成的hash都会不一样, cost数字越大,哈希值就越安全,但性能会差一点
const encryptHash = (plaintextPassword, cost) => {
const salt = bcrypt.genSaltSync(cost);
const hash = bcrypt.hashSync(plaintextPassword, salt);
return hash;
};
// 同步验证
const isPass = (plaintextPassword, hash) => {
return bcrypt.compareSync(plaintextPassword, hash);
};
// 测试
const passwordHash = encryptHash("test", 10);
console.log(passwordHash, isPass("test", passwordHash)); // 生成的不可逆密文 true