资讯详情

前端数据加密?用JS-SHA256实现哈希摘要与HMAC签名实战指南

📅 2026/10/19 9:58:39 | 华诺云谱 👁 阅读
前端数据加密?用JS-SHA256实现哈希摘要与HMAC签名实战指南
先纠正一个说法“前端数据加密”这个提法十次里有八次并不准确。我们真正要做的是用 SHA-256 这种哈希算法把数据变成一个固定长度的“指纹”而不是把数据加密后再解回来。标题里的 JS-SHA256就是目前前端圈里用得比较多、体积小、兼容性也老道的一套 SHA-256 实现适合做密码保护、数据完整性校验、请求签名这类实打实的场景。这篇文章我会从选型理由讲到完整调用方式再给你几个能直接抄走的业务场景和排错清单。后端想补前端安全基础的、前端新手、以及被需求方追着说“你给我加密一下”的同学都可以跟着过一遍。1. 动手前先把概念想清楚1.1 “加密”和“哈希”不是一回事先说个很多人绕进去的坑哈希Hash不是加密。加密是可逆的你拿密钥加密完还能用密钥解回来哈希是单向的给一个输入算出一个 64 位十六进制字符串但给你这个字符串你没法倒推出原始输入。它更像给数据拍了一张证件照照片可以核对身份但没法从照片还原出本人。SHA-256 属于 SHA-2 家族输出固定是 64 个十六进制字符256 bit不管输入是 1 个字节还是 1GB 文件结果长度都一样。它还有两个关键特性雪崩效应和抗碰撞性。雪崩效应指的是输入哪怕只改一个字符输出的哈希值也会面目全非抗碰撞性指的是很难找到两个不同的输入却得到同样的哈希值。这两个特性决定它能干三件事验证数据有没有被改动、隐藏原始数据密码场景、给请求内容做签名。那为什么说前端做不了“真正的加密”因为前端代码和密钥最终都会暴露在浏览器里你藏不住秘密。前端能做的是防误改、防明文落盘、防日志泄漏真正要保密的场景必须靠后端和传输层。所以本文里提到的“加密”实际是哈希摘要和 HMAC 签名这一点先对齐后面不容易跑偏。1.2 为什么选 JS-SHA256 而不是别的库目前前端算 SHA-256可选方案大致有四类原生 Web Crypto API、JS-SHA256、CryptoJS、Node.js 的 crypto仅服务端。我给一个对比方便你按场景挑方案体积兼容性使用难度适合场景Web Crypto API0浏览器内置现代浏览器老 IE 不行中等接口偏底层正式产品、不支持老浏览器JS-SHA256约 6KBgzip 后更小IE9、老内核都稳低直接调用需要兼容老环境、快速接入CryptoJS较大模块多很好低如果项目里已经用它做 AES顺带用Node crypto0仅服务端低Node 脚本、后端校验我选择 JS-SHA256 的核心原因有三个第一零依赖、体积小不会给打包带来明显负担第二API 足够简单不需要理解 SubtleCrypto 那套 Promise 和 ArrayBuffer 转换逻辑第三兼容性确实能打很多政企类项目里的老旧浏览器内核它照样跑得动。但它也有明显的边界它只做哈希和 HMAC不做 AES、RSA 这类加解密。如果你的需求是“把数据加密后存起来”这不是它的活得换方案。还有一点要注意密码存储不能只靠 SHA-256哪怕加了盐也不够正规做法是 PBKDF2、bcrypt 或 argon2 这类慢哈希算法。JS-SHA256 适合的是“给数据算指纹”而不是“给密码做最终存储”。2. 两分钟把库跑起来2.1 安装与引入npm 和 CDN 两种方式在工程化项目里安装就是一条命令。我用 npm 比较多yarn、pnpm 同理npm install js-sha256然后在模块里引入import { sha256, hmac } from js-sha256; console.log(sha256(hello)); // 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824如果你的项目还在用 CommonJS写法是const { sha256, hmac } require(js-sha256);如果是简单页面不想走打包工具直接上 CDN用 script 标签引进来也一样script srchttps://cdn.jsdelivr.net/npm/js-sha2560.9.0/src/sha256.js/script script // 全局变量是 sha256 console.log(sha256(hello)); /script这里有个容易踩的坑老版本 CDN 地址里的文件名可能是sha256.min.js而 0.9.0 以后包的入口结构有调整。最稳妥的办法是打开 CDN 目录看一眼实际的src路径别照搬我几年前的旧链接。另外script 标签引入的全局对象和 npm 包导出的结构不完全一样你要是既用了 CDN 又在业务代码里 import可能会拿到两个不同的实例建议二选一别混用。2.2 核心 API 逐一拆解JS-SHA256 的 API 看着不多但有几个细节能把人绕晕。我按实际使用频率逐个说最基础的一步调用sha256(要计算的字符串); // 返回值是 64 位小写十六进制字符串流式处理重要如果数据量很大或者你想分批读入就不要直接传整个字符串用update方法一段一段喂进去const hash sha256.create(); hash.update(第一段); hash.update(第二段); hash.update(第三段); const result hash.hex(); // 最终哈希值这个create()返回一个哈希对象它内部会维护上下文状态喂多少段都行最后调hex()出结果。注意不要对同一个对象在hex()之后再继续update()结果可能不符合预期。如果你只是临时算一个小字符串直接sha256(xxx)内部也是这么处理的只是帮你封装好了。返回多种格式sha256(hello).hex()十六进制字符串最常用sha256(hello).array()无符号字节数组sha256(hello).digest()ArrayBuffersha256(hello).arrayBuffer()也是 ArrayBuffer不同版本叫法略有差别HMACconst key 服务端下发的签名密钥; const message 要签名的内容; const sign hmac(key, message); // 返回 64 位十六进制字符串HMAC 是“带密钥的哈希”一句话理解同样一段内容不同密钥产出的签名不同。所以它可以用来做接口签名服务端用同一个密钥再算一遍比对结果就知道数据有没有被篡改。这是我在实际项目里用得最多的功能第三节会展开。几个边界情况传空字符串sha256()会返回一个固定值e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855这不是 bug是 SHA-256 算法对空输入的标准输出。传中文sha256(你好)直接就能算内部按 UTF-8 编码处理但结果和后端语言算出来的是否一致取决于后端用什么编码后面排查章节会说。传非字符串类型比如数字sha256(123)它会尝试转成字符串再算。但我不建议依赖这种隐式转换统一在调用前转成字符串。API返回类型典型用途sha256(string)string (hex)简单摘要sha256.create().update(...).hex()string (hex)大文件、分块数据sha256.hmac(key, string)string (hex)接口签名sha256(arrayBuffer)string (hex)File、Blob 类数据摘要sha256.update(...).array()number[]需要字节数组的后续处理3. 三个能直接抄作业的实操场景3.1 场景一表单密码加盐哈希几乎每个带登录功能的项目都会提这个需求密码不要明文传到后端。纯前端做这一步最基础的做法是把密码和一个盐值拼在一起算 SHA-256再把结果发给后端。盐salt的作用是避免彩虹表攻击——如果所有用户用同一个密码得到同一个哈希攻击者直接拿预计算的彩虹表比对就能批量破解加了随机盐之后每个用户的哈希都不一样破解成本成倍增加。function hashPassword(password, salt) { return sha256(salt password); } // 盐可以是由后端生成、随用户名绑定的一串随机字符 // 或者前端生成后把 salt 一并传给后端但要先跟后端约定好格式 const salt a1b2c3d4e5f6g7h8; const hashed hashPassword(userPassword123, salt); // 发送给后端的是 hashed而不是明文密码这里有一个非常关键的限制前端哈希只能防止密码明文出现在网络日志、浏览器历史、抓包工具里它不能替代 HTTPS。因为攻击者如果能把恶意脚本注入页面或者直接控制传输链路前端怎么算都防不住。正确姿势是“前端做一层摘要后端拿到后再用自己的盐和慢哈希算法处理”两层配合。单独指望前端哈希去保密码安全纯属自我安慰。另外一个坑是盐不能是固定的“常量字符串”。有些人图省事把盐写死在代码里比如sha256(mysite password)这等于没加盐。哪怕做不到每个用户动态盐至少也应该是每次请求随机生成、由后端校验时反查的。正规做法是后端生成每用户唯一的随机盐存在用户表里前端最多只是做传输摘要最终存储还是后端说了算。项目里如果权限允许我会直接跟后端同事对齐前端拿到的盐只用于传输摘要不要让前端参与最终密码存储逻辑。3.2 场景二大文件完整性校验上传下载大文件时怎么确认文件没有在传输过程中损坏最朴素的办法是算文件的 SHA-256两端比对。如果你让用户选完文件直接sha256(整段文本)在 500MB 文件面前浏览器可能直接崩溃。正确做法是分块读取、流式更新哈希。async function computeFileHash(file) { // 每块 2MB实测下来内存占用和性能都比较平衡 const CHUNK_SIZE 2 * 1024 * 1024; const hash sha256.create(); let offset 0; while (offset file.size) { const chunk file.slice(offset, offset CHUNK_SIZE); const buffer await chunk.arrayBuffer(); // 注意update 可以直接吃 ArrayBuffer hash.update(buffer); offset CHUNK_SIZE; } return hash.hex(); } const fileInput document.getElementById(file); fileInput.addEventListener(change, async (e) { const file e.target.files[0]; const hashValue await computeFileHash(file); console.log(文件 SHA-256, hashValue); });有几个细节值得展开。第一为什么用arrayBuffer()而不是FileReader.readAsBinaryString()arrayBuffer()是标准方法返回二进制缓冲区sha256.update()可以直接处理性能更好。早期为了兼容旧浏览器有人用readAsBinaryString()但它返回的是二进制字符串大文件转字符串会带来额外的编码开销而且很多旧接口已经被废弃。第二分块大小不是越大越好。2MB 是我的常用值内存占用约等于两倍块大小速度也够快。你要是跑在低端手机上可以调成 512KB。第三别在循环里用字符串拼接存整个文件内容那是内存炸弹。create()update()的方式好处是哈希对象只维护固定大小的上下文不会因为你喂了 1GB 数据就把 1GB 都留在内存里。实际项目里这个哈希还可以配合上传进度一起做在分块循环里通过offset / file.size算出处理进度展示给用户体验会好很多。另外如果服务端需要校验这个哈希记得沟通清楚二进制块被 hash 的顺序必须和上传顺序一致否则两边算出来的值对不上。3.3 场景三请求签名防篡改接口请求在传输过程中被中间人篡改是后端比较头疼的问题。加签名是常见缓解手段前端把请求参数按规则拼接再用 HMAC-SHA256 生成签名后端用同样的密钥和规则重算一遍不一致就拒绝处理。import { hmac } from js-sha256; function generateSign(params, secretKey, timestamp) { // 参数名按 ASCII 排序拼成 keyvaluekey2value2 const sortedKeys Object.keys(params).sort(); const signStr sortedKeys .map((key) ${key}${params[key]}) .join(); // 把时间戳也放进去防止旧请求被无限重放 const raw ${signStr}timestamp${timestamp}; return hmac(secretKey, raw); } const params { userId: 10086, amount: 99.5, orderId: A12345 }; const secretKey your-secret-key; const timestamp Date.now(); const sign generateSign(params, secretKey, timestamp); fetch(/api/order/create, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ ...params, timestamp, sign }) });这里有一个必须面对的现实密钥放在前端是不可能绝对保密的懂行的人打开控制台就能翻到。所以这种签名方案防的是“非恶意的意外篡改”和“参数被随手改掉”防不了真正有攻击意图的人。我一般会在方案设计时就跟后端说清楚它适合内部系统、低风险接口、或者用于防止 CDN 缓存污染这类场景。如果接口涉及资金、隐私数据必须走正式的服务端签名体系前端只负责传递不负责保管密钥。还可以加一个 nonce随机数来进一步增强抗重放能力每次请求带一个唯一随机数服务端保存已使用的 nonce重复请求直接拒绝。时间戳窗口加上 nonce才算是一个能拿得出手的轻量签名方案。4. 高频踩坑与安全红线4.1 踩坑实录速查表我把自己和身边同事踩过的典型问题整理成一张表遇到类似现象可以先过来翻问题现象最常见原因处理方式前端算出的哈希和后端对不上编码不一致后端用了 GBK/UTF-16统一用 UTF-8前端明确传字符串或 ArrayBuffer哈希结果一会全小写一会全大写误以为自己改了大小写实际库返回全小写后端比较前统一toLowerCase()大文件直接算页面卡死一次性读取整个文件到内存用create() 分块update()CDN 引用后sha256 is not defined文件路径或版本不对全局对象名不同打开 CDN 目录核对真实路径确认全局是sha256老浏览器里 File 的arrayBuffer()不存在兼容性缺口用FileReader读ArrayBuffer的兼容写法HMAC 签名后端一直校验失败拼接规则不一致多了空格、大小写、时间戳格式不同和后端拉一个标准示例串两边跑同一份样例比对有一个问题容易被忽略sha256.update(buffer)接收 ArrayBuffer 时有些版本的库不能直接接收Uint8Array的视图对象需要转成真正的ArrayBuffer。如果你拿到的数据是Uint8Array可以用buffer.slice().buffer或者直接new Uint8Array(typedArray).buffer之类的方式处理最好先看一眼你锁定的版本在 README 里的示例。我当年就在这里浪费过一个下午表现是哈希结果不稳定一会对一会不对查到最后才发现是类型传入的问题。4.2 几条不要越过的红线第一前端哈希不等于加密更不等于安全。前面已经反复强调前端一切代码对用户是透明的。如果有人告诉你“前端做了哈希就能防脱库”那是在安慰你。第二不要用 SHA-256 做最终密码存储。SHA-256 太快了GPU 集群每秒能算几十亿次普通人的弱密码用彩虹表很快就能撞出来。密码存储认准慢哈希PBKDF2、bcrypt、scrypt 或 argon2。第三不要在浏览器里长期保存签名密钥。就算业务要求前端做 HMAC 签名密钥也最好是接口动态下发、短期有效而不是写死在代码里长期不变。路径上只要有一个 XSS密钥立刻裸奔。另外还有一条老生常谈任何时候都要开 HTTPS。你算得再花哨如果传输过程是明文中间人直接把内容替换掉前端做的所有摘要和签名都成了笑话。哈希和签名是传输层之上的补充不是替代品。4.3 性能优化和替代方案的组合拳JS-SHA256 的性能在普通字符串和小文件场景下完全够用但遇到超大文件可以再压一压性能瓶颈。我用过的有效手段有三个分块大小调到 4MB 到 8MB减少update()的调用次数。调用本身有开销块越大开销越少但内存占用也越高4MB 是一个比较平衡的点。大文件哈希放到 Web Worker 里跑避免阻塞主线程。UI 能继续响应用户不会觉得页面卡死了。Worker 里同样引入 js-sha256用postMessage传分块数据或者让 Worker 自己读文件都可以。如果项目只跑在现代浏览器完全可以用原生 Web Crypto API 替代不需要引入任何库而且它走的是浏览器底层实现很多时候比纯 JS 快一截。async function sha256Native(buffer) { const digest await crypto.subtle.digest(SHA-256, buffer); return Array.from(new Uint8Array(digest)) .map((b) b.toString(16).padStart(2, 0)) .join(); }这段原生代码不长但没有 js-sha256 那么强的兼容性而且crypto.subtle只在安全上下文HTTPS 或 localhost下可用HTTP 环境会直接拿不到。所以我的实际策略是老项目、兼容性要求高的场景用 JS-SHA256新项目、环境可控的场景优先 Web Crypto API。两者也可以共存做一个能力检测切换兼顾兼容和性能。回到标题的问题怎么快速实现前端数据加密如果只想要一个能在所有浏览器里稳定跑、接口简单、还不拖累打包体积的哈希工具JS-SHA256 是一个很省心的选择。但我文章里反复强调的那些概念边界才是真正值钱的部分——知道自己用的工具能做什么、不能做什么比会调用几个 API 重要得多。最后分享一个我个人的小习惯遇到哈希值对不上的问题先不要怀疑库先用一个固定字符串比如sha256(abc)在前端、后端、命令行各算一遍三方一致再排查业务代码三方不一致99% 是编码或拼接规则的问题。这条排查路径帮我节省了大量时间你可以直接效仿。
📝

华诺云谱内容团队

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

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

你可能需要的服务

订阅华诺云谱资讯周报

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

↑