对于新手在网页开发的时候,总会遇到对数据加密的误解,尤其是刚开始接触相关代码看了一些用例之后90%的人免不了会想过一个问题,就是前后端加解密是为了什么。
例如下面的代码
- 后端加密代码
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class EncryptionUtil {
//加密使用AES算法、加密的秘钥Key
private static final String ALGORITHM = "AES/CBC/PKCS7Padding";
private static final String KEY = "mysecretkey12345";
public static String encrypt(String value) throws Exception {
//生成密钥的规范类,传入Key和使用的算法
SecretKeySpec secretKeySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
//获取对应加密算法的执行类
Cipher cipher = Cipher.getInstance(ALGORITHM);
//初始化,指定运行模式为加密,并传入加密规范类
IvParameterSpec iv = new IvParameterSpec(KEY.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec,iv);
//加密数据,并把加密后的数据使用base64第二次加密
byte[] encryptedValue = cipher.doFinal(value.getBytes());
return Base64.getEncoder().encodeToString(encryptedValue);
}
public static String decrypt(String value) throws Exception {
SecretKeySpec secretKeySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM + "/CBC/PKCS7Padding");
//指定运行模式为解密
IvParameterSpec iv = new IvParameterSpec(KEY.getBytes());
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv);
byte[] decodedValue = Base64.getDecoder().decode(value);
byte[] decryptedValue = cipher.doFinal(decodedValue);
return new String(decryptedValue);
}
}
- 前端加密代码
function encrypt(value) {
//加密的key、初始化向量iv
var key = CryptoJS.enc.Utf8.parse('mysecretkey12345');
//向量可以和key不同,iv只是用来决定加密算法内部在加密时的加密强度
var iv = CryptoJS.enc.Utf8.parse('mysecretkey12345');
var encrypted = CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(value), key,
{
keySize: 16,
iv: iv,
//加解密用到的模式
mode: CryptoJS.mode.CBC,
//填充模式,原始数据的长度不是加密算法所要求的块长度的整数倍,就需要进行填充
padding: CryptoJS.pad.Pkcs7
});
return encrypted.toString(CryptoJS.enc.Base64);
}
function decrypt(value) {
var key = CryptoJS.enc.Utf8.parse('mysecretkey12345');
var iv = CryptoJS.enc.Utf8.parse('mysecretkey12345');
var decryptedStr = CryptoJS.enc.Base64.parse(value).toString(CryptoJS.enc.Utf8);
var decrypted = CryptoJS.AES.decrypt(decryptedStr, key,
{
keySize: 16,
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return decrypted.toString(CryptoJS.enc.Utf8);
}
- 使用示例
后端加密:
String plainText = "Hello World!";
String encryptedText = EncryptionUtil.encrypt(plainText);
System.out.println("加密数据为: " + encryptedText);
后端解密:
String encryptedText = "加密数据";
String decryptedText = EncryptionUtil.decrypt(encryptedText);
System.out.println("解密数据为: " + decryptedText);
前端加密:
var plainText = "Hello World!";
var encryptedText = encrypt(plainText);
console.log("加密数据为: " + encryptedText);
前端解密:
var encryptedText = "解密数据";
var decryptedText = decrypt(encryptedText);
console.log("解密数据: " + decryptedText);
上面的代码就是一个用在前后端数据交互时使用的AES加解密代码,很多新手一定想过前端加密后传给后端,后端在解密,这一套流程到的是为了什么,直观的来看这一套流程有点多此一举,但其实前后端数据交互时的加密,不是给前端或者后端自己使用的,而是为了不让数据明文形式在网络层传输
,在黑客技术中,有一种攻击手段叫做中间人攻击
,能够在客户端与服务端通讯的网络层中截取并篡改数据,从而达到攻击目的,因此数据的加密是为了不让重要数据在网络层被截取后又被有心人直接使用。
但是上面这种加密手段不全面,它有两个弊端,第一个是如果攻击者的目的不是数据本身的内容,而只是为了污染你的网站,那就没辙,比如数据被截取之后不管你加密与否,直接使用其他的脏数据代替。第二个是数据加密的一个通病就是Key如何保证安全的问题,因为密钥不能放在客户端,不然很容易就丢了,而如果放在服务端,那也就意味着客户端使用的时候需要去访问,而在访问的时候,就要面对网络层中间人攻击,而你的密钥本身不可能加密,不然就成无限套娃了,也就是说如果你不使用相应措施的情况下,自己书写前后端数据的加密key的丢失只是个时间问题。
对于第一个问题,在加密算法的发展中,出现了另外一种加密流派,叫做数据签名
,核心思想是挑选重要数据进行加密组成签名数据,后端重组重要数据的签名,与最开始的签名做校验。而第二个问题,解决上最稳妥方法就是使用https协议,依靠协议本身的安全性解决这个问题。而且当你使用HTTP协议之后,前后端数据交互时的加密,就不再需要自己去写了,协议本身就给你提供了这种安全性,这时就可以把原本的网络层加密用在对客户端或者服务端自身数据的加密上,同时使用局部变量无法被客户端监听的特性保证密钥的安全。