
简介针对前端项目中对敏感信息进行RSA加密传输的需求资源提供了一套基于jsencrypt的完整前端加密解密方案并特别兼容uniapp跨端项目。面向有一定前端基础、急需在浏览器或小程序中实现RSA公私钥加密的开发者可快速解决常规jsencrypt在uniapp中报错无法使用的问题适用于登录密码、订单数据等敏感字段的安全传输场景。包体共3个文件由2个js文件和1个txt说明文档构成压缩包仅37KB。其中jsencrypt.js是适配uniapp的修改版本rsa.js封装了可直接导入调用的加密与解密方法txt文档则记录密钥生成要点、在线公钥私钥生成工具地址及前后端对接注意事项整体轻量且结构清晰便于直接复制到项目中使用。目前已有2805人学习下载。通过资源读者可直接获得修改后的jsencrypt.js、现成的rsa.js调用封装以及配套的公私钥生成说明省去自行搜索和调试兼容性的时间同时也能理解RSA加密解密在前后端交互中的具体落地方式是一份即拿即用的实践参考。1. 登录参数被抓包之后才知道前端 RSA 加密不是玄学登录接口把密码明文放在请求体里连到同一个 Wi-Fi 的人用抓包工具就能直接看到安全评审这一关基本过不去。jsencrypt 是目前前端做 RSA 加解密最常用的纯前端 SDK它不依赖 Node 环境普通浏览器页面和 uniapp 的 H5 端、App 端都能用核心用法就是拿到后端给的公钥在浏览器里完成“公钥加密、私钥解密”这条链路。下面把密钥怎么生成、代码怎么写、到了 uniapp 环境有哪些差异以及长度超限、中文乱码、密钥格式不对这几个一定会撞上的问题全理一遍。适合正在接登录、支付、实名认证这类敏感字段又不想改动整个后端协议的前端开发者。2. RSA 非对称加密与 jsencrypt 的选型公钥锁、私钥开和密钥格式2.1 RSA 加解密的最小模型前端持公钥后端持私钥RSA 是非对称加密存在公钥和私钥两把钥匙。公钥加密的数据只有配对的私钥能解开反过来私钥加密过的内容可以用公钥验证来源。和 AES 这类对称加密相比对称加密加密解密用的是同一把密钥通信双方必须同时持有这把钥匙而钥匙本身要先通过网络传给对方传输过程就成了新的暴露面RSA 不需要预先交换密钥公钥可以随便公开私钥只留在后端。这个特性让它天然适合“前端提交敏感数据后端解密”的场景前端把密码用公钥锁起来后端用私钥打开中间被截获的只是一段不可逆推的密文。jsencrypt 的位置就是把这个过程封装成一个纯前端工具库。它内部处理了密钥解析、PKCS#1 v1.5 填充、Base64 编码这一整套流程前端开发者不需要理解大数模幂的数学原理只需要new JSEncrypt()、setPublicKey()、encrypt()三步。这也是为什么它在“前端面试题”里经常作为 RSA 算法落地方案被问到——面试官通常想确认的不只是你会不会调用 API而是你有没有搞清楚公钥和私钥各自的存放边界。有一点要特别说清楚如果需求写着“前端解密”要分清是真正的解密还是“验签”。标准流程里私钥只属于后端前端用公钥做加密后端用私钥解密反过来后端用私钥对关键数据签名、前端用公钥做verify校验完整性这是签名验签接口不是加解密接口。jsencrypt 同时提供encrypt/decrypt和sign/verify两组能力混用最容易出权限事故。真正的“前端解密”通常只出现在离线数据包、本地缓存加密这类场景里正式业务接口不要设计成前端解后端密文。2.2 密钥长度、填充与可加密数据上限为什么请求体一长就报错RSA 加密不是“输入多长输出多长”它对单次能加密的明文长度有硬性限制。jsencrypt 底层使用的是 RSAES-PKCS1-v1_5 填充这个填充会占用 11 个字节所以单次加密的明文上限是“密钥位数 / 8 - 11”。简单算一下1024 位密钥最多加密 117 字节2048 位密钥最多加密 245 字节。这个上限不是库的缺陷是 RSA 算法本身的定义。密钥长度密文长度单次明文上限1024 位128 字节117 字节2048 位256 字节245 字节3072 位384 字节373 字节这个表格解释了为什么很多人第一次把一整个表单对象JSON.stringify之后丢进encrypt()就当场报错。中文字符在 UTF-8 编码下通常占 3 个字节2048 位密钥连 100 个中文都装不下更别说带一段完整地址信息。常见的做法是只对密码、身份证号、银行卡号这类单字段做 RSA 加密不要加密整个请求体。如果业务上确实需要把一整套 JSON 整体加密就不要硬用 RSA 去塞后面第 6 章的 RSA AES 混合加密才是正解。2.3 jsencrypt 的密钥格式与解析边界PKCS#1、PKCS#8 和带口令私钥密钥格式是另一个高频翻车点。OpenSSL 默认生成的私钥是 PKCS#1 格式特征是-----BEGIN RSA PRIVATE KEY-----而很多后端框架、Java 的KeyPairGenerator或云厂商控制台导出的私钥是 PKCS#8 格式特征是-----BEGIN PRIVATE KEY-----。jsencrypt 对这两种私钥都有解析逻辑所以通常都能setPrivateKey但如果后端给的是带口令的私钥也就是-----BEGIN ENCRYPTED PRIVATE KEY-----开头jsencrypt 不支持解密这种格式因为它没有接收密码参数的入口。公钥的格式相对统一常见的是-----BEGIN PUBLIC KEY-----这一种。真正隐蔽的问题不在“哪种格式”而在于字符串本身是否完整。开发环境里很多人从后端抄公钥时换行被聊天工具吞掉或者复制时只复制了 Base64 内容忘了头尾jsencrypt 传进去之后setPublicKey不报错加密也返回一个字符串但后端用私钥一解全是乱码。遇到这种情况先别怀疑密码不对八成是公钥字符串已经不完整了。约定一个稳定的做法后端给前端公钥时直接给完整的 PEM 字符串前端原样保存不trim首尾不让任何格式化工具碰它。3. 普通前端接入 jsencrypt最小实现、密钥生成和参数设置3.1 安装与最小可用代码三行代码跑通公钥加密引入方式按项目场景选。用 npm 或 yarn 管理的工程安装并默认导入是常见做法。不要写成按需引入某个子模块因为 jsencrypt 导出的是整个类。import JSEncrypt from jsencrypt const publicKey -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCq... -----END PUBLIC KEY----- const encryptor new JSEncrypt() encryptor.setPublicKey(publicKey) const cipherText encryptor.encrypt(123456) console.log(cipherText) // Base64 密文这段代码做的事有三步new JSEncrypt()创建一个实例setPublicKey()把后端下发的 PEM 公钥加载进实例encrypt()对明文加密并返回 Base64 文本。encrypt()在失败时会返回false而不是抛异常所以后续把结果塞进接口前一定要判断返回值如果直接request({ data: { password: cipherText } })一旦返回false后端的私钥解密拿到的是一个布尔值排查起来会很绕。如果是在传统多页面项目里没有构建工具直接把下载的jsencrypt.min.js用script标签引入挂载在全局的window.JSEncrypt上用法完全一致。我自己更推荐先确认清楚项目是 H5 还是 uniapp 再选方式因为 uniapp 里的引入方式有一点特殊第 4 章会专门展开。3.2 本地生成 RSA 密钥对一条命令与参数选择联调阶段最省事的密钥生成方式是 OpenSSL两条命令就能拿到一对密钥。工具函数、脚本和后端同事手里的密钥生成逻辑完全统一。# 生成 2048 位私钥默认 PKCS#1 格式 openssl genrsa -out private.pem 2048 # 从私钥导出公钥PKCS#8 格式 openssl rsa -in private.pem -pubout -out public.pemgenrsa的2048是密钥位数这是目前生产环境的最低建议值。1024 位在 2024 年之后已经不适合承载正式业务虽然 jsencrypt 仍然支持但安全评审一般不会放行4096 位安全性更高加解密耗时也会明显上涨前端弱机型上会有可感知的卡顿。第二条命令里的-pubout表示从私钥文件中提取公钥输出的public.pem是 PKCS#8 格式这是最不容易出问题的一种组合。生成的private.pem只应该出现在后端和运维手里public.pem的内容可以放进前端常量或通过接口下发。联调时有些人图方便用在线工具生成密钥对我一般只把它当临时方案在线工具没法确认有没有留存密钥生产环境坚决不用。没有 OpenSSL 环境的时候也可以用 Node.js 的crypto.generateKeyPairSync生成但输出格式需要自己转成 PEM不如 OpenSSL 一条命令直接。3.3 对接后端的约定密文传输、编码与字段名前端加密完成之后后端拿到的是一段标准 Base64 字符串长度取决于密钥位数1024 位密钥加密后是 128 个字符左右2048 位是 256 个字符左右。这段字符串里可能包含和/字符如果请求是通过 URL 参数传递必须用encodeURIComponent包一层再塞进 URL不然号在服务端会被解析成空格导致私钥解密直接失败。放在 POST 请求体里则没有这个问题。字段命名也需要提前约定。我见过前端把密文放在password字段、后端拿着同一个字段去解密结果和明文逻辑混在一起调试了半天。更清晰的做法是统一叫encryptedData或key后端看到这个字段就知道要先做 RSA 解密再进入正常业务逻辑。另一个容易被忽略的约定是字符编码前端加密的是普通字符串后端解密后要按 UTF-8 还原如果后端用ISO-8859-1去读字节中文必乱码这个坑在第 5 章还会展开。4. uniapp 里跑通 jsencryptH5 与 App 端的兼容方案4.1 安装与条件编译绕开小程序端的 window 缺失uniapp 项目本质上是一个 Vue 工程H5 端可以直接复用第 3 章的代码。但微信小程序端没有window对象jsencrypt 在加载时会访问浏览器运行时环境直接import会在编译或运行期抛window is not defined。App 端则不一样Vue 页面运行在 WebView 容器里window存在可以正常使用只有 nvue 这类原生渲染页面没有完整 DOM不建议放加解密逻辑。处理多端差异的标准做法不是每个页面都判断而是把加密封装成一个工具类用 uniapp 的条件编译把非目标端代码剔掉。条件编译指令会在编译阶段直接把非目标平台的内容注释掉不是运行时判断所以小程序包里不会混入 jsencrypt 的代码。// utils/rsa.ts let JSEncrypt: any null // #ifdef H5 || APP-PLUS JSEncrypt require(jsencrypt) // #endif export function rsaEncrypt(text: string, publicKey: string): string { // #ifdef H5 || APP-PLUS if (!text || !publicKey) return const encryptor new JSEncrypt() encryptor.setPublicKey(publicKey) return encryptor.encrypt(text) || // #endif // #ifdef MP-WEIXIN // 小程序端无 window 对象建议后端代为加密或使用 web-view 承载 H5 加密页 return // #endif }这里用require而不是import是因为条件编译对import语句的预处理在某些老版本 HBuilderX 里不够干净require包一层更稳妥。// #ifdef到// #endif之间的代码块只会进入对应平台产物。参数publicKey需要从外部传入不要在工具类里硬编码否则密钥轮换时要重新发版。4.2 在登录页里真实使用只加密密码字段别碰整个表单工具类写好之后页面里的调用和普通 Vue 组件没有区别。下面是一段 uniapp 登录页的示例组合式 API 写法核心动作是拿到用户输入的密码后先走 RSA 加密再放进请求体。template view classlogin input v-modelusername placeholder账号 / input v-modelpassword placeholder密码 / button clickhandleLogin登录/button /view /template script setup langts import { ref } from vue import { rsaEncrypt } from /utils/rsa const username ref() const password ref() async function handleLogin() { const encryptedPwd rsaEncrypt(password.value, publicKey) if (!encryptedPwd) { console.error(RSA 加密失败请检查明文长度或公钥格式) return } const res await uni.request({ url: /api/login, method: POST, data: { username: username.value, encryptedPwd } }) console.log(res.data) } /script这里的publicKey可以定义在同级config.ts里也可以从后端接口拉取后缓存到uni.setStorageSync。我一般不建议在页面里直接拼公钥字符串因为密钥对轮换是常态写死在页面里等于每次轮换都要发版。从接口拉公钥的做法前端启动时拿一次后端更新密钥后通过版本号下发新公钥前端后续请求自然使用新公钥加密。注意这段代码只对password做了加密username仍然明文传输。用户名这类非敏感字段没必要增加服务端解密负担RSA 加密有长度上限加密尽量拆到单字段粒度。如果后端接口设计成“请求体整体加密”那就不是这种写法见第 6 章的混合加密方案。4.3 App 打包与公钥放置别把仓库钥匙放门口App 端和 H5 端还有一个区别App 打包后的代码在用户手机上公钥虽然暴露在包里但公钥本来就是公开信息这个没问题。真正的问题是有没有人在 App 端把私钥也一起编译进去或者从后端接口拉私钥到本地。私钥一旦进了客户端安装包反编译提取只是时间问题整个加密体系等于裸奔。我处理 App 端密钥的逻辑是前端只持有公钥私钥永远留在服务端如果某些离线校验场景必须在本地点验签名那只放一个专门用于验签的公钥证书并且明确标注它不能反推私钥。密钥轮换时通过配置接口下发新公钥前端缓存到本地存储下次启动优先用缓存、同时静默拉取最新配置。这样既保证了旧数据能解开又让轮换不需要强制用户升级 App。5. jsencrypt 踩坑与排查长度超限、中文乱码、密钥格式不匹配5.1 三个必然遇到的高频错误长度、中文、布尔返回值现象控制台报Message too long for RSA。原因要加密的明文长度超过密钥上限。很多新手第一次加密就把JSON.stringify后的完整表单丢进去表单里只要有备注、地址这些长文本245 字节的上限瞬间被击穿。解决用第 2 章的长度表对照一下只对密码、手机号这类短字段加密确认必须加密长内容时不要硬调密钥位数改用混合加密方案。这里有个容易被忽略的细节encrypt()失败时返回的是false而不是抛异常所以接口里拿到的密文要判空不然后端会收到一个布尔值。现象后端解密出来的中文变成好的或%E5%A5%BD%E7%9A%84这种乱码。原因RSA 加密是对字节的操作jsencrypt 在处理多字节中文字符时字符编码转换和后端解码方式不一致。后端拿 Java 的new String(bytes, ISO-8859-1)去解 UTF-8 字节流就会得到乱码。解决前端加密前先做一次encodeURIComponent把中文变成 ASCII 字符集里的转义序列后端解密后再按 URL 编码还原。const plainText 你好世界 const cipherText encryptor.encrypt(encodeURIComponent(plainText)) // 后端先 RSA 解密再对密文调用 decodeURIComponent 得到中文原文这段代码背后的逻辑是让进入 RSA 的字符串只包含 ASCII 字符彻底避开 UTF-8 字节序差异。后端如果是 Java对应的是URLDecoder.decode(decrypted, UTF-8)Node 端则用decodeURIComponent。这个约定需要在接口文档里写清楚否则前后端各自处理一半很容易出现前端加了、后端没解的情况。现象加解密结果突然返回false但代码和昨天一模一样。原因大概率不是代码改了而是密钥或输入变了。排查顺序推荐先打印传入的明文确认不是空字符串和超长内容再检查publicKey字符串看头尾是否完整、换行是否被格式化工具自动合并成一行最后和私钥提供方确认这组公私钥是否真的配对。大多数“昨天还好好的今天就没用”都和密钥被重新生成有关联调环境里经常有人重新执行了 OpenSSL 命令前端公钥没更新密文自然解不开。5.2 环境与格式的隐蔽坑密钥对匹配、小程序编译、老 WebView 编码现象公钥明明是对的加密也不报错但后端解密是空或者直接异常。解决先用 OpenSSL 在命令行验证这组公私钥是否匹配把前端代码排除在外。很多“加密结果后端解不开”的问题其实是两把钥匙或公钥内容复制丢了几个字符。# 用公钥加密一段测试文本 echo hello | openssl pkeyutl -encrypt -pubin -inkey public.pem -out enc.bin # 用私钥解密能输出 hello 说明密钥对匹配 openssl pkeyutl -decrypt -inkey private.pem -in enc.bin这条命令需要 OpenSSL 1.1.1 以上版本。如果命令行解密正常问题就在前端字符串传递环节如果命令行也失败直接找后端要一对新密钥不要在前端代码里继续耗时。这个排查顺序能把“库的问题”和“密钥的问题”快速切开。现象微信小程序端npm install jsencrypt后编译就报window is not defined。原因小程序没有 BOM 对象jsencrypt 初始化时会读取全局环境。解决把 import 放进条件编译里让MP-WEIXIN平台完全不要加载这个库。条件编译处理掉之后小程序已经成功编译并上线那“加密怎么办”常见做法是后端提供加密接口小程序把敏感字段明文通过 HTTPS 传到后端由后端在服务端完成 RSA 加密后再入库或转发不在小程序端做任何加解密。另一个可行方案是用web-view承载一个 H5 加密页面但也只是绕过问题。这些问题在“uniapp 微信小程序打包”相关的技术社区里讨论很多结论基本一致小程序端尽量不要碰 RSA。现象同一套 RSA 代码在 PC 浏览器和安卓高端机上正常在低版本 WebView 上解出来是乱码。原因老版本 WebView 对 Unicode 字符转字节的处理有差异特别是遇到 emoji 或生僻字时。解决沿用encodeURIComponent方案让进入 RSA 的数据永远是 ASCII同时在前端埋一个自检函数加密后立刻用公钥对应的本地测试私钥解一遍仅在测试环境做发现乱码直接上报机型。这类问题玄学成分高统一编码是成本最低的预防手段。6. 进阶当 RSA 装不下长文本用 RSA AES 混合加密接住整包数据RSA 只适合加密短字段这是算法决定的天花板。如果产品需求是“前端把整个请求体加密后再提交”最干净的做法是 RSA AES 混合加密也叫数字信封。前端生成一个随机的 16 字节 AES 密钥用 AES 加密完整的 JSON 请求体得到一个任意长度都可承载的密文再用 RSA 公钥只加密这个 AES 密钥本身。后端收到两个字段后先用私钥 RSA 解开拿到 AES 密钥再用它解请求体。RSA 只处理几十个字节的密钥AES 处理真正的业务数据性能和长度限制同时解决。这个方案的关键参数有四组AES 算法选择AES-128-GCM或AES-256-GCMGCM 模式自带完整性校验AES 密钥每次请求随机生成不要复用RSA 密钥保持 2048 位传输时两个密文都做 Base64 编码。前端实现可以借助crypto-js或 Web Crypto API但 uniapp App 端对 Web Crypto 的支持不统一我更建议把混合加密的逻辑封装成独立模块H5 和 App 端各自验证一遍。做完了之后加一个自检本地循环加密解密 100 次统计平均耗时。RSA 2048 位加密一次通常在几十毫秒如果单次超过 200 毫秒再考虑要不要退回到后端加密方案。最后聊一个习惯。我现在接手任何和加密相关的需求第一件事不是写代码而是先问三个问题明文最长有多长、公钥和私钥分别由谁管理、后端用什么字符编码来解。这三个问题不定下来后面百分之百翻车。曾经有一个项目前端用了混合加密后端也照着接好了结果上线当晚发现后端解 AES 时把密钥字段当成普通字符串做了 Base64 解码整批登录请求失败最后回滚到明文传输等第二天修复。RSA 本身不难难的永远是约定。希望帮到你。本文还有配套的精品资源点击获取