尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

jsencrypt 前端 RSA 加解密实战:密钥格式、分段与 uniapp 跨端避坑

发布时间:2026/9/25 21:06:18

资讯中心
01
ARTICLE

jsencrypt 前端 RSA 加解密实战:密钥格式、分段与 uniapp 跨端避坑

jsencrypt 前端 RSA 加解密实战:密钥格式、分段与 uniapp 跨端避坑
简介对于需要在uni-app或普通前端项目中对敏感信息进行RSA加密传输的开发者这是一套可直接落地的轻量方案。方案基于jsencrypt库核心是一份修正后的jsencrypt.js可规避uni-app运行时的兼容报错同时附带封装好的rsa.js统一导出加密与解密方法开发者可在业务代码中直接引用免去自行调试RSA流程的麻烦。压缩包内共3个文件由两个JavaScript文件和一个txt说明文档组成整体仅37KB结构精简、易集成到既有工程。说明文档对公钥私钥生成、保存及调用方式做了归纳结合封装代码可快速理解RSA在前后端交互中的使用要点。目前该资源已有2805人下载学习无论浏览器端传统页面还是uni-app跨端项目均可直接复用适合涉及登录、支付等敏感数据保护场景的前端工程师参考。1. jsencrypt 不是黑匣子前端 RSA 加解密到底解决什么问题登录页把密码从明文变成密文是前端最常被问到的一段代码。jsencrypt 是这里最普及的 RSA 加解密库页面拿公钥加密服务端拿私钥解密密文走网络也不怕被截包直接读。这个库在普通浏览器里一行 import 就能用放到 uniapp 的 H5、App 端也能跑小程序端做一层宿主兼容也能跑。网上很多前端面试题就喜欢围绕这套流程追问为什么 encrypt 会返回 false密钥格式 PKCS#1 和 BEGIN PUBLIC KEY 有什么区别长文本怎么分段。下面按密钥格式、最小代码、跨端封装和踩坑记录一次讲完新手能照抄熟手可以直接看第 5 章避坑。2. 先对齐 RSA 密钥体系格式决定 encrypt 成不成功RSA 本身不复杂但前端对接最容易挂在“密钥格式”上而不是挂在算法理解上。很多人在浏览器里调通了一段代码换到 uniapp 或者换一套后端密钥就发现encrypt返回 false追根溯源都是公钥 PEM 第一行不一样。这一章先把密钥方向、生成方式和格式判别讲清楚。2.1 非对称加密方向前端只碰公钥私钥留在服务端RSA 是典型的非对称加密一对密钥由一个公钥和一个私钥组成公钥加密的内容只有配对的私钥能解开。反过来如果用私钥加密所有人拿公钥都能解开这种用法只能做签名验证不能做保密传输。所以前端加密登录密码的正确姿势是公钥下发到前端明文在浏览器里加密密文传给服务端服务端用私钥解密并验证。这段逻辑面试几乎必问。追问通常是既然公钥是公开的攻击者能不能拿公钥把密文解回来答案是不能。公钥和私钥虽然在数学上关联但只能单向运算从公钥推不出私钥这也是 RSA 能解决前端敏感字段传输问题的根基。理解了这一点你就明白为什么网上那些“前端用私钥加密、后端用公钥解密”的代码只适合本地演示线上这么干等于把验证规则暴露给所有人。jsencrypt 在代码层面没有强制你只用公钥加密它同样提供setPrivateKey和decrypt方便做本地自测。但正式项目里私钥一旦放进前端打包产物就等于把门锁钥匙贴在门上。uniapp 打包后的 JS 代码可以被反编译私钥混淆一下也只是掩耳盗铃。自己测试可以用私钥解密生产环境永远把私钥留在后端前端只持有公钥外加一个能从后端拉公钥的接口。2.2 OpenSSL 生成 RSA 密钥对一条命令看清三种 PEM常见做法是用 OpenSSL 生成密钥对。拿到的文件是 PEM 格式本质是一段 Base64 文本外面套了头尾标识。你不需要背下整个 ASN.1 结构但必须能看懂首行。# 生成 2048 位 RSA 私钥PKCS#1 格式首行 BEGIN RSA PRIVATE KEY openssl genrsa -out private_key.pem 2048 # 导出公钥默认是 X.509 SPKI 格式首行 BEGIN PUBLIC KEY openssl rsa -in private_key.pem -pubout -out public_key_spki.pem # 导出公钥PKCS#1 RSAPublicKey 格式首行 BEGIN RSA PUBLIC KEY openssl rsa -in private_key.pem -RSAPublicKey_out -out public_key_pkcs1.pem # 查看公钥的模长和指数确认密钥位数 openssl rsa -pubin -in public_key_pkcs1.pem -text -noout第一行genrsa生成的是 PKCS#1 私钥首行是-----BEGIN RSA PRIVATE KEY-----很多传统 RSA 代码用的就是这种。第二行导出公钥的 SPKI 格式首行-----BEGIN PUBLIC KEY-----Java 的KeyPairGenerator和 Node 的crypto默认输出都是这种。第三行是为了兼容 jsencrypt 老版本习惯而导出的 PKCS#1 公钥首行-----BEGIN RSA PUBLIC KEY-----。genrsa的2048是密钥位数这个参数直接决定能加密多长的明文1024 位对应 117 字节上限2048 位对应 245 字节上限。现代项目建议直接用 2048一方面安全性更强另一方面前端分段压力也小一点。最后一行是排障工具如果输出的 modulus 显示 2048 bit说明密钥没问题如果显示 1024说明密钥偏弱建议重新生成。仓库里永远不要把private_key.pem提交进 git前端配置文件里能出现的只有公钥。2.3 公钥格式PKCS#1 与 X.509 的判别方法jsencrypt 对公钥的解析逻辑在不同版本里变过。老版本主要按 PKCS#1 的 RSAPublicKey 结构去读而很多后端接口给的是 X.509 SPKI 格式也就是首行BEGIN PUBLIC KEY的那种公钥。这种不对齐会演变成两个方向要么setPublicKey直接抛错要么encrypt安静地返回 false。首行习惯叫法常见来源对接建议-----BEGIN PUBLIC KEY-----X.509 SPKI / 常被叫 PKCS#8 公钥Java KeyPairGenerator、Node crypto、openssl rsa -pubout先确认项目内 jsencrypt 版本能否解析不稳就转格式-----BEGIN RSA PUBLIC KEY-----PKCS#1 RSAPublicKeyopenssl rsa -RSAPublicKey_out大多数 jsencrypt 示例的默认格式解析最顺排障第一步永远是看公钥文件首行head -1 public_key.pem # -----BEGIN PUBLIC KEY----- # 或 # -----BEGIN RSA PUBLIC KEY-----如果后端给的是第一种而你又没法让后端改导出方式自己转一下最快openssl rsa -pubin -in public_key_spki.pem -RSAPublicKey_out -out public_key_pkcs1.pem这条命令把 SPKI 公钥转成 PKCS#1 公钥转完后再给 jsencrypt 使用。注意私钥也有同样的 PKCS#1BEGIN RSA PRIVATE KEY和 PKCS#8BEGIN PRIVATE KEY之分setPrivateKey报错时同样先看首行。前端面试题里问 RSA 加解密多半会顺带问格式其实就是想看候选人有没有被这个常规坑咬过。3. 在浏览器里跑通 jsencrypt 最小加解密密钥就绪后先把最小闭环在浏览器里跑通。这一步不涉及 uniapp但它是后面跨端方案的基础。代码不依赖框架直接开一个测试页就能验证。3.1 安装与引入npm 为主CDN 兜底工程化项目用 npm 安装npm install jsencrypt引入方式取决于模块规范// 现代工程 / uniapp Vue3 import JSEncrypt from jsencrypt // CommonJS 老工程 // const JSEncrypt require(jsencrypt)jsencrypt的主入口导出的是一个构造函数new JSEncrypt()生成一个加密实例实例上挂setPublicKey、setPrivateKey、encrypt、decrypt四个方法。引入时报错先确认 npm 包有没有装进 dependenciesuniapp 项目还要确认包是否被放在了不会被打包器摇树优化掉的位置。如果在 CDN 场景使用直接用bin/jsencrypt.min.js会挂到全局JSEncrypt变量逻辑和 npm 版本一致。3.2 最小可用加解密从 setPublicKey 到 decrypt 的一轮闭环把下面代码放进任意浏览器页面跑一轮加密再解密闭环通过就说明密钥和库本身都没问题。import JSEncrypt from jsencrypt const publicKey -----BEGIN RSA PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC... -----END RSA PUBLIC KEY----- const privateKey -----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQC... -----END RSA PRIVATE KEY----- function rsaEncrypt(plainText) { const crypt new JSEncrypt() crypt.setPublicKey(publicKey) const cipherText crypt.encrypt(plainText) if (cipherText false) { throw new Error(encrypt 返回 false先查公钥格式和明文长度) } return cipherText } function rsaDecrypt(cipherText) { const crypt new JSEncrypt() crypt.setPrivateKey(privateKey) return crypt.decrypt(cipherText) || } // 自测控制台应输出 true const encrypted rsaEncrypt(hello-rsa) console.log(encrypted) console.log(rsaDecrypt(encrypted) hello-rsa)每次调用都new一个实例是为了避免复用实例时上层逻辑把 key 或内部状态改乱。setPublicKey如果遇到不认识的首行会抛异常所以更稳的写法是 try/catch 包住上面的示例把异常交给调用方处理。encrypt成功后返回 Base64 字符串失败则返回false不会抛错——这是很多人把空字符串传给后端的原因调用方忘了判断 false。私钥那段只用于本地自测线上前端永远不要出现。后端对接时还要对齐填充方式。jsencrypt 默认使用 PKCS#1 v1.5 paddingJava 侧对应Cipher.getInstance(RSA/ECB/PKCS1Padding)Node 侧对应crypto.constants.RSA_PKCS1_PADDING。如果你后端用了 OAEP两边密文格式就对不上报错会非常诡异。3.3 超过单次加密上限先编码再分段RSA 单次加密能容纳的明文长度是固定的等于密钥字节数减 11。1024 位密钥是 128 字节减 11 得到 117 字节2048 位密钥是 256 字节减 11 得到 245 字节。登录密码这种短字段不会撞上限但用户昵称、手机号加备注这类长字段就很容易让encrypt返回 false。const MAX_CHUNK_SIZE 117 // 1024 位密钥用 1172048 位改成 245 function segmentEncrypt(plainText, publicKey) { const crypt new JSEncrypt() crypt.setPublicKey(publicKey) // 中文先编码成 ASCII避免字符编码差异 const encoded encodeURIComponent(plainText) const chunks [] for (let start 0; start encoded.length; start MAX_CHUNK_SIZE) { const piece encoded.slice(start, start MAX_CHUNK_SIZE) const encrypted crypt.encrypt(piece) if (encrypted false) { throw new Error(分段 ${start / MAX_CHUNK_SIZE 1} 加密失败) } chunks.push(encrypted) } return chunks.join(|) }这里有两个关键参数。第一个是MAX_CHUNK_SIZE117它对应 1024 位密钥换成 2048 位密钥必须改成 245否则每段加密会因超长继续返回 false。第二个是encodeURIComponent它把中文变成%E4%B8%AD这样的纯 ASCII 序列每个字符恰好对应一个字节切片长度才准确。后端收到每段 Base64 后先按|分割逐段解密最后decodeURIComponent还原原文。|不会出现在 Base64 字符集里所以用它做分段分隔符不会撞车。4. 封装成 uniapp 的 RSA 工具一份代码对接三端uniapp 能编译到 H5、App 和各家小程序但 jsencrypt 不是所有端都长一个样。这一章先讲端与端的差异再给出一份可以直接抄的封装最后处理小程序没有 window 的问题。4.1 uniapp 三端差异能直接用但小程序要照顾宿主H5 端本质是浏览器页面jsencrypt 按浏览器用法直接跑。App 端虽然不在浏览器里但 uniapp 的 JS 运行环境仍然保留了标准 JavaScript 能力加密这种纯计算逻辑一般也能直接跑。真正容易出问题的是微信小程序没有window、document、navigator这些 BOM 对象jsencrypt 初始化时如果引用了它们就会抛 undefined 报错。另一个差异在模块加载。小程序端用的是 CommonJS 和自身模块系统npm 包里的 ESM 产物不一定能被正确打包。常见做法有两种要么继续用 npm 依赖把兼容 shim 补上要么把jsencrypt.min.js复制到项目本地作为普通 JS 文件手动导出。下面给出的是 npm 思路的封装。4.2 封装 utils/rsa.js一个模块统一加密入口在 uniapp 项目里新建utils/rsa.js把加解密方法统一封装业务页面只关心明文和公钥不关心 jsencrypt 内部细节。// utils/rsa.js import JSEncrypt from jsencrypt export function encryptByPublicKey(plainText, publicKey) { if (!plainText || !publicKey) return const encryptor new JSEncrypt() try { encryptor.setPublicKey(publicKey) } catch (error) { console.error(setPublicKey 失败检查公钥格式) return } const encrypted encryptor.encrypt(plainText) if (encrypted false) { console.error(encrypt 返回 false检查明文长度和公钥) return } return encrypted } export function decryptByPrivateKey(cipherText, privateKey) { if (!cipherText || !privateKey) return const decryptor new JSEncrypt() try { decryptor.setPrivateKey(privateKey) } catch (error) { console.error(setPrivateKey 失败) return } return decryptor.decrypt(cipherText) || }这个封装做了三件事空参数直接返回空串避免把空值送进加密实例setPublicKey的异常被捕获转化为一条可读日志encrypt返回 false 时不静默吞掉而是记录原因。业务侧调用时要注意返回值不能直接当密文发送先判空再走接口否则后端拿到空密码登录永远失败。实际登录页用法import { encryptByPublicKey } from /utils/rsa.js import { rsaPublicKey } from /config/api.js function login(account, password) { const encryptedPassword encryptByPublicKey(password, rsaPublicKey) if (!encryptedPassword) { uni.showToast({ title: 密码加密失败, icon: none }) return } uni.request({ url: https://api.example.com/login, method: POST, data: { account, password: encryptedPassword }, success: () {} }) }uni.showToast只是提示真正的判断逻辑是if (!encryptedPassword)。生产代码里这一步失败时要上报日志而不是只弹一个 toast因为加密失败通常意味着公钥配置被改坏或者后端密钥轮换了。4.3 小程序端没有 window本地化 jsencrypt 的补丁写法如果 uniapp 编译到微信小程序后出现window is not defined或navigator is not defined最直接的兜底方案是本地化 jsencrypt。我一般这样处理把node_modules/jsencrypt/bin/jsencrypt.min.js的内容复制到项目common/jsencrypt.js在文件最前面补两行// common/jsencrypt.js var window window || {} var navigator navigator || {}原理是让这个模块在自己的作用域内建立一个局部window和navigator避免顶层 BOM 引用直接抛错。补完后按 CommonJS 导出业务代码改成// 小程序端手动导入 const JSEncrypt require(../common/jsencrypt.js)这个补丁只对纯计算逻辑有效。如果真实机型上仍然报getRandomValues相关错误说明该版本 jsencrypt 的随机数源依赖浏览器 crypto API此时不要在页面里继续硬扛把“密码加密”改成调用后端加密接口或云函数更稳。测试阶段用Math.random补随机数看似能跑但 RSA 填充的随机性直接影响安全强度生产环境不建议这么凑合。4.4 公钥管理常量指定还是接口请求公钥不是机密放前端常量没问题但密钥轮换时要重新发版。常见的做法是两种并存初期把公钥写在config/api.js里后端要换钥时再改成接口下发。// config/api.js export default { rsaPublicKey: -----BEGIN RSA PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC... -----END RSA PUBLIC KEY----- }如果走接口下发在登录前用一个uni.request拉取公钥并缓存。注意 PEM 字符串的换行setPublicKey会自行解析头尾和 Base64 正文字符串中的\n保留完整即可不要手动去掉。从接口拿回来的公钥如果带了多余空白字符先trim()再交给加密函数。5. 避坑指南encrypt 返回 false 背后的五个常见原因这一章是实战里最容易让新手翻车的地方。每条按“现象 → 原因 → 解决”写照着排除基本能定位问题。5.1 encrypt 返回 false不是库坏了是长度或密钥头不对现象encrypt()返回false代码不抛错接口收到的密码字段是空串。原因有两个一是明文超过单次加密上限1024 位密钥超过 117 字节就失败2048 位密钥超过 245 字节失败二是公钥格式和 jsencrypt 解析器不匹配集中在BEGIN PUBLIC KEY和BEGIN RSA PUBLIC KEY的差异上。解决先用第 3.3 节的分段函数排除长度问题再head -1看公钥首行是BEGIN PUBLIC KEY就按第 2.3 节转换。这也是前端面试题里常见的追问场景答出“先查格式再查长度”基本就过关了。5.2 公钥头是 BEGIN PUBLIC KEYjsencrypt 不认现象前端从 Java 或 Node 后端拿到的公钥首行是-----BEGIN PUBLIC KEY-----加密结果要么抛异常要么返回 false。原因这是 X.509 SPKI 格式而老版本 jsencrypt 对 PKCS#1 的-----BEGIN RSA PUBLIC KEY-----解析最稳。解决优先让后端换导出方式后端不方便改就用 OpenSSL 转换。openssl rsa -pubin -in spki.pem -RSAPublicKey_out -out pkcs1.pem转换后的文件首行变成BEGIN RSA PUBLIC KEY再交给前端。如果项目里已经有大量代码用 SPKI 公钥并且当前版本能跑那就保持现状但要在配置处注释清楚“不要随意换公钥来源”免得后人换一个格式不同的密钥又把问题带回来。5.3 中文或特殊字符解密乱码加密前先 encodeURIComponent现象加密“你好”后后端解出来是N-之类的字符。原因不同版本的 jsencrypt 对 Unicode 字符的处理方式不一致有的按 UTF-8 字节序列编码有的直接取 charCode后端再用标准 UTF-8 解码就乱了。解决前端加密前先encodeURIComponent(plainText)让所有字符变成 ASCII 百分号编码后端解密后再decodeURIComponent还原。这个方法也能解决用户名里带空格、、/会被某些常规 Base64 处理改掉的问题。注意前端和后端要同时约定后端只看解密结果也要做decodeURIComponent。5.4 微信小程序报 window is not defined本地化加 shim现象编译到小程序控制台直接报window is not defined或者navigator is not defined。原因小程序宿主没有浏览器 BOM 对象jsencrypt 初始化时引用了它。解决把jsencrypt.min.js复制到本地文件头部加var window window || {}补一个局部对象再按第 4.3 节方式导入。如果补完还是报 crypto 相关错就把加密逻辑后移到服务端小程序只做传输不在本地算 RSA。5.5 密文进 URL 后被截断加号变空格或斜杠丢失现象前端加密完的 Base64 串放在 URL query 里传给后端后端说解不开或者后端拿到的变成了空格。原因Base64 字符集包含、/、URL 参数传输时会被解析成空格/可能截断路径。解决不要把密文直接拼 URL要么放 POST body 的 JSON 字段里要么encodeURIComponent(cipherText)后再拼接。uniapp 里uni.request走 POST body 时一般安全但 GET 和其他自定义 header 场景都要先编码。这个坑和加解密本身无关却会让加密链路看着像 RSA 的锅。6. 进阶验证用 Node.js 把前端密文完整解回去如果你负责对接的后端是 Node.js可以用它快速验证前端 jsencrypt 的产出。这一步不是重写加解密而是把完整链路打通前端加密 → 网络传输 → 服务端解密任何一环格式不对都会立刻暴露。// verify.js const crypto require(crypto) const fs require(fs) const privateKey fs.readFileSync(./private_key.pem, utf8) const cipherText process.argv[2] // 前端加密后的 Base64 字符串 const decrypted crypto.privateDecrypt( { key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING }, Buffer.from(cipherText, base64) ) console.log(decrypted.toString(utf8))运行方式前端先rsaEncrypt(hello-rsa)得到一段 Base64然后拷贝到命令行执行node verify.js 密文。这里有两个参数要和前端对齐padding必须是RSA_PKCS1_PADDING这是 jsencrypt 默认的填充方式密文必须是 Base64 解码不能直接把字符串当成 UTF-8 传给privateDecrypt。如果解密出来是乱码回到第 5.3 节检查是否缺了encodeURIComponent。如果后端不是 Node.js也可以用同样的思路在本地起一个解密脚本或者让后端同事提供一段最小解密接口。验证通过后把第 2.2 节生成的临时密钥删掉换成后端下发的正式密钥。我现在的习惯是每个项目开工第一天先用这段脚本跑通一次闭环把“前端公钥格式、后端解密 padding、传输编码”三个点全部确认完再写业务代码。这个习惯帮我挡掉了绝大多数加密对接故障。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。