文章目录
- Pre
- 概述
- 前端加密是否有意义?
- 环境准备
- 加密方法、MODE和PADDING的选择
- 前端
- 后端
- 应用:从传输到解密的全过程
- 安全性增强
- 动态生成密钥和初始向量
- 1. 前端:动态生成密钥和IV
- 2. 后端:解密动态密钥和IV
- 结语
Pre
加密与安全_解密AES加密中的IV和Seed
加密与安全_双向RSA+AES加密及Code实现
加密与安全_常见的分组密码 ECB、CBC、CFB、OFB模式介绍
概述
当我们在前端和后端之间传输敏感信息时,安全性变得越来越重要。今天,我们将一起探索如何在前端加密密码,并在后端安全解密的过程。
使用Vue.js作为前端框架,并通过Java来处理后端解密。
前端加密是否有意义?
前端加密是否真的有意义?其实,这个问题的答案并不绝对。
- 一方面,前端加密能够一定程度上防止中间人攻击,从而保护用户的隐私。
- 但另一方面,如果攻击者能够篡改前端代码,前端加密就形同虚设。
因此,前端加密到底值不值得,还是见仁见智。
环境准备
前端使用的是Vue.js,也可以选择纯JS。同时,我们引入crypto-js
库。至于后端,Java将担任这次任务的重任。
为了让前端和后端能够顺利交流,我们必须确保前后端使用相同的加密方式、模式(MODE)和填充方式(PADDING)。
加密方法、MODE和PADDING的选择
加密方法、MODE和PADDING之间也必须配合得天衣无缝。前后端的这些参数必须一致,才能确保传输的信息能够正确加密和解密。
必须选择前后端都支持的加密方法、模式和填充方式。
https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
https://cryptojs.gitbook.io/docs/#pbkdf2
以下是\选择的参数:
- 加密方法: AES(推荐加密方法,参考:FIPS 197)
- 模式: CBC(默认模式,参考:FIPS 81)
- 填充方式: Pkcs7(与Java的PKCS5Padding基本等价)
在这里,我们选择AES/CBC/Pkcs7
作为加密组合,而Java端则使用AES/CBC/PKCS5Padding
。这样一来,我们的前后端就可以在同一个频道上交流了。
前端
需要引入crypto-js
库,并设置好密钥(key)和初始向量(iv)。这些设置必须与后端保持一致,才能确保加密与解密的正确性。
// aesutils.js
import CryptoJs from 'crypto-js'
// 把key、iv设置成固定值,前后端的值要一致
let key = CryptoJs.enc.Utf8.parse("xxxxxx");
let iv = CryptoJs.enc.Utf8.parse("yyyyyy");
export function Encrypt(word) {
let srcs = CryptoJs.enc.Utf8.parse(word);
var encrypted = CryptoJs.AES.encrypt(srcs, key, {
iv: iv,
mode: CryptoJs.mode.CBC,
padding: CryptoJs.pad.Pkcs7
});
return CryptoJs.enc.Base64.stringify(encrypted.ciphertext);
}
在这个加密函数中,我们将原始文本转换为Base64编码后的密文,然后将其发送到后端。如此一来,前端的密码在传输过程中便不会以明文形式暴露。
后端
后端的任务是接收到前端传来的密文,并将其解密为原始的明文密码。这一步同样至关重要,因为后端需要将这些密码存储或用于后续操作。
import org.apache.tomcat.util.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
public class AESUtils {
private static final String key = "xxxxxx";
private static final String iv = "yyyyyy";
public static String deocdeStr(String text) {
try {
byte[] encrypted = new Base64().decode(text);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding ");
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
IvParameterSpec ivParameter = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameter);
byte[] decrypted = cipher.doFinal(encrypted);
String originalString = new String(decrypted, StandardCharsets.UTF_8);
return originalString;
} catch (Exception e) {
return e.toString();
}
}
}
后端解密的核心步骤与前端加密紧密关联。首先,我们将Base64编码的密文解码为字节数组,然后利用AES/CBC/PKCS5Padding的组合将其解密为原始字符串。
应用:从传输到解密的全过程
在前端,我们使用工具类加密密码,然后通过axios
等方法将加密后的密文传输到后端:
import { Encrypt } from "@/utils/aesutils";
Encrypt(this.$data.formData.password);
在后端,接收到加密的密文后,我们调用工具类解密并还原密码,然后将其用于后续的数据库操作:
AESUtils.deocdeStr(user.getPassword());
安全性增强
尽管我们使用了固定的密钥和初始向量(IV),但在实际的生产环境中,这种方式的安全性可能还不够强。接下来,我们将进一步提升安全性,探索如何使用动态生成的密钥和初始向量,并研究在生产环境中如何更好地管理这些关键要素。
动态生成密钥和初始向量
使用固定的密钥和IV,虽然简化了前后端的协调工作,但也带来了风险。一旦密钥和IV被泄露,攻击者就可以轻松解密传输的数据。为了解决这个问题,我们可以采取动态生成密钥和IV的方法。
1. 前端:动态生成密钥和IV
在前端,我们可以在每次需要加密时,动态生成密钥和IV。然后,将密钥和IV与加密后的数据一起发送到后端。为了避免密钥和IV直接暴露在网络传输中,我们可以将它们与加密数据一起加密或使用安全的密钥交换算法。
import CryptoJs from 'crypto-js';
// 生成随机密钥和IV
function generateKeyAndIV() {
let key = CryptoJs.lib.WordArray.random(128 / 8); // 128-bit key
let iv = CryptoJs.lib.WordArray.random(128 / 8); // 128-bit IV
return { key, iv };
}
// 加密函数
export function encryptWithDynamicKey(word) {
const { key, iv } = generateKeyAndIV();
let srcs = CryptoJs.enc.Utf8.parse(word);
let encrypted = CryptoJs.AES.encrypt(srcs, key, {
iv: iv,
mode: CryptoJs.mode.CBC,
padding: CryptoJs.pad.Pkcs7
});
// 将密钥和IV与加密后的数据一起编码
return {
ciphertext: CryptoJs.enc.Base64.stringify(encrypted.ciphertext),
key: CryptoJs.enc.Base64.stringify(key),
iv: CryptoJs.enc.Base64.stringify(iv)
};
}
generateKeyAndIV
函数生成了一个随机的密钥和IV。然后,我们将这些随机生成的密钥和IV与加密后的数据一起编码并发送给后端。
2. 后端:解密动态密钥和IV
在后端接收到加密数据和动态生成的密钥、IV后,我们需要解码并使用它们进行解密操作。
import org.apache.tomcat.util.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
public class AESUtils {
public static String decryptWithDynamicKey(String ciphertext, String keyBase64, String ivBase64) {
try {
byte[] encrypted = new Base64().decode(ciphertext);
byte[] key = new Base64().decode(keyBase64);
byte[] iv = new Base64().decode(ivBase64);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameter = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameter);
byte[] decrypted = cipher.doFinal(encrypted);
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
return e.toString();
}
}
}
这个解密函数接受Base64编码的密文、密钥和IV。我们先将它们解码为字节数组,然后使用它们来解密密文,恢复原始数据。
结语
通过动态生成密钥和初始向量,并使用安全的密钥管理服务,我们可以大幅提升系统的安全性。这不仅能防止密钥泄露带来的风险,还能通过安全的密钥交换和管理,确保前后端通信的绝对安全。
在实际的生产环境中,密钥的管理和轮换至关重要,建议使用专业的KMS来简化并加强密钥管理工作。通过这些措施,可以构建一个更加安全和稳健的系统 。