在 HTTP 协议下,数据是明文传输,传输过程中网络嗅探可直接获取其中的数据。 如用户的密码和信用卡相关的资料,一旦被中间人获取,会给用户带来极大的安全隐患。另一方面在非加密的传输过程中,攻击者可更改数据或插入恶意的代码等。那么前端加密的意义: 即在数据发送前将数据进行哈希或使用公钥加密。如果数据被中间人获取,拿到的则不再是明文。
当然还有其他一些优点:例如避免后端等打印日志直接暴露明文密码,还可以避免明文撞库等.
加密是指将明文通过加密算法和加密密钥转为密文
解密是指将密文通过解密算法和解密密钥转为明文
前端加密的几种做法
? JavaScript 加密后传输(具体可以参考后面的常见加密方法)
? 浏览器插件内进行加密传输 (这个用得不是很多,这里暂不细究)
? Https 传输
分类
常见加密方式分为以下两类:
- 对称加密:采用对称密码编码技术,加/解密使用相同密钥进行,效率较高。
- 非对称加密:基于密钥交换协议,拥有公开密钥和私有密钥,使用公钥加密后需使用对应私钥才能进行解密。
Tips:
MD5、SHA256等一般称为数据摘要算法(采用哈希算法),即将数据块映射为定长的摘要信息,过程是单向的(无法反推原数据),一般用于数据验签(校验数据是否完整或校验数据是否被篡改)。
- 常见对称加密有AES、DES、3DES等,这里选用AES实现。
- 常见非对称加密有RSA、ECC等,这里选用RSA实现。
加密算法
不同于哈希(后面会提到),加密(Encrypt)是将目标文本转换成具有不同长度的、可逆的密文。也就是说加密算法是可逆的,而且其加密后生成的密文长度和明文本身的长度有关。所以如果被保护数据在以后需要被还原成明文,则需要使用加密。
在加密算法中又分为对称加密(symmetric encryption)和非对称加密(asymmetric encryption)。
对称加密(Symmetric Cryptography)
对称加密是最快速、最简单的一种加密方式,加密(encryption)与解密(decryption)用的是同样的密钥(secret key)。对称加密有很多种算法,由于它效率很高,所以被广泛使用在很多加密协议的核心当中。
对称加密通常使用的是相对较小的密钥,一般小于256 bit。因为密钥越大,加密越强,但加密与解密的过程越慢。如果你只用1bit来做这个密钥,那黑客们可以先试着用0来解密,不行的话就再用1解;但如果你的密钥有1MB大,黑客们可能永远也无法破解,但加密和解密的过程要花费很长的时间。密钥的大小既要照顾到安全性,也要照顾到效率,是一个trade-off。
2000年10月2日,美国国家标准与技术研究所(NIST-- American National Institute of Standards and Technology)选择了Rijndael算法作为新的高级加密标准(AES-- Advanced Encryption Standard)。.NET中包含了Rijndael算法,类名叫RijndaelManaged,下面举个例子。
非对称加密(Asymmetric Cryptography)
非对称加密为数据的加密与解密提供了一个非常安全的方法,它使用了一对密钥,公钥(public key)和私钥(private key)。私钥只能由一方安全保管,不能外泄,而公钥则可以发给任何请求它的人。非对称加密使用这对密钥中的一个进行加密,而解密则需要另一个密钥。比如,你向银行请求公钥,银行将公钥发给你,你使用公钥对消息加密,那么只有私钥的持有人--银行才能对你的消息解密。与对称加密不同的是,银行不需要将私钥通过网络发送出去,因此安全性大大提高。
目前最常用的非对称加密算法是RSA算法,是Rivest, Shamir, 和Adleman于1978年发明,他们那时都是在MIT。.NET中也有RSA算法,请看下面的例子:
对称加密DES实现方式
1、使用Crypto-JS通过DES算法在前端加密
npm install crypto-js
2、加解密 使用DES算法,工作方式为ECB,填充方式为PKcs7
var CryptoJS = require("crypto-js");
const secretKey = 'com.sevenlin.foo.key';
var afterEncrypt = CryptoJS.DES.encrypt('encryptCode', CryptoJS.enc.Utf8.parse(secretKey), {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}).toString()
console.log(afterEncrypt);//8/nZ2vZXxOzPhU7ZHBwz7w==
var afterDecrypt = CryptoJS.DES.decrypt(afterEncrypt, CryptoJS.enc.Utf8.parse(secretKey), {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}).toString(CryptoJS.enc.Utf8);
console.log(afterDecrypt);//encryptCode
3、使用BC通过DES算法在后端解密
3.1.安装
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<version>1.46</version>
</dependency>
3.2.加解密工具
public class DesCipherUtil {
private DesCipherUtil() {
throw new AssertionError("No DesCipherUtil instances for you!");
}
static {
// add BC provider
Security.addProvider(new BouncyCastleProvider());
}
/**
* 加密
*
* @param encryptText 需要加密的信息
* @param key 加密密钥
* @return 加密后Base64编码的字符串
*/
public static String encrypt(String encryptText, String key) {
if (encryptText == null || key == null) {
throw new IllegalArgumentException("encryptText or key must not be null");
}
try {
DESKeySpec desKeySpec = new DESKeySpec(key.getBytes());
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = secretKeyFactory.generateSecret(desKeySpec);
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS7Padding", "BC");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] bytes = cipher.doFinal(encryptText.getBytes(Charset.forName("UTF-8")));
return Base64.getEncoder().encodeToString(bytes);
} catch (NoSuchAlgorithmException | InvalidKeyException | InvalidKeySpecException | NoSuchPaddingException
| BadPaddingException | NoSuchProviderException | IllegalBlockSizeException e) {
throw new RuntimeException("encrypt failed", e);
}
}
/**
* 解密
*
* @param decryptText 需要解密的信息
* @param key 解密密钥,经过Base64编码
* @return 解密后的字符串
*/
public static String decrypt(String decryptText, String key) {
if (decryptText == null || key == null) {
throw new IllegalArgumentException("decryptText or key must not be null");
}
try {
DESKeySpec desKeySpec = new DESKeySpec(key.getBytes());
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = secretKeyFactory.generateSecret(desKeySpec);
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS7Padding", "BC");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] bytes = cipher.doFinal(Base64.getDecoder().decode(decryptText));
return new String(bytes, Charset.forName("UTF-8"));
} catch (NoSuchAlgorithmException | InvalidKeyException | InvalidKeySpecException | NoSuchPaddingException
| BadPaddingException | NoSuchProviderException | IllegalBlockSizeException e) {
throw new RuntimeException("decrypt failed", e);
}
}
}
3.3、解密前端的加密信息
String fromWeb = "8/nZ2vZXxOzPhU7ZHBwz7w==";
String key = "com.sevenlin.foo.key";
String afterDecrypt = DesCipherUtil.decrypt(fromWeb, key);
System.out.println(afterDecrypt);//encryptCode
非对称加密
(1)下载加密文件依赖 (2) 引入依赖
<script src="bin/jsencrypt.min.js"></script>
或者
import '../../assets/js/jsencrypt.min.js';
(3)使用方式
this.privateKey=`-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDlOJu6TyygqxfWT7eLtGDwajtNFOb9I5XRb6khyfD1Yt3YiCgQ
WMNW649887VGJiGr/L5i2osbl8C9+WJTeucF+S76xFxdU6jE0NQ+Z+zEdhUTooNR
aY5nZiu5PgDB0ED/ZKBUSLKL7eibMxZtMlUDHjm4gwQco1KRMDSmXSMkDwIDAQAB
AoGAfY9LpnuWK5Bs50UVep5c93SJdUi82u7yMx4iHFMc/Z2hfenfYEzu+57fI4fv
xTQ//5DbzRR/XKb8ulNv6+CHyPF31xk7YOBfkGI8qjLoq06V+FyBfDSwL8KbLyeH
m7KUZnLNQbk8yGLzB3iYKkRHlmUanQGaNMIJziWOkN+N9dECQQD0ONYRNZeuM8zd
8XJTSdcIX4a3gy3GGCJxOzv16XHxD03GW6UNLmfPwenKu+cdrQeaqEixrCejXdAF
z/7+BSMpAkEA8EaSOeP5Xr3ZrbiKzi6TGMwHMvC7HdJxaBJbVRfApFrE0/mPwmP5
rN7QwjrMY+0+AbXcm8mRQyQ1+IGEembsdwJBAN6az8Rv7QnD/YBvi52POIlRSSIM
V7SwWvSK4WSMnGb1ZBbhgdg57DXaspcwHsFV7hByQ5BvMtIduHcT14ECfcECQATe
aTgjFnqE/lQ22Rk0eGaYO80cc643BXVGafNfd9fcvwBMnk0iGX0XRsOozVt5Azil
psLBYuApa66NcVHJpCECQQDTjI2AQhFc1yRnCU/YgDnSpJVm1nASoRUnU8Jfm3Oz
uku7JUXcVpt08DFSceCEX9unCuMcT72rAQlLpdZir876
-----END RSA PRIVATE KEY-----`;
this.publicKye=`-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDlOJu6TyygqxfWT7eLtGDwajtN
FOb9I5XRb6khyfD1Yt3YiCgQWMNW649887VGJiGr/L5i2osbl8C9+WJTeucF+S76
xFxdU6jE0NQ+Z+zEdhUTooNRaY5nZiu5PgDB0ED/ZKBUSLKL7eibMxZtMlUDHjm4
gwQco1KRMDSmXSMkDwIDAQAB
-----END PUBLIC KEY-----`;
var encrypt = new JSEncrypt();
encrypt.setPublicKey(this.publicKye);
var encrypted = encrypt.encrypt('encryptCode');//w1a1FXmlbFj9yOxLCoqIzNo2ytXypyupZABsi/e4kMA9mERngmaDwlOuHsUDQKC0nK1v7Ehr3vYKcALFQvjscWEkGIW/UWCk73jArwqEYF1wd45eHSCPwUeB85Ellr+IYTqhZXcfmHZUCuprF2gayPUecq7F51aWxpfqMP0uvtY=
// Decrypt with the private key...
var decrypt = new JSEncrypt();
decrypt.setPrivateKey(this.privateKey);
var uncrypted = decrypt.decrypt(encrypted);//encryptCode
生成publickey和privateKey的在线地址
虽然前端可以加密,终归不是安全方式,如果为了更加的安全还是使用https传输,后端加密保存吧!
常用加密调用方式
哈希算法(Hash)
哈希(Hash)是将目标文本转换成具有固定长度的字符串(或叫做消息摘要)。 当输入发生改变时,产生的哈希值也是完全不同的。从数学角度上讲,一个哈希算法是一个多对一的映射关系,对于目标文本 T,算法 H 可以将其唯一映射为R,并且对于所有的T,R具有相同的长度,所以 H 不存在逆映射,也就是说哈希算法是不可逆的。
- 基于哈希算法的特性,其适用于该场景:被保护数据仅仅用作比较验证且不需要还原成明文形式。比较常用的哈希算法是 MD5 和 SHA1 。
- 我们比较熟悉的使用哈希存储数据的例子是:当我们登录某个已注册网站时,在忘记密码的情况下需要重置密码,此时网站会给你发一个随机的密码或者一个邮箱激活链接,而不是将之前的密码发给你,这就是因为哈希算法是不可逆的。
需要注意的是:在 Web 应用中,在浏览器中使用哈希加密的同时也要在服务端上进行哈希加密。
服务端哈希加密原因: 一方面因为不需要将密文解密成明文来比对密码,另一方面是一旦加密算法和密钥泄露,那么整个用户资料库就相当于明文存储了。如果前端传过来的是明文,那么在注册时将其哈希,存入数据库。登录时,将密码哈希和数据库对应的数据比对,若一致则说明密码正确。
现在,对于简单的哈希算法的攻击方法主要有:寻找碰撞法和穷举法。所以,为了保证数据的安全,可以在哈希算法的基础上进一步的加密,常见的方法有:加盐、慢哈希、密钥哈希、XOR 等。
加盐(Adding Salt)
加盐加密是一种对系统登录口令的加密方式,它实现的方式是将每一个口令同一个叫做“盐”(salt)的 n 位随机数相关联。
使用salt加密,它的基本想法是这样的:
- 用户注册时,在密码上撒一些盐。生成一种味道,记住味道。
- 用户再次登陆时,在输入的密码上撒盐,闻一闻,判断是否和原来的味道相同,相同就让你吃饭。
注册时:
- 用户注册,系统随机产生salt值。
- 将salt值和密码连接起来,生产Hash值。
- 将Hash值和salt值分别存储在数据库中。
登陆时:
- 系统根据用户名找到与之对应的密码Hash。
- 将用户输入密码和salt值进行散列。
- 判断生成的Hash值是否和数据库中Hash相同。
慢哈希函数(Slow Hash Function)
顾名思义,慢哈希函数是将哈希函数变得非常慢,使得攻击方法也变得很慢,慢到足以令攻击者放弃,而往往由此带来的延迟也不会引起用户的注意。降低攻击效率用到了密钥扩展( key stretching)的技术,而密钥扩展的实现使用了一种CPU 密集型哈希函数( CPU-intensive hash function)。看起来有点晕~还是关注下该函数怎么用吧!
如果想在一个 Web应用中使用密钥扩展,则需要设定较低的迭代次数来降低额外的计算成本。我们一般直接选择使用标准的算法来完成,比如PBKDF2或bcrypt。PHP、斯坦福大学的 JavaScript加密库都包含了 PBKDF2的实现,浏览器中则可以考虑使用 JavaScript 完成,否则这部分工作应该由服务端进行计算。
md5加密:md5.js
MD5.js 共有6中加密方法:hex_md5(s), b64_md5(s), str_md5(s), hex_hmac_md5(key, data), b64_hmac_md5(key, data), str_hmac_md5(key, data)
使用
<script src="js/md5.js" type="text/javascript"></script>
<script>
var pwd = hex_md5("Victor");
console.log(pwd);
//123
</script>
base64加密:base64.js
<script src="js/base64.js"></script>
<script>
var str = b.encode("admin:admin"); //加密
var str1 = b.decode(str); //解密
console.log(str);
console.log(str1);
//12345
</script>
sha1加密:sha1.js
<script src="js/sha1.js"></script>
<script>
var sha = hex_sha1('mima123465');
console.log(sha);
//123
</script>
RSA加密:jsencrypt.js
<script src="js/jsencrypt.js"></script>
<script>
var str = "密码";
// 公钥
var publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCfp4T5UK76SfSvn1wr4+PyStkHWONLVITZp5JLkFkHpiERchwShET+WVlLsbbgyt7Yt/boMoxr+XTD2NXd1gPvq11OU3dNYLE5hL2j8BCBw8EswkCbP+GgYdGF3FRw4eGURA4fcSO44IKuWtmtSyw7y1OMqzMUb6PROXrMFbrntQIDAQAB";
// 私钥
var privateKey = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAJ+nhPlQrvpJ9K+fXCvj4/JK2QdY40tUhNmnkkuQWQemIRFyHBKERP5ZWUuxtuDK3ti39ugyjGv5dMPY1d3WA++rXU5Td01gsTmEvaPwEIHDwSzCQJs/4aBh0YXcVHDh4ZREDh9xI7jggq5a2a1LLDvLU4yrMxRvo9E5eswVuue1AgMBAAECgYBBGEtmX9hIIplKdf5dwtgw9QWSMlLSZw6Xs/Q5LQyvyrOW6mC5yAZdck6vRzFabDz6Pd/FAr8wUxChvapNfbIyhCsyAuqjVWV/aGDhaauQvwm3MrZ33jnMxI1z9UXPHTzWrYOltv/cKVqvDTKzEokDoTzEVLQClaYcEVMwhRrpYQJBANR48h9o1s7y0sAOoVBo5ktGZhb6gHtwNJ4Yw7Aey2bAWjgU4PevUq985+jBWhdAj81dZ0lMwARBIcYzyo0RLMkCQQDAXIup+fViw0BD+gl36kOo5RkTgz3aoALJsDBLGWi2J9QY9i0UZO9LsjNlYlTldKRzJWkhjdAdxuZe+8Mr8tWNAkB39HB92yqrW9wwIhJg21hpVy17+6FeD1MljsWsQTaEpERLNT5tl8YO3W3wsniyd/i38OvHLlWaEUuIIuVQD0pBAkAFXk/ThZNQl0xi2UaMXzBWNVaW0dHXGSIayN0AK4xdkQz9SVmGuS5oeWevy6e84513TTL66b9GJXfmbLZMsrE9AkAXuppwZW4I/xWZ2RaIsKqyyYYJUPbZ9HT3iO6FSUVPskzaOIPgU9sir+UcvJfWgXDWQ2F/4uUogB2ZS45YcFM8";
// 加密
var encrypt = new JSEncrypt();
encrypt.setPublicKey(publicKey);
var encryptStr = encrypt.encrypt(str);
console.log("加密后:" + encryptStr);
// 解密
var decrypt = new JSEncrypt();
decrypt.setPrivateKey(privateKey);
var decryptStr = decrypt.decrypt(encryptStr);
console.log( "解密后:" + decryptStr);
</script>