news 2026/6/26 6:12:17

AES + RSA 混合加密方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AES + RSA 混合加密方案

AES + RSA 混合加密方案

一、架构概览

核心思想:RSA 只用于密钥交换(传输 AES 密钥),AES-GCM 负责所有消息内容的加解密。非对称 + 对称混合,兼顾安全与性能。


二、算法参数

参数说明
RSA 密钥长度2048 bit仅用于密钥交换
RSA 填充模式OAEP-SHA256比 PKCS1v1.5 更安全,抗选择密文攻击
AES 密钥长度128 bit(16字节)GCM 模式,每次加密随机生成 IV
AES 模式AES/GCM/NoPadding自带认证标签,防篡改
GCM IV 长度12 字节(96 bit)每次加密随机生成
GCM Tag 长度128 bit认证标签,防篡改
密文格式IV(12B) + 密文 + Tag(16B)拼接后统一 Base64 编码

三、后端核心代码

3.1 工具类 —CryptoUtils.java

talk-common/src/main/java/com/talk/common/utils/CryptoUtils.java

// ========== RSA 密钥对生成 ==========publicstaticKeyPairgenerateRsaKeyPair(){returnSecureUtil.generateKeyPair("RSA",2048);}// ========== RSA-OAEP 加密(公钥加密 AES 密钥) ==========privatestaticfinalStringRSA_ALGORITHM="RSA/ECB/OAEPWithSHA-256AndMGF1Padding";publicstaticStringrsaEncrypt(Stringdata,StringpublicKey){PublicKeykey=decodePublicKey(publicKey);Ciphercipher=Cipher.getInstance(RSA_ALGORITHM);OAEPParameterSpecspec=newOAEPParameterSpec("SHA-256","MGF1",MGF1ParameterSpec.SHA256,PSource.PSpecified.DEFAULT);cipher.init(Cipher.ENCRYPT_MODE,key,spec);byte[]encrypted=cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));returnBase64.encode(encrypted);}// ========== AES-GCM 加密 ==========publicstaticStringaesEncrypt(Stringplaintext,StringaesKey){byte[]keyBytes=Base64.decode(aesKey);byte[]iv=newbyte[12];// 12字节随机 IVnewSecureRandom().nextBytes(iv);Ciphercipher=Cipher.getInstance("AES/GCM/NoPadding");GCMParameterSpecspec=newGCMParameterSpec(128,iv);// 128-bit Tagcipher.init(Cipher.ENCRYPT_MODE,newSecretKeySpec(keyBytes,"AES"),spec);byte[]encrypted=cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));// IV + 密文 拼接后 Base64byte[]result=newbyte[iv.length+encrypted.length];System.arraycopy(iv,0,result,0,iv.length);System.arraycopy(encrypted,0,result,iv.length,encrypted.length);returnBase64.encode(result);}

3.2 密钥交换 —CryptoController.java

talk-chat/src/main/java/com/talk/chat/controller/CryptoController.java

步骤 1:获取公钥

@GetMapping("/public-key")publicAjaxResult<KeyExchangeResponse>getPublicKey(){KeyPairkeyPair=CryptoUtils.generateRsaKeyPair();StringpublicKeyBase64=CryptoUtils.encodePublicKey(keyPair.getPublic());StringprivateKeyBase64=CryptoUtils.encodePrivateKey(keyPair.getPrivate());// 完整 SHA-256 指纹,防碰撞Stringfingerprint=DigestUtil.sha256Hex(publicKeyBase64);// 私钥存 Redis(10分钟 TTL),key = rsa_keypair:{fingerprint}redisTemplate.opsForValue().set(Constants.REDIS_RSA_KEY_PREFIX+fingerprint,privateKeyBase64,10,TimeUnit.MINUTES);returnAjaxResult.success(newKeyExchangeResponse(publicKeyBase64,fingerprint));}

步骤 2:交换 AES 密钥

@PostMapping("/exchange")publicAjaxResult<String>exchangeKey(@Valid@RequestBodyKeyExchangeRequestrequest){// 从 Redis 取私钥StringprivateKeyBase64=redisTemplate.opsForValue().get(Constants.REDIS_RSA_KEY_PREFIX+request.getFingerprint());// RSA 解密得到 AES 密钥StringaesKey=CryptoUtils.rsaDecrypt(request.getEncryptedAesKey(),privateKeyBase64);// AES 密钥存 Redis(30分钟 TTL),key = aes_key:{userId}:{keyId}StringkeyId=UUID.randomUUID().toString().replace("-","").substring(0,16);redisTemplate.opsForValue().set(Constants.REDIS_AES_KEY_PREFIX+userId+":"+keyId,aesKey,30,TimeUnit.MINUTES);// 用完即弃:删除 RSA 私钥redisTemplate.delete(Constants.REDIS_RSA_KEY_PREFIX+request.getFingerprint());returnAjaxResult.success(keyId);}

3.3 加密过滤器 —CryptoFilter.java

talk-framework/src/main/java/com/talk/framework/filter/CryptoFilter.java

触发条件:请求头X-Encrypted: true

三层安全校验

① 时间戳校验 → |now - timestamp| < 5分钟(防过期请求) ② Nonce 校验 → Redis 查重,5分钟内不可重复(防重放) ③ AES 密钥 → 从 Redis 取,失效返回 401
@OverrideprotectedvoiddoFilterInternal(HttpServletRequestrequest,...){// 1. 时间戳容差校验(5分钟)longtimestamp=Long.parseLong(request.getHeader("X-Timestamp"));if(Math.abs(System.currentTimeMillis()-timestamp)>5*60*1000){returnerror("请求已过期");}// 2. Nonce 防重放(Redis 标记,5分钟 TTL)StringnonceKey="nonce:"+request.getHeader("X-Nonce");if(redisTemplate.hasKey(nonceKey)){returnerror("请求重复");// 检测到重放攻击}redisTemplate.opsForValue().set(nonceKey,"1",5,TimeUnit.MINUTES);// 3. 包装请求(解密)和响应(加密)CryptoRequestWrapperreqWrapper=newCryptoRequestWrapper(request,aesKey);CryptoResponseWrapperresWrapper=newCryptoResponseWrapper(response,aesKey);filterChain.doFilter(reqWrapper,resWrapper);resWrapper.finishResponse();// 加密响应体}

跳过加密的路径shouldNotFilter):/auth//crypto//upload//stream(SSE)、Swagger 文档等。


四、前端核心代码

4.1 加密工具 —crypto.js

talk-ui/src/api/crypto.js

// ========== AES-GCM 加密(Web Crypto API,浏览器原生) ==========exportasyncfunctionaesEncrypt(plaintext,aesKeyBase64){constkeyBytes=base64ToArrayBuffer(aesKeyBase64)constkey=awaitcrypto.subtle.importKey('raw',keyBytes,{name:'AES-GCM'},false,['encrypt'])constiv=crypto.getRandomValues(newUint8Array(12))// 12字节随机 IVconstencoded=newTextEncoder().encode(plaintext)constencrypted=awaitcrypto.subtle.encrypt({name:'AES-GCM',iv},key,encoded)// IV + 密文 拼接constresult=newUint8Array(iv.length+encrypted.byteLength)result.set(iv,0)result.set(newUint8Array(encrypted),iv.length)returnarrayBufferToBase64(result.buffer)}// ========== RSA-OAEP 加密(JSEncrypt 库)==========exportfunctionrsaEncrypt(data,publicKeyPem){constencryptor=newJSEncrypt()encryptor.setPublicKey(base64ToPem(publicKeyPem,'PUBLIC'))encryptor.setOptions({encryptionScheme:'pkcs1_oaep',// OAEP 填充,与后端一致signingScheme:'pkcs1v15'})returnencryptor.encrypt(data)}

4.2 密钥交换流程

exportasyncfunctionexchangeKeys(){// ① 获取服务端 RSA 公钥const{publicKey,fingerprint}=awaitfetchPublicKey()// ② 本地生成 AES-128 密钥constaesKey=awaitgenerateAesKey()// ③ RSA-OAEP 加密 AES 密钥,发送给服务端constencryptedAesKey=rsaEncrypt(aesKey,publicKey)constkeyId=awaitsendEncryptedAesKey(fingerprint,encryptedAesKey)// ④ 内存保存(不持久化,每次登录重新交换)tokenManager.setCryptoKey(keyId,aesKey)}

4.3 请求自动加解密 —request.js拦截

talk-ui/src/api/request.js

// 请求加密:自动注入 X-Encrypted / X-Key-Id / X-Nonce / X-Timestampconst{encrypted,headers}=awaitencryptRequest(requestData)// encrypted = { keyId: "xxx", data: "Base64密文" }// 响应解密:自动检测 EncryptedPayload 格式并解密constdecrypted=awaitdecryptResponse(data)

4.4 密钥生命周期 —token-manager.js

talk-ui/src/api/token-manager.js

// 内存存储(不持久化到 Storage)setCryptoKey(keyId,aesKey)// 登录后调用getCryptoKey()// 返回 { keyId, aesKey }clearCryptoKey()// 退出登录时清除// 退出登录时自动清除clearTokens(){cryptoKeyId=null;// 密钥随登录态一起销毁cryptoAesKey=null;}

五、Redis Key 设计

Key 前缀格式TTL用途
rsa_keypair:rsa_keypair:{fingerprint}10 分钟RSA 私钥(交换阶段临时)
aes_key:aes_key:{userId}:{keyId}30 分钟AES 密钥(与 Access Token 同生命周期)
nonce:nonce:{nonce}5 分钟防重放攻击标记

六、安全措施总结

措施实现
机密性AES-GCM 加密消息内容,密钥通过 RSA-OAEP 传输
完整性GCM 模式自带 128-bit 认证标签,篡改即解密失败
防重放随机 nonce(32位十六进制)+ Redis 去重 + 时间戳 5 分钟容差
前向安全AES 密钥每次登录重新交换,RSA 私钥用后即删
密钥隔离AES 密钥按 userId 隔离存储(key =aes_key:{userId}:{keyId}
最小暴露RSA 私钥仅存 Redis 10 分钟,AES 密钥前端仅存内存
降级兼容未交换密钥时自动降级为明文传输,不影响基本功能
SSE 豁免/stream路径跳过加密过滤器,SSE 流不受影响

七、涉及文件清单

文件职责
后端-工具talk-common/.../utils/CryptoUtils.javaAES/RSA 加解密核心
后端-DTOtalk-common/.../dto/crypto/EncryptedPayload.java加密载荷包装
后端-DTOtalk-common/.../dto/crypto/KeyExchangeRequest.java密钥交换请求
后端-DTOtalk-common/.../dto/crypto/KeyExchangeResponse.java密钥交换响应
后端-常量talk-common/.../constant/Constants.javaRedis Key / Header / TTL
后端-控制器talk-chat/.../controller/CryptoController.java密钥交换 API
后端-过滤器talk-framework/.../filter/CryptoFilter.java请求解密 + 响应加密
后端-包装器talk-framework/.../filter/CryptoRequestWrapper.java请求体解密
后端-包装器talk-framework/.../filter/CryptoResponseWrapper.java响应体加密
前端talk-ui/src/api/crypto.js前端 AES/RSA/密钥交换
前端talk-ui/src/api/token-manager.js密钥内存管理
前端talk-ui/src/api/request.js请求/响应自动加解密拦截
前端talk-ui/src/pages/login/index.vue登录后触发密钥交换
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/26 6:12:04

Pinecone混合搜索实战:稠密+稀疏向量工程落地指南

1. 项目概述&#xff1a;为什么 Pinecone 的向量搜索不是“配个 API 就完事”的事我在做企业级知识库系统时&#xff0c;被客户问得最多的一句话是&#xff1a;“你们用的 Pinecone&#xff0c;是不是只要把文本转成向量塞进去&#xff0c;再调个similarity_search就能返回正确…

作者头像 李华
网站建设 2026/6/26 6:10:52

2026年企业级智能AI办公软件FAQ全解析(下篇)

接上篇......Q18&#xff1a;企业级智能体和通用AI Agent相比&#xff0c;技术架构上做了哪些针对性优化&#xff0c;才能适配复杂办公场景&#xff1f; A&#xff1a; 通用AI Agent大多面向开放互联网场景设计&#xff0c;存在任务拆解精度低、企业系统对接能力弱、运行不可控…

作者头像 李华
网站建设 2026/6/26 6:09:01

隔震支座厂家怎么选?从技术标准到实力解析,2026年选型避坑指南

芦山县人民医院隔震与抗震楼随着国家对学校、医院等人员密集场所的抗震设防要求日益严格&#xff0c;“减隔震技术”已逐渐成为现代建筑设计的标配。然而&#xff0c;面对市面上众多的供应商&#xff0c;许多工程采购方在选择隔震支座厂家时&#xff0c;往往容易陷入迷茫。我国…

作者头像 李华
网站建设 2026/6/26 6:08:44

解密启动盘UD分区的技术原理 | FBinst 理论+实操手搓全能三分区启动盘

在PE启动盘领域有一种启动盘制作模式叫做"全能三分区启动".具体来说,这种启动盘制作好后,磁盘分区图大概是这样的:在DiskGenius中查看,这个磁盘的分区结构就像这样:你可能会发现,在DG中显示出有300MB的空闲空间.为什么会有空闲空间呢?这些空闲空间存在的意义是什么呢…

作者头像 李华
网站建设 2026/6/26 6:02:21

基于STM32单片机生理监控心率彩屏蓝牙APP波形心电图设计24-156-1(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_可以扫码

基于STM32单片机生理监控心率彩屏蓝牙APP波形心电图设计24-156-1(设计源文件万字报告讲解)&#xff08;支持资料、图片参考_相关定制&#xff09;_可以扫码 24-156、STM32单片机生理监控心率脉搏TFT彩屏波形曲线心电图心率蓝牙上传及APP显示心率波形设计 产品功能描述&#xff…

作者头像 李华