资讯详情

前端 RSA 加密实战:jsencrypt 使用教程与 uniapp 避坑指南

📅 2026/10/14 4:01:36 | 华诺云谱 👁 阅读
前端 RSA 加密实战:jsencrypt 使用教程与 uniapp 避坑指南
简介针对前端项目中需对敏感信息进行RSA加密后再传输给服务器的常见需求这份小工具包面向使用原生JavaScript或uni-app框架的开发者提供可直接引入的加密解密方案兼容常规H5页面与小程序的加密封装调用。压缩包共3个文件包含2个JavaScript文件和1个文本说明文件整体仅37KB。其中加密库文件为专门修正过的版本解决了uni-app工程引入普通库时报错的问题封装配置文件提供统一的加密与解密方法开发者只需替换公钥私钥即可调用文本说明文件则用于记录密钥对生成及保存的关键提示。目前已有2805人学习下载适用于对数据安全有要求、又希望避免在移动端与小程序端重复踩坑的前端开发者。通过这套资源用户可以跳过繁琐的兼容性排查快速完成RSA加解密功能部署并理解如何在实际项目中规范地生成、配置和管理密钥对。1. 前端 RSA 加密落地为什么 jsencrypt 是绕不开的那个库做前端的这些年只要涉及登录、支付、用户敏感信息提交几乎都会撞上 RSA 加密。后端扔给你一串公钥前端加密后端私钥解密这套流程在 PC 端、H5、小程序里反复出现。jsencrypt 这个库是我在多个项目里实际用过的它不依赖原生 crypto 模块纯 JavaScript 实现打包后体积也不算大在 uniapp 这种非标准浏览器环境里也能跑通。这篇文章不打算从 RSA 数学原理开始讲直接说怎么用、参数怎么设、哪些环节最容易翻车。适合两类人一类是刚接手前端加密任务、需要快速把 RSA 跑通的初级开发者另一类是已经写过不少加密逻辑、但总在 iOS 兼容性或者长文本解密上栽跟头的熟手。后面全部用我踩过的坑说话。2. jsencrypt 的安装与基础用法先分清加密、解密、签名三件事2.1 安装版本选择npm 包和源码版各有什么坑jsencrypt 在 npm 上的包名是jsencrypt我建议直接安装已发布版本而不是从 GitHub 拉源码。因为源码版经常有人忘记编译 dist 目录拿到的文件里只有 ES6 源码在一些老项目里跑不起来。npm install jsencrypt --save # 或者用 yarn yarn add jsencrypt安装完后在 PC 端页面里可以直接这样引入import JSEncrypt from jsencrypt这个库对外暴露的入口文件是bin/jsencrypt.min.jsnpm 包的 main 字段已经指好了不需要手动指定路径。如果你用的是 uniapp直接用上面的 import 也可以但要注意后面会提到的 polyfill 问题。关于版本我目前使用较多的是 3.x它修复了早期版本的一些字符集问题。如果你之前用的是 2.x在升级后需要重点测试中文加密和解密因为 3.x 对 UTF-8 的处理更严格老版本可能出现乱码。2.2 公钥加密的基本姿势核心 API 只有两个jsencrypt 的 API 设计得极其简单实际生产中大部分场景只需要用到encrypt和decrypt两个方法。// 加密端 import JSEncrypt from jsencrypt const publicKey -----BEGIN PUBLIC KEY-----\n...你的公钥...\n-----END PUBLIC KEY----- const encryptor new JSEncrypt() encryptor.setPublicKey(publicKey) const plainText hello rsa const encrypted encryptor.encrypt(plainText) console.log(encrypted) // Base64 编码的密文这段代码里有两个关键点。第一setPublicKey接收的是完整的 PEM 格式公钥包括BEGIN/END头和换行符不要自己手动去掉换行。第二encrypt返回的是 Base64 字符串后端拿到后通常直接 Base64 解码再走私钥解密流程。我经常看到有人把公钥写成一行去掉换行后报错。实际上 JSEncrypt 内部对换行符是宽容的但前提是头尾标记不能丢。有些后端的公钥是 PKCS#8 格式BEGIN PUBLIC KEY有些是 PKCS#1 格式BEGIN RSA PUBLIC KEY这两种 jsencrypt 都支持但如果你用的是自写的加密工具类就得问清楚后端是哪种格式。2.3 私钥解密别只用公钥加密就完事在 HTTP 环境下只用公钥加密已经不安全了因为公钥是公开的任何人可以构造合法密文。所以真实项目里往往还要配合私钥签名。但如果你确实需要前端做解密比如后端下发了用私钥加密的数据就用decrypt// 解密端 const privateKey -----BEGIN PRIVATE KEY-----\n...你的私钥...\n-----END PRIVATE KEY----- const decryptor new JSEncrypt() decryptor.setPrivateKey(privateKey) const decrypted decryptor.decrypt(encryptedText) console.log(decrypted) // 明文这里要注意decrypt方法内部默认处理的是 RSA 的 PKCS#1 v1.5 padding不是 OAEP。如果后端用的是 RSA/ECB/OAEPWITHSHA-256 这种算法jsencrypt 就不支持了需要换库或者让后端配合调整。我在某个项目里就遇到过后端坚持用 OAEP前端换成了 node-forge 才解决。2.4 加签验签接口防篡改的常用姿势除了加密解密jsencrypt 还提供了sign和verify方法用于数字签名场景。前端用私钥签名后端用公钥验签这样能保证数据没有被中间人篡改。const signer new JSEncrypt() signer.setPrivateKey(privateKey) const signature signer.sign(plainText, JSEncrypt.MD5) // 第二个参数可以传 SHA256、RSA-SHA256 等取决于后端要求 const verifier new JSEncrypt() verifier.setPublicKey(publicKey) const isValid verifier.verify(plainText, signature, JSEncrypt.MD5) console.log(isValid) // truesign的第二个参数是哈希算法。默认的JSEncrypt.MD5在现在的安全要求下已经不太推荐但很多老项目的后端还保留着 MD5 的验签逻辑。如果你能跟后端商量优先用JSEncrypt.SHA256或者RSA-SHA256。注意这里的算法字符串要和后端的KeyGenerator对应否则验签会一直失败。3. uniapp 环境适配搞定原生加密模块缺失和字符集两个大坑3.1 H5 端直接可用但小程序端需要处理 window 对象uniapp 的 H5 端因为跑在浏览器里使用 jsencrypt 基本无障碍直接 import 即可。但到了微信小程序或者 App 端JSEncrypt 内部依赖window对象而小程序环境里没有window所以第一步就要处理全局对象。// 在 main.js 或入口文件里做兼容 // 小程序环境模拟 windowjsencrypt 内部会访问 window.crypto // 实际上 jsencrypt 需要的是 btoa/atob 和 crypto 对象 // 常见做法是在引入 jsencrypt 之前先定义全局变量 if (typeof window undefined) { global.window global }这行代码能解决部分报错但接下来你会遇到更麻烦的问题crypto.getRandomValues不存在。jsencrypt 的随机数生成依赖原生 crypto 对象小程序里没有这个 API导致每次加密都会抛异常。解决方式有两种一种是引入一个随机数 polyfill另一种是改用纯数学随机数生成。我推荐用 polyfill因为纯数学随机数在安全强度上有隐患。// 随机数 polyfill 示例放到 window 兼容之后 if (typeof window ! undefined !window.crypto) { window.crypto { getRandomValues: function(array) { for (let i 0; i array.length; i) { array[i] Math.floor(Math.random() * 256) } return array } } }注意这个 polyfill 只是让程序跑起来如果用在高安全要求的场景比如支付密码建议后端配合使用其他方案。Math.random 不是密码学安全的随机数生成器这一点必须清楚。3.2 uniapp 编译后 JSEncrypt 被 tree-shaking 误伤我在 uniapp 项目里还遇到过一个奇怪问题H5 构建正常但发布到微信小程序后encrypt方法变成一个空函数。查了半天发现是打包工具把 JSEncrypt 里的某些分支代码给优化掉了因为那些代码里有浏览器环境特有的对象引用。解决办法是关闭对这个库的 tree-shaking或者在引入时明确指定全局引用。// 强制引入完整包 import JSEncrypt from jsencrypt/bin/jsencrypt.min // 或者用 require 方式 const JSEncrypt require(jsencrypt/bin/jsencrypt.min)如果上述方法还不行就把这个 JS 文件放到common目录下用import相对路径引入。类似这样import JSEncrypt from /common/jsencrypt.min.js引入后建议真机预览测一下因为开发者工具里的环境和小程序运行时还是有差别。3.3 中文加密乱码setPublicKey 之后要指定字符集中文乱码是 jsencrypt 使用中出镜率最高的坑。原因在于 RSA 加密是按字节处理的jsencrypt 默认把字符串转成 Latin-1 字符集中文直接超出范围。某个版本之后JSEncrypt 提供了setKey的编码参数但更通用的做法是加密前手动做 UTF-8 编码转换。// 加密前统一转为 UTF-8 function utf8ToBytes(str) { const encoder new TextEncoder() return encoder.encode(str) } // 使用方式 const encrypted encryptor.encrypt(utf8ToBytes(plainText).toString())这种方式在某些场景下可以绕过去但不是最干净的。我推荐一个更直接的做法调整 JSEncrypt 内部使用的编码方式。// 在引入后修改 JSEncrypt 原型上的转换函数 JSEncrypt.prototype._getKey function(Key) { // 原本使用的是 wc.encode改成 UTF-8 编解码 }这个做法需要你对源码有足够理解新手容易改坏。所以我在生产环境里更推荐用 TextDecoder 做显式转换// 解密后手动解码 const bytes decryptor.decrypt(encryptedText) const decoder new TextDecoder(utf-8) const plainText decoder.decode(bytes)3.4 不同端的 Base64 差异出现“”号变成空格RSA 加密输出的 Base64 字符串里可能含有、/、这些字符。在某些后端框架里会被 URL 解码成空格导致解密失败。这不是 jsencrypt 的问题是编码层的坑。所以我每次在 uniapp 里把密文传给后端时都会做一层 encodeURIComponent后端做 decodeURIComponent 后再处理。// 提交密文时 const safeEncrypted encodeURIComponent(encrypted) // 后端拿到后先 decodeURIComponent 再解密4. RSA 加密长度极限与分段加密不是你想加多长就加多长4.1 1024 和 2048 位密钥一次能加密多少字节RSA 的加密能力受限于密钥长度。以 1024 位密钥为例最大可加密字节数是 128 字节但还要减去 padding 占用的 11 字节PKCS#1 v1.5 padding所以实际明文最多是 117 字节。2048 位密钥对应的就是 256 字节总长明文上限 245 字节。根据这个参数jsencrypt 并没有内置分段加密功能。换句话说如果你直接把超过 117 字节的中文文本传给encrypt它会抛出异常或者返回空字符串。这里给一张常用的参数表密钥长度总字节数padding 开销单次最大明文长度1024 位12811117 字节2048 位25611245 字节4096 位51211501 字节注意这里说的字节长度ASCII 字符一个字节中文 UTF-8 编码下通常是三个字节所以能承载的中文字符更少。4.2 大文本加密时的分块实现按块加密再拼接既然单次加密有极限那就分段。常见的做法是把明文按固定长度切片然后逐块加密最后把每块密文拼接起来由后端按同样规则拆分解密。function rsaLongEncrypt(str, publicKey, maxLen 100) { const encryptor new JSEncrypt() encryptor.setPublicKey(publicKey) const total str.length const chunks [] for (let i 0; i total; i maxLen) { const chunk str.substr(i, maxLen) const encrypted encryptor.encrypt(chunk) if (!encrypted) { throw new Error(加密第 ${i} 块失畋请检查密钥或长度) } chunks.push(encrypted) } return chunks.join(|) }这个方案里maxLen不能超过密钥对应的上限。1024 位密钥建议取 1002048 位可以取 200留一点余量防止特殊字符编码后长度超出。分段符我用了|但建议你的项目里用一个后端不会看错的特殊分隔符。更好的做法是把密文数组直接 JSON 序列化后传过去后端解析数组再逐段解密避免分隔符冲突。4.3 后端解密失败时的排查方向先确认分块顺序分段加密顺利跑通后端起先失败这是最常见的场景。现象是报错javax.crypto.BadPaddingException或者解密出来前半段是明文、后半段是乱码。原因通常有三种第一分段加密时块的顺序在后端被打乱第二前端用|拼接后密文里有换行符被后端截断了第三后端把整个拼接后的字符串当成一整段去 Base64 解码。我的排查路径一般是先在后端日志打印收到的密文长度如果长度明显大于单块密文的合理长度就说明是拼接后整体解密。这时候让后端改成按分隔符拆分或者前端直接把数组传给后端。4.4 超长文本压测最优分段长度怎么调到不崩分段长度不是越大越好。我见过有人直接把maxLen设成 117结果每次加密十次有两次失败。原因在于中文字符在 UTF-8 编码下是 3 个字节117 个中文字符实际编码后是 351 字节远超限额。我一般的习惯是先测出密钥位数再根据待加密数据的字符类型估算字节数。如果全是中文1024 位密钥的maxLen取 30 比较稳。如果是中英混排取 50。我还会写一段快速压测代码function testEncryptLength(publicKey, testStr, maxLen) { const encryptor new JSEncrypt() encryptor.setPublicKey(publicKey) try { const result encryptor.encrypt(testStr.substr(0, maxLen)) return result ? true : false } catch(e) { return false } }这段代码跑一轮就能确定当前密钥下能加密的最大长度。偷懒的话直接按字节计算也行但在生产环境我会建议多留 10% 的余量。5. 避坑指南jsencrypt 六大常见问题和排查方法5.1 现象一加密结果为空字符串原因明文长度超过密钥限制或者公钥格式不正确。解决先看公钥是否包含完整的 PEM 头和尾。再用短文本测试比如hello如果短文本加密正常说明是长度超限。检查明文长度按 4.2 节的分段方式处理。5.2 现象二加密时报 “Cannot read property encrypt of undefined”原因在 uniapp 或小程序环境里前面说的window对象未模拟成功或者模块加载顺序有问题。解决把global.window global放到引入 jsencrypt 之前并确认没有其他插件先把window覆盖掉。如果你用import引入了压缩版改成require方式。5.3 现象三加密中文后解密乱码原因字符集不匹配jsencrypt 的默认编码处理在某些版本里不支持 UTF-8。解决加密前把字符串转为 UTF-8 字节数组解密后用TextDecoder恢复。或者修改源码里对应的编解码函数。5.4 现象四前端加密后后端总是解密失败且异常信息是 “javax.crypto.IllegalBlockSizeException”原因后端得到的密文长度不是密钥位数的八分之一。比如 1024 位密钥Base64 解码后的密文长度应为 128 字节。解决检查传输过程中是否发生 URL 编码变化特别是密文里含号。按 3.4 节的做法前端做encodeURIComponent后端先decodeURIComponent再做 Base64 解码。5.5 现象五同一个明文每次加密结果都不同后端偶尔验签失败原因RSA 加密本身带随机 padding所以每次结果不一样是正常的。验签失败单独排查签名算法和哈希算法是否匹配。解决确认后端验签使用的哈希算法。如果后端是SHA256withRSA前端对应的sign第二个参数要传SHA256或者RSA-SHA256而不是JSEncrypt.MD5。5.6 现象六在 iOS 的 WKWebView 浏览器上encrypt 方法直接卡死原因某些 iOS 版本对 JSEncrypt 里用到的crypto.getRandomValues支持不完整或者 WebView 中安全策略限制了某些随机数生成方式。解决升级 JSEncrypt 到 3.x 最新版并且在页面加载时主动预热加密功能进入页面后立刻做一次短文本加密初始化。如果仍卡死考虑切换成 node-forge 作为备选。6. 进阶技能自定义密钥对生成与在线验证手段6.1 用 OpenSSL 生成自己的测试密钥对很多场景下后端只能给你公钥但你自己联调时需要一对密钥这时用 OpenSSL 最快。# 生成 2048 位私钥 openssl genrsa -out private_key.pem 2048 # 从私钥导出公钥 openssl rsa -in private_key.pem -pubout -out public_key.pem # 查看公钥内容 cat public_key.pem生成后两个文件里的 PEM 字符串就是 jsencrypt 需要的格式。私钥文件一般用BEGIN PRIVATE KEY这是 PKCS#8 格式。如果某些后端只认 PKCS#1 格式可以转换# 转换为 PKCS#1 格式 openssl rsa -in private_key.pem -traditional -out private_pkcs1.pem6.2 用 Node.js 快速验证前端密文是否可解密前端调通了之后我习惯在本地用 Node.js 的 crypto 模块模拟后端解密确认整个链路没问题。// 将前端生成的密文粘贴到这里验证 const crypto require(crypto) const fs require(fs) const privateKey fs.readFileSync(./private_key.pem, utf8) const encryptedBase64 这里贴上你前端加密后的密文 const buffer Buffer.from(encryptedBase64, base64) const decrypted crypto.privateDecrypt( { key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING }, buffer ) console.log(解密结果, decrypted.toString(utf8))这个脚本不依赖任何第三方库适合在任何 Node 环境里跑。如果这里能解出来后端大概率也能解出来。如果这里失败说明前端生成的密文本身有问题后端就不用看了。6.3 生产环境中的密钥轮换与前端缓存问题最后一个实用的技巧业务公钥不要写死在前端代码里。建议后端提供一个获取公钥的接口前端每次拿到公钥后加密。这样密钥轮换时前端不用发新版。// 获取公钥并缓存 let cachedPublicKey null async function getPublicKey() { if (cachedPublicKey) return cachedPublicKey // 避免在小程序里使用 window 缓存 const res await fetch(/api/public-key) cachedPublicKey res.publicKey return cachedPublicKey }要注意公钥缓存的时间和密钥实际生效时间可能不同步。后端在切换密钥的过渡期最好保留旧公钥的解密逻辑至少一个时间窗口。之前我就在一个项目里因为公钥更新太急老用户等了两天才刷新导致集体报错。从那以后我每次设计这类接口都会强制走一遍“新旧公钥并存至少 24 小时”的流程后端也统一保留上一轮公钥的解密能力。希望这个习惯对你也有用。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑