目录
一、后端实现
二、前端实现(Vue2)
三、补充
1.增强安全措施
四、最后阐明
步调大致如下:
- 后端天生RSA密钥对,提供公钥接口。
- 前端哀求公钥,天生随机AES密钥和IV。
- 用RSA公钥加密AES密钥,用AES密钥加密数据。
- 发送包罗加密后的AES密钥和数据的哀求体。
- 后端用RSA私钥解密AES密钥,再用AES密钥解密数据。
- 利用注解和拦截器主动处明白密过程。
须要确保每个步调都精确实现,特殊是加密模式、填充方式以及编码解码的同等性,避免因设置差别导致解密失败。有什么没参加的在批评区艾特我,我进行补充
一、后端实现
- public class AesUtils {
- public static String encrypt(String data, String key, String iv) throws Exception {
- SecretKeySpec keySpec = new SecretKeySpec(
- Base64.getDecoder().decode(key), "AES"
- );
- IvParameterSpec ivSpec = new IvParameterSpec(
- Base64.getDecoder().decode(iv)
- );
- Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
- cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
- byte[] encrypted = cipher.doFinal(data.getBytes());
- return Base64.getEncoder().encodeToString(encrypted);
- }
- public static String decrypt(String data, String key, String iv) throws Exception {
- byte[] keyBytes = Base64.getDecoder().decode(key);
- byte[] ivBytes = Base64.getDecoder().decode(iv);
- byte[] encryptedBytes = Base64.getDecoder().decode(data);
- Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
- SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
- IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
- cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
- return new String(cipher.doFinal(encryptedBytes));
- }
- }
复制代码
- /**
- * 解密切面
- */
- @ControllerAdvice
- public class DecryptAdvice extends RequestBodyAdviceAdapter {
- private final RsaKeyManager keyManager;
- public DecryptAdvice(RsaKeyManager keyManager) {
- this.keyManager = keyManager;
- }
- @Override
- public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
- return methodParameter.hasMethodAnnotation(NeedDecrypt.class);
- }
- @Override
- public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage,
- MethodParameter parameter,
- Type targetType,
- Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
- try {
- String encryptedBody = new String(inputMessage.getBody().readAllBytes());
- JSONObject json = JSONObject.parseObject(encryptedBody);
- // 解密 AES 密钥
- String encryptedAesKey = json.getString("encryptedKey");
- String aesKey = RsaUtils.decryptByPrivateKey(encryptedAesKey, keyManager.getPrivateKey());
- // 解密数据
- String decryptedData = AesUtils.decrypt(
- json.getString("encryptedData"),
- aesKey,
- json.getString("iv")
- );
- return new DecryptedHttpInputMessage(
- new ByteArrayInputStream(decryptedData.getBytes()),
- inputMessage.getHeaders()
- );
- } catch (Exception e) {
- throw new RuntimeException("解密失败", e);
- }
- }
- }
复制代码
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface NeedDecrypt {
- }
复制代码
- /**
- * 生成RSA
- */
- public class RsaKeyManager {
- Logger log = LoggerFactory.getLogger(RsaKeyManager.class);
- private final RedisService redisService;
- // Redis 键名
- private static final String PUBLIC_KEY = "rsa:public";
- private static final String PRIVATE_KEY = "rsa:private";
- public RsaKeyManager(RedisService redisService) {
- this.redisService = redisService;
- }
- /**
- * 初始化密钥(全局唯一)
- */
- @PostConstruct
- public void initKeyPair() throws Exception {
- // 使用 SETNX 原子操作确保只有一个服务生成密钥
- Boolean isAbsent = redisService.setIfAbsent(PUBLIC_KEY, "");
- if (Boolean.TRUE.equals(isAbsent)) {
- KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
- generator.initialize(2048);
- KeyPair keyPair = generator.generateKeyPair();
- // 存储密钥
- redisService.set(PUBLIC_KEY,
- Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded())
- );
- redisService.set(PRIVATE_KEY,
- Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded())
- );
- log.info("---------------------------初始化RSA秘钥---------------------------");
- }
- }
- /**
- * 获取公钥
- */
- public String getPublicKey() {
- Object publicKey = redisService.get(PUBLIC_KEY);
- return Objects.isNull(publicKey)?null:publicKey.toString();
- }
- /**
- * 获取私钥
- */
- public String getPrivateKey() {
- Object privateKey = redisService.get(PRIVATE_KEY);
- return Objects.isNull(privateKey)?null:privateKey.toString();
- }
- }
复制代码
- 新增DecryptedHttpInputMessage
- public class DecryptedHttpInputMessage implements HttpInputMessage {
- private final InputStream body;
- private final HttpHeaders headers;
- public DecryptedHttpInputMessage(InputStream body, HttpHeaders headers) {
- this.body = body;
- this.headers = headers;
- }
- @Override
- public InputStream getBody() throws IOException {
- return this.body;
- }
- @Override
- public HttpHeaders getHeaders() {
- return this.headers;
- }
- }
复制代码
- @RestController
- @RequestMapping("/rsa")
- public class RSAController {
- @Autowired
- private RsaKeyManager rsaKeyManager;
- /**
- * 获取公钥
- * @return 结果
- */
- @GetMapping("/publicKey")
- public R<String> getPublicKey() {
- String publicKey = rsaKeyManager.getPublicKey();
- return R.ok(publicKey);
- }
- }
复制代码 二、前端实现(Vue2)
- 加密工具(src/utils/crypto.js):
getPublicKey 为哀求公钥的接口,须要按照自己哀求方式去获取
- import JSEncrypt from 'jsencrypt'
- import CryptoJS from 'crypto-js'
- import { getPublicKey } from '../request/api/auth'
- // 初始化公钥
- export async function initPublicKey() {
- try {
- const res = await getPublicKey()
- return formatPublicKey(res.data)
- } catch (error) {
- console.error('公钥获取失败:', error)
- throw new Error('安全模块初始化失败')
- }
- }
- // 生成AES密钥
- export function generateAesKey() {
- const key = CryptoJS.lib.WordArray.random(32)
- const iv = CryptoJS.lib.WordArray.random(16)
- return {
- key: CryptoJS.enc.Base64.stringify(key),
- iv: CryptoJS.enc.Base64.stringify(iv)
- }
- }
- // AES加密
- export function aesEncrypt(data, key, iv) {
- const encrypted = CryptoJS.AES.encrypt(
- JSON.stringify(data),
- CryptoJS.enc.Base64.parse(key),
- {
- iv: CryptoJS.enc.Base64.parse(iv),
- mode: CryptoJS.mode.CBC,
- padding: CryptoJS.pad.Pkcs7
- }
- )
- return encrypted.toString()
- }
- // 格式化公钥
- function formatPublicKey(rawKey) {
- return `-----BEGIN PUBLIC KEY-----\n${wrapKey(rawKey)}\n-----END PUBLIC KEY-----`
- }
- // 每64字符换行
- function wrapKey(key) {
- return key.match(/.{1,64}/g).join('\n')
- }
复制代码
- service.interceptors.request.use(async config => {
- if (config.needEncrypt) {
- await initPublicKey(service)
-
- // 生成AES密钥
- const aes = generateAesKey()
-
- // 加密数据
- const encryptedData = aesEncrypt(config.data, aes.key, aes.iv)
-
- // 加密AES密钥
- const encryptor = new JSEncrypt()
- encryptor.setPublicKey(publicKey)
- const encryptedKey = encryptor.encrypt(aes.key)
-
- // 构造请求体
- config.data = {
- encryptedKey: encryptedKey,
- encryptedData: encryptedData,
- iv: aes.iv
- }
- }
- return config
- })
复制代码 三、补充
- @PostMapping("/secure-data")
- @NeedDecrypt
- public String handleSecureData(@RequestBody Map<String, Object> decryptedData) {
- return "Decrypted data: " + decryptedData.toString();
- }
复制代码
- {
- "encryptedKey": "RSA加密后的AES密钥",
- "encryptedData": "AES加密后的数据",
- "iv": "Base64编码的IV"
- }
复制代码 1.增强安全措施
- // 前端每次请求生成新密钥
- const aes = generateAesKey()
复制代码
- // 后端解密后可添加HMAC校验
- String hmac = json.getString("hmac");
- if(!verifyHMAC(decryptedData, hmac, aesKey)) {
- throw new SecurityException("Data tampered");
- }
复制代码
- // 前端添加时间戳和随机数
- config.data.timestamp = Date.now()
- config.data.nonce = Math.random().toString(36).substr(2)
复制代码 四、最后阐明
该方案相比纯RSA加密有以下优势:
- 性能提升:AES加密大数据效率比RSA高1000倍以上
- 前向安全性:每次哀求利用差别AES密钥
- 安全性增强:CBC模式+随机IV避免模式分析攻击
实际部署时需注意:
- 利用HTTPS传输加密后的数据
- 定期轮换RSA密钥对
- 对敏感接口添加频率限制
- 在网关层实现解密拦截器(而非应用层)
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |