前后端分离的架构流行起来之后敏感数据传输一直是个绕不开的话题。很多项目第一反应是“我都上HTTPS了还要在应用层做加密吗”但实际做过安全评审或者被等保要求卡过的人心里都清楚HTTPS保护的是传输管道一旦请求落到服务器、进了日志、被运维平台抓包分析敏感字段照样是明文。我之前在某个内部管理系统里接RSA加密就是因为网关层做了SSL终止后端到网关之间是明文安全那边一句话就把需求提过来了登录密码、身份证号、手机号必须在业务代码里加密后再传。所以这篇文章就聊聊我用Spring Boot加Vue3实现RSA加密解密的全过程从方案选型、密钥格式、前后端代码到常见的坑和排查方法尽量把能直接抄作业的东西都给你。这个方案适合谁看准备给前后端项目加应用层加密的开发同学或者正在做登录模块、数据上报接口、敏感字段脱敏传输的人。不管你是Java后端还是Vue前端只要把公钥接口、加密工具类、解密逻辑这三块串起来整个链路就通了。1. 方案设计思路为什么选RSA而不是其他方案1.1 HTTPS已经加密了为什么还要在应用层做加密先把这个最容易被质疑的问题讲清楚。HTTPS加密是端到端的看起来浏览器到服务器之间全是密文但现实中存在几个并不罕见的例外。第一种情况架构里存在网关或反向代理比如用Nginx做HTTPS卸载SSL offload证书绑定在Nginx层Nginx到后端服务之间的HTTP流量反而是明文。只要有人能接触到内网、能抓包密码就直接暴露了。第二种情况日志系统。后端框架普遍会记录请求参数如果你的接口直接把明文密码放在请求体里日志框架一开密码就进ELK了后面查日志的运维、开发、第三方维保人员都能看到。这不是危言耸听我见过不止一个项目在日志里捞出了测试账号密码。第三种情况等保和行业合规要求。很多评审标准里明确写了“敏感数据在传输过程中应采取加密保护措施”HTTPS是基础项应用层加密是加分项有些业务场景直接被要求必须做字段级加密。所以RSA这套方案解决的不是“替代HTTPS”的问题而是纵深防御即使传输链路被解密、日志被截获、抓包工具被打开对方拿到的只是RSA密文没有私钥就是一堆Base64乱码。应用的加密目标是让敏感数据以密文形态出现在不该出现的地方。1.2 RSA、AES、国密SM2怎么选真正做方案的时候不会只有RSA一个选项。我整理一个对比大家在评审时也方便直接讲。方案类型优点缺点适用场景RSA非对称实现简单Java和前端生态都很成熟加密长度受限、性能比对称加密慢小字段加密、密钥交换、签名AES对称加密快、支持大数据量、无长度限制密钥分发难密钥本身需要保护大字段加密、数据传输主体国密SM2非对称合规友好、国产化要求前端库生态弱、后端支持需要额外依赖有国密合规要求的政企项目结论很直接常规业务系统RSA够用代码量少前后端库都很稳定。如果项目有明确的国产化合规要求再考虑SM2。AES一般不是单独用来做传输加密的而是和RSA配合也就是后面要说的混合加密。另外提一句RSA加密在业务里最常用的方向是“公钥加密、私钥解密”也就是前端加密、后端解密私钥永远不落在前端。反过来“私钥签名、公钥验签”是身份认证场景可以用来签名防篡改但不是本文的重点。1.3 整体架构与核心流程整个交互流程并不复杂核心就三步前端向后端发起请求获取RSA公钥。公钥是公开信息这个接口不需要鉴权。前端拿到公钥后用JSEncrypt或Web Crypto API对敏感字段比如密码做加密把密文字符串放在请求体里发给后端。后端持私钥对密文做Base64解码和RSA解密还原成明文后再执行真正的业务逻辑比如密码校验、落库。这里有个重要原则RSA加密只保护传输过程后端解密出来之后该怎么处理就怎么处理。密码字段解密后如果需要存库必须再用摘要算法或专用加密组件处理一次不能拿着明文密码直接存。私钥是整条链路的安全基石前端绝对不能出现私钥后端私钥也不能进日志、不能提交到代码仓库。2. 核心细节解析密钥、格式与库选型2.1 密钥生成与格式PKCS#1 与 PKCS#8RSA密钥对在Java里最常用的生成方式是KeyPairGenerator但很多第一次做的人会被密钥格式绕晕。这里说清楚两个常见编码格式PKCS#1私钥的标准格式PEM文件里常见BEGIN RSA PRIVATE KEY公钥对应BEGIN RSA PUBLIC KEY。PKCS#8更通用的私钥封装格式PEM里常见BEGIN PRIVATE KEY公钥则使用X.509的SubjectPublicKeyInfo结构对应BEGIN PUBLIC KEY。Java的KeyPairGenerator生成的PublicKey.getEncoded()默认就是X.509格式也就是公钥走PKCS#8那一套私钥走PKCS8EncodedKeySpec同样也是PKCS#8。所以只要你不做特殊转换后端拿到手的Base64字符串就是PKCS#8。前端JSEncrypt做setPublicKey时对不带PEM头的Base64公钥直接就能解析但为了兼容性后端接口返回的可以统一是去掉换行符的Base64字符串前端使用时如果需要可以自己拼上-----BEGIN PUBLIC KEY-----头。我的习惯是后端返回裸Base64前端封装函数里统一处理这样两边都清爽。2.2 前端加密库选型jsencrypt 与 Web Crypto API前端做RSA加密主流就两条路第三方库JSEncrypt或者浏览器原生Web Crypto API。JSEncrypt的优点是简单、老牌、资料多。用法就是new JSEncrypt()setPublicKey然后encrypt同步返回密文非常直白。缺点是库比较老底层基于jsbn性能和包体积一般不过加密一个密码字段这点开销无所谓。Web Crypto API是浏览器标准能力不需要额外依赖底层实现效率高支持RSA-OAEP等多种算法。但它是异步API而且解析PEM公钥需要自己写工具函数把Base64解码成ArrayBuffer再importKey代码量明显多一些。适合对依赖数量和包体积在意、项目新、浏览器环境可控的情况。我个人的工程选择是中小项目、快速交付、团队里前端水平参差直接JSEncrypt封装一层工具函数后面想换也方便。如果项目本来就对性能敏感、要求零第三方加密依赖上Web Crypto API。文章后面的实操代码以JSEncrypt为主因为它最容易被大家复现。2.3 密钥长度、加密长度与性能权衡RSA密钥长度直接决定安全强度和能加密的明文长度。1024位已经不建议用了安全强度不够被要求整改是迟早的事。2048位是当前主流基线绝大多数业务场景足够。4096位更安全但密钥生成慢、加解密耗时增加对登录这种低频操作影响不大对高频接口没必要。加密长度限制是新手最容易踩的坑。以2048位密钥、PKCS#1 v1.5填充为例一次最多能加密的字节数是2048 ÷ 8 - 11 245字节这个11字节是PKCS#1 v1.5填充的固定开销。也就是说用2048位密钥加密明文不能超过245字节。一个包含中文的地址、一个JSON串、一串较长的备注很容易就超了。超长之后JSEncrypt的encrypt方法会返回false后端收到的就是空值排查半天还不知道问题出在哪。所以设计阶段就要想清楚RSA适不适合加密大字段不适合。登录密码这种短字段没问题真要加密一大段数据就得走分段加密或者混合加密方案后面第6章会细讲。性能上RSA加密一个245字节的块本地跑也就几毫秒到十几毫秒对接口整体耗时影响很小。但如果你对每个请求里的大字段都做RSA并发上来之后CPU开销会被放大所以实践中RSA只用于加密密码、手机号、身份证号这类小字段。3. 实操过程Spring Boot后端搭建与接口实现3.1 工程准备与依赖Spring Boot项目不需要额外引入RSA的第三方库JDK自带的javax.crypto和java.security就够用了。只需要基础的web依赖。dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency这里补充一句如果项目里用了Spring Security登录认证建议走框架的过滤器链但RSA解密处理可以放在自定义的Controller层方法里不影响认证流程。为了演示清晰我这边的例子就用普通Controller处理。3.2 RSA工具类编写工具类是整个链路的基础我直接把生产里用过的简化版本贴出来。注意点都用注释标了。package com.example.demo.util; import javax.crypto.Cipher; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; public class RsaUtils { // 生成密钥对密钥长度2048 public static KeyPair generateKeyPair() throws NoSuchAlgorithmException { KeyPairGenerator generator KeyPairGenerator.getInstance(RSA); generator.initialize(2048); return generator.generateKeyPair(); } // PublicKey转Base64字符串 public static String getPublicKeyStr(PublicKey publicKey) { return Base64.getEncoder().encodeToString(publicKey.getEncoded()); } // PrivateKey转Base64字符串 public static String getPrivateKeyStr(PrivateKey privateKey) { return Base64.getEncoder().encodeToString(privateKey.getEncoded()); } // 公钥加密 public static String encrypt(String data, String publicKey) throws Exception { Cipher cipher Cipher.getInstance(RSA/ECB/PKCS1Padding); cipher.init(Cipher.ENCRYPT_MODE, getPublicKey(publicKey)); byte[] bytes cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(bytes); } // 私钥解密 public static String decrypt(String data, String privateKey) throws Exception { Cipher cipher Cipher.getInstance(RSA/ECB/PKCS1Padding); cipher.init(Cipher.DECRYPT_MODE, getPrivateKey(privateKey)); byte[] bytes cipher.doFinal(Base64.getDecoder().decode(data)); return new String(bytes, StandardCharsets.UTF_8); } public static PublicKey getPublicKey(String base64Key) throws Exception { byte[] keyBytes Base64.getDecoder().decode(base64Key); X509EncodedKeySpec spec new X509EncodedKeySpec(keyBytes); KeyFactory factory KeyFactory.getInstance(RSA); return factory.generatePublic(spec); } public static PrivateKey getPrivateKey(String base64Key) throws Exception { byte[] keyBytes Base64.getDecoder().decode(base64Key); PKCS8EncodedKeySpec spec new PKCS8EncodedKeySpec(keyBytes); KeyFactory factory KeyFactory.getInstance(RSA); return factory.generatePrivate(spec); } }这里最需要注意的是Cipher.getInstance(RSA/ECB/PKCS1Padding)。有些教程直接写Cipher.getInstance(RSA)表面看也能跑但底层具体用哪种填充方式完全依赖JDK默认实现不同JDK版本可能不一致。为了保证前端JSEncrypt加密的密文后端一定解得了建议显式指定RSA/ECB/PKCS1Padding。JSEncrypt底层用的就是RSAES-PKCS1-v1_5填充两边对齐才不会出问题。另外Base64编码串里是不存在换行的但有些系统从PEM文件里拷贝密钥时会带上换行符解析时会导致InvalidKeySpecException。工具类里可以在解码前统一去掉所有空白字符比如base64Key.replaceAll(\\s, )这样更稳妥。3.3 密钥对的生成与存储策略密钥对怎么来两种常见方式。开发环境推荐“启动时生成保存到本地文件”。这样每次重启公钥不会变前端测试也稳定。我写一个初始化组件package com.example.demo.config; import com.example.demo.util.RsaUtils; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component; import java.io.File; import java.nio.file.Files; import java.security.KeyPair; Component public class RsaKeyInitializer implements ApplicationRunner { private static final String PUBLIC_KEY_FILE rsa_public_key.txt; private static final String PRIVATE_KEY_FILE rsa_private_key.txt; Override public void run(ApplicationArguments args) throws Exception { File pubFile new File(PUBLIC_KEY_FILE); File priFile new File(PRIVATE_KEY_FILE); if (!pubFile.exists() || !priFile.exists()) { KeyPair keyPair RsaUtils.generateKeyPair(); Files.write(pubFile.toPath(), RsaUtils.getPublicKeyStr(keyPair.getPublic()).getBytes()); Files.write(priFile.toPath(), RsaUtils.getPrivateKeyStr(keyPair.getPrivate()).getBytes()); } } }生产环境建议把密钥放到配置中心或者环境变量里而不是直接写在代码中。用ConfigurationProperties加载配置很常见rsa: public-key: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC... private-key: MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmE...Data Component ConfigurationProperties(prefix rsa) public class RsaProperties { private String publicKey; private String privateKey; }有个经验值得分享如果开发环境用“启动生成”方案千万别把生成的文件提交到Git。否则密钥泄露不说每次拉到新环境还可能因为文件存在导致密钥不一致排查起来很困惑。我一般会在.gitignore里把这两个密钥文件排除掉。生产环境的私钥一旦泄露等于整个加密体系失效。所以私钥文件的文件权限要收紧比如Linux下chmod 600密钥内容也不要打印到日志里。调试时哪怕只print一次后面也可能被日志系统捞走。3.4 提供公钥接口与解密接口公钥接口很简单直接把RsaProperties里的公钥字符串返回即可。不需要鉴权但建议加响应头禁止缓存或者至少让调用方明确这是公开信息。RestController RequestMapping(/api) public class AuthController { private final RsaProperties rsaProperties; public AuthController(RsaProperties rsaProperties) { this.rsaProperties rsaProperties; } GetMapping(/public-key) public ResultString getPublicKey() { return Result.success(rsaProperties.getPublicKey()); } PostMapping(/login) public ResultString login(RequestBody LoginRequest request) { // 解密前端加密后的密码 String password null; try { password RsaUtils.decrypt(request.getEncryptedPassword(), rsaProperties.getPrivateKey()); } catch (Exception e) { return Result.error(密码解密失败请重新尝试); } // 这里用解密后的password做真实的业务校验 return Result.success(登录成功); } }LoginRequest里我习惯设计成username和encryptedPassword两个字段encryptedPassword对应前端传来的密文。请求体里不会出现明文密码日志里自然也看不到。解密失败要区分原因Base64格式错、填充方式不对、密文被截断都会抛异常。建议在全局异常处理器里捕获向前端返回500还是自定义错误码看你项目规范但最重要的是不要把异常堆栈暴露给调用方避免攻击者拿到信息。4. 实操过程Vue3前端加密实现4.1 前端工程与依赖Vue3项目直接安装JSEncryptnpm install jsencrypt装完之后在需要的地方import即可。不过我更推荐把它封装成一个独立模块方便统一处理公钥获取、缓存、异常。前端代码的目录结构按常见Vue3工程来看我会在src/utils下建rsa.js然后在src/api里对接后端接口。4.2 封装RSA加密工具函数封装的核心是不要每次加密都去请求公钥接口。公钥在一段时间内是固定的前端缓存一份每次调用时先取缓存缓存没有再去请求接口。这样能少一次网络往返登录页面加载更快。// src/utils/rsa.js import JSEncrypt from jsencrypt import request from /utils/request let cachedPublicKey async function getPublicKey() { if (cachedPublicKey) { return cachedPublicKey } const res await request.get(/api/public-key) cachedPublicKey res.data return cachedPublicKey } export async function encryptByPublicKey(plainText) { const publicKey await getPublicKey() const encryptor new JSEncrypt() encryptor.setPublicKey(publicKey) const encrypted encryptor.encrypt(plainText) if (encrypted false) { throw new Error(RSA加密失败明文长度超出限制或公钥不合法) } return encrypted }这里有个细节JSEncrypt的encrypt方法在加密失败时会返回false而不是抛异常。如果不判断这个返回值密文一旦为空字符串后端收到的就是空值排查时很容易绕晕。所以在封装里显式判断一次报错信息也写清楚能省很多事后调试时间。如果你的前端环境里需要兼容低版本浏览器JSEncrypt这套是没问题的。如果只用现代浏览器也可以用Web Crypto API但封装逻辑会多一些。我维护的另一个小项目用的是原生方案核心逻辑就是crypto.subtle.importKey导入SPKI格式公钥再用RSA-OAEP算法加密性能确实更好但代码量比JSEncrypt多出一截。4.3 登录场景接入登录页面里的接入方式很直观。表单校验通过之后再把密码字段加密而不是用户一输入就加密。这样方便做校验和日志排查。// Login.vue 简化示例 import { encryptByPublicKey } from /utils/rsa const handleLogin async () { // 校验表单略 const encryptedPassword await encryptByPublicKey(form.password) await request.post(/api/login, { username: form.username, encryptedPassword }) }这里再强调一次前端代码里绝对不能出现私钥。有人可能觉得“我把私钥放前端后端返回的密文就能直接解密了”这是完全错误的思路。私钥一旦进了前端代码等同于公开Chrome DevTools里翻一翻就能提取出来整个加密体系直接崩塌。顺便提一下状态管理。很多Vue3项目会接Pinia或Vuex尽量别在store的state里存明文密码更别在开发工具里打印出来。加密前打印一次可能觉得没什么但截图发群里、录屏演示、交接给第三方开发人员都是泄露风险。4.4 前端解密与敏感信息回显大部分项目只需要前端加密、后端解密不需要做反向解密。但有人会问“后端返回的用户手机号、身份证号我也想加密回显怎么办”如果后端用RSA私钥加密返回那么前端必须有公钥来解密而公钥是公开的这套流程只能防链路窃听不能防前端本身被篡改。如果你真想对回显数据加密合理的方案有两种后端返回脱敏后的数据比如138****1234这在实际业务中已经能满足绝大多数展示需求。走混合加密后端用前端生成的AES密钥加密响应数据前端用同一把AES密钥解密。这样前端不存RSA私钥也能安全解密。我见过不少项目试图用RSA做双向加解密把私钥放在前端结果安全评审直接不通过。设计阶段真要想清楚RSA公钥加密、私钥解密这个方向是天然给“前端加密、后端解密”用的反过来用属于本末倒置。5. 常见问题与排查实录5.1 后端解密报 InvalidKeySpecException这个问题出现频率最高。现象是接口返回500日志里一堆InvalidKeySpecException: java.security.spec.InvalidKeySpecException。常见原因有三个密钥字符串里混入了换行、空格等不可见字符。前端传过来的公钥字符串带了-----BEGIN PUBLIC KEY-----等PEM头但后端的Base64.getDecoder().decode()不支持直接解析PEM头。前后端使用的密钥格式不一致比如前端用了PKCS#1公钥后端却按PKCS#8解析。建议统一约定后端接口返回裸的Base64字符串不带头不带换行前端也按裸Base64使用。如果一定要带PEM头那后端要先剥离头尾再解码。工具类里最好统一处理一次空白字符。5.2 前端加密返回 false前面已经提过JSEncrypt的encrypt方法在明文过长时会返回false。这个问题的典型现象是短密码正常一旦加密的字段内容变长比如备注、地址就报错。2048位密钥上限245字节这是硬约束。解决思路两种只对短字段做RSA加密长字段干脆不加密交给HTTPS。长字段做分段加密前端把密文用分隔符拼起来后端再分段解密。分段加密的完整示例我放在第6章混合加密里讲这里先说结论普通登录密码完全不用分段遇到长字段才需要。5.3 中文乱码问题中文乱码出现的位置一般在后端解密之后。如果前端JSEncrypt加密时传的是中文字符串后端解密后直接new String(bytes)可能会乱码。原因在于字节和字符的编码不一致。JSEncrypt内部加密时会把字符串按UTF-8转成字节所以后端解密后必须用UTF-8解码也就是new String(bytes, StandardCharsets.UTF_8)。很多老的教程示例代码只写了new String(bytes)在默认平台编码不是UTF-8的服务器上就会出现乱码。我建议不管是加密还是解密全程统一UTF-8前端请求头Content-Type里的charsetUTF-8也要保证后端Spring Boot默认就是UTF-8一般不会出问题。5.4 每次重启后公钥变了之前加密的数据解密失败这种情况一看就知道是“启动时重新生成密钥对”同时没有把密钥持久化。开发环境还好接口重跑一遍就行。一旦到了生产密钥一变正在使用系统的用户登录时前端拿新公钥加密后端拿新私钥解密其实也能通。真正的问题是如果系统里存在用旧密钥加密的数据比如服务间异步调用、缓存里存了旧密文重启后这些数据就解不开了数据直接作废。所以生产环境一定要固定密钥对放到配置中心或者密钥文件里启动时读取而不是生成。密钥轮换应该有完整方案而不是靠重启碰运气。5.5 常见问题速查表问题现象可能原因解决方案后端解密报InvalidKeySpecException密钥含换行、PEM头、格式不一致统一裸Base64、清理空白字符前端encrypt返回false明文超过245字节短字段只传敏感信息长字段走分段加密解密后中文乱码字符集不一致统一UTF-8解密后显式指定UTF-8重启后旧数据解不开每次启动重新生成密钥对生产环境固定密钥对妥善保存公钥接口频繁请求前端未缓存公钥封装工具函数缓存公钥字符串日志里出现明文密码解密后打印日志日志脱敏禁止打印密码字段6. 安全增强与混合加密扩展6.1 密钥轮换与多密钥管理固定密钥对虽然解决了解密问题但也意味着密钥长期不变一旦泄露影响面会非常大。所以生产环境要有密钥轮换机制。最简单的方案是在数据库或者配置中心里维护多个密钥对每个密钥对有个版本号。前端请求公钥接口时拿到当前版本号加密时把版本号一块传给后端。后端根据版本号选择对应的私钥解密。轮换时新增一个版本作为当前版本旧版本保留一段时间保证历史数据还能解密等所有旧密文都处理完了再下线旧版本。这个方案不需要引入太复杂的组件一张配置表或者一个对象存储就能搞定。我在一个多环境项目里就是这么做的开发环境一个密钥对、测试一个、生产一个互不影响轮换时也只在配置中心改一下。6.2 RSAAES混合加密方案如果真的要加密大段数据最合理的方案是RSA和AES结合。前端生成一个临时的AES密钥用RSA公钥加密这个AES密钥然后用AES密钥加密真正的业务数据。后端先解密AES密钥再用AES密钥解密数据。前端核心逻辑// 精简示例 function generateAesKey() { const key crypto.getRandomValues(new Uint8Array(16)) return Array.from(key).map(b b.toString(16).padStart(2, 0)).join() } // 用RSA加密AES密钥 const encryptedAesKey await encryptByPublicKey(aesKey) // 用AES加密业务数据这里用CryptoJS做示例 const encryptedData CryptoJS.AES.encrypt(plainText, aesKey).toString()后端核心逻辑String aesKey RsaUtils.decrypt(request.getEncryptedAesKey(), rsaProperties.getPrivateKey()); // 再用AES密钥解密这样做的好处很明显AES没有长度限制加密速度快适合加密大字段RSA只加密一把128位或256位的随机密钥完全不会踩245字节的天花板。我不建议为了“显得专业”就无脑上混合加密。大多数登录场景就是加密个密码RSA直接加密就完事了。只有当加密字段多、长度大、日志和安全要求高时才值得引入AES。6.3 防重放与防篡改应用层RSA加密解决了明文泄露问题但防不了重放攻击。攻击者把密文原样抓下来再原样提交一次后端解出来还是同一个密码。这就是为什么不能只做加密还要配合时效性和唯一性校验。简单的做法是请求体里加一个时间戳字段后端判断当前时间和时间戳的差值超过比如5分钟就拒绝。再进一步可以加一个随机数nonce后端把用过的nonce存起来同一个nonce不允许重复使用。时间戳和nonce建议也放在加密内容里否则攻击者篡改时间戳很容易绕过。加签也值得考虑。前端对加密后的整个请求体做一个摘要签名后端验签通过后才处理。能把篡改请求挡住一大半。不过签名逻辑会增加一点开发量小项目可以先从时间戳加nonce做起。6.4 一个常见的误解RSA能替代HTTPS吗最后再花点篇幅把这个误解讲透。RSA应用层加密和HTTPS不是二选一的关系而是叠加的关系。HTTPS保护的是传输层防止数据在链路上被窃听和篡改。应用层加密保护的是“即使链路被绕过、日志被拿到、内部人员直接查库”敏感字段依然不是明文。我见过有人把RSA加密当成万能药觉得上了RSA就可以不用配置证书、不走HTTPS。这种想法大错特错。RSA只加密了业务字段请求头、URL路径、Cookie、流量特征全在裸奔攻击者照样能从请求频率、字段长度、响应状态里分析出大量信息。真正上线时HTTPS是基础设施必须保留RSA加密是加固层两者配合。写在最后一些实操中的体会把Spring Boot和Vue3的RSA加解密完整跑通之后回头复盘我觉得最有价值的一个习惯就是把前端加密封装成独立工具模块后端加密封装成独立工具类不要散落在各个业务代码里。前端统一管理公钥缓存、加密失败异常后端统一管理格式清洗、字符集、异常处理。这样密钥格式要调整、算法要升级的时候改动范围都在一两个文件里不会到处找代码。另一个体会是调试阶段先在本地把公钥、私钥生成好固定写入配置不要每次启动都变。开发时前端页面频繁刷新如果公钥一直在变会平白多出好多“我这边明明加密了后端怎么解不出来”的问题。先固定密钥跑通一条链路再考虑密钥轮换和动态管理。如果你现在正要接这个功能按这篇文章的路径走一遍先把后端工具类写好把公钥接口暴露出来前端用JSEncrypt把密码加密发过来后端解密通过这第一步就跑通了。之后的优化比如混合加密、防重放、密钥版本化都是在这个地基上慢慢加。加密这件事做得早总比被安全评审提醒之后再补要省心得多。