场景
前后端使用HTTP协议进行交互的时候,由于HTTP报文为明文,所以通常情况下对于比较敏感的信息可以通过对称加密在前端加密,然后在后端解密实现”混淆”的效果,避免在传输过程中敏感信息的泄露(如,密码,证件信息等)。
前端加密的意义
为什么说”混淆”呢?因为我们知道只要不是服务端加密,那么在Web前端加密的都不能保证真正的加密:
- 攻击者可以通过前端源码知道加密算法进行破解
- 攻击者只要拿到加密后的密文请求服务端,服务端是无感知的
这里需要明确一个共识,服务端绝对不能相信前端传递过来的密文直接保存入库,只能通过服务端自己加密进行加密保存。
那么前端加密真的就没有意义么?对于安全级别来说其实意义不大,但是有以下好处:
- 前端加密增加攻击者的成本
- 传输过程中不是原始密码,避免在传输通道中密码泄露的风险
前端加密的争论其实一直都有,这里我也不做过多的讨论,如果前后端交互需要安全的通道可以使用HTTPS协议进行通信。
通过DES实现前后端加解密
使用Crypto-JS
通过DES算法在前端加密
安装
$ npm install crypto-js
加解密
使用DES算法,工作方式为ECB
,填充方式为PKcs7
var CryptoJS = require("crypto-js");
const secretKey = 'com.sevenlin.foo.key';
var afterEncrypt = CryptoJS.DES.encrypt('passwordtoecrypt', CryptoJS.enc.Utf8.parse(secretKey), {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}).toString()
console.log(afterEncrypt);//7D7RsPHmNSlBAaEVgEyE4aL0j1SJtFi9
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);//passwordtoecrypt
使用BC
通过DES算法在后端解密
安装
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<version>1.46</version>
</dependency>
加解密工具
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);
}
}
}
解密前端的加密信息
String fromWeb = "7D7RsPHmNSlBAaEVgEyE4aL0j1SJtFi9";
String key = "com.sevenlin.foo.key";
String afterDecrypt = DesCipherUtil.decrypt(fromWeb, key);
System.out.println(afterDecrypt);//passwordtoecrypt
总结
这里只是简单介绍下前端加密和后端解密的大概思路,使用DES算法实现的一个例子,虽然实际应用可能不是这么简单,但可以作为一个简单的DEMO来学习。