当前位置:首页 > 每日看点 > 正文内容

大家在做登录功能时,一般怎么做暴力破解防护?

卡卷网8个月前 (01-19)每日看点356

防爆破有一个统一的方法来对抗, 那就是: 工作量证明(Proof of Work).

工作量证明并不复杂: 发起请求前让客户端先算一个十分烧CPU的值-比如说我要找出某个哈希值, 它的前六位必须全是0.

很多人或许听过它在区块链上的应用, 比如比特币挖矿. 其实, 工作量证明的核心思想也能用来对付各种"爆破式攻击".

客户端在发请求前必须先做一段计算, 而服务端只要简单验证就行.

这相当于是说:"老兄, 你想进来可以, 但得先把这一大堆数字给我算完, 再告诉我结果." 而这个计算过程对用户来说, 要消耗CPU, 时间, 电量, 算下来就有了成本. 然后服务端验证却很快, 只需要轻轻计算一次哈希就能知道你算得对不对.

这样一来, 正常用户虽然多花点时间, 几百毫秒, 但还能接受; 可攻击者要是一口气想发几百万次的请求, 攻击者就得老老实实把这一大堆计算都做了-成本瞬间飙升.

工作量证明的优点:

  1. 验证成本低: 服务端只算一次哈希就能验证. 约耗费1ms即可.
  2. 攻击成本高: 攻击者想发海量请求, 计算量会成倍增加. 单次请求花费500ms计算,百万次请求几乎就是难以承担的计算成本了.
  3. 调节灵活: 修改难度, 即可控制一次计算的耗时.

当然, 它也不是万能的. 如果有人有钱又肯烧电, 还是能硬扛. 因此, 实际应用时, 通常还会搭配限速, IP封禁, 验证码, 多因素认证等其他策略.

总之, 工作量证明很适合那些容易被批量尝试的接口. 如果你遇到暴力轰炸或穷举攻击, 不妨试试这个方法. 虽然用户会多花些时间, 但能有效降低爆破的成功率. 毕竟, 算点哈希, 胜过被无限次轰炸.

下面是一份带有注释的单文件示例代码, 展示了如何在Deno环境下使用工作量证明(Proof of Work)来对抗爆破式攻击. 其中使用了node:crypto来计算SHA-256, 这些哈希计算逻辑也可以在Node.js中直接复用.

使用方式:

  1. 将这份代码保存为server.ts.
  2. 在终端运行: deno run --allow-net server.ts
  3. 打开浏览器访问http://localhost:8000/查看页面, 点击按钮执行POW计算并向后端验证.

import { serve } from "https://deno.land/std@0.209.0/http/server.ts"; // node:crypto在Deno中也能用, 同样适用于Node.js, 因此哈希计算逻辑可复用 import { createHash } from "node:crypto"; /** * 这是一个前后端同文件示例: * - 当访问根路径"/"时, 会返回内置的HTML页面(HTML_PAGE变量). * - /challenge 用来提供challenge(随机字符串)和难度difficulty. * - /verify 用来验证客户端提交的(challenge + nonce)是否满足难度要求. */ // —— 前端HTML + JS的内容, 放在一个字符串常量里. —— const HTML_PAGE = `<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Proof of Work (Single File Demo)</title> <style> body { font-family: sans-serif; margin: 20px; } #logArea { margin-top: 20px; padding: 10px; background: #f7f7f7; border: 1px solid #ccc; white-space: pre; font-family: monospace; } #status { margin-top: 10px; color: blue; font-weight: bold; } button { padding: 8px 16px; } </style> </head> <body> <h1>Proof of Work (Single File Demo)</h1> <button id="powButton">Compute POW and Verify</button> <p id="status"></p> <div id="logArea"></div> <script> // 获取页面元素 const logArea = document.getElementById('logArea'); const statusEl = document.getElementById('status'); /** * 前端的统一日志打印函数 * @param {string} level - 日志级别, 如"INFO","WARN","ERROR" * @param {string} message - 具体日志内容 */ function frontLog(level, message) { // 按[INFO] [WARN] [ERROR]的格式打印 const logMessage = \`[\${level}] \${message}\`; console.log(logMessage); // 同时输出到浏览器控制台 logArea.textContent += logMessage + "\\n"; // 显示到网页上的日志区域 } /** * 将 ArrayBuffer 转成十六进制字符串, 以便查看哈希结果 * @param {ArrayBuffer} buffer - 浏览器端计算的哈希结果(二进制) * @returns {string} - 对应的hex字符串 */ function bufferToHex(buffer) { const hexCodes = []; const view = new DataView(buffer); for (let i = 0; i < view.byteLength; i++) { // 将每个字节转成两位16进制表示 const value = view.getUint8(i).toString(16).padStart(2, "0"); hexCodes.push(value); } return hexCodes.join(""); } /** * 计算字符串的SHA-256哈希(浏览器端) * @param {string} str - 待哈希的字符串 * @returns {Promise<string>} - hex格式的哈希值 */ async function sha256(str) { const encoder = new TextEncoder(); const data = encoder.encode(str); // crypto.subtle.digest在浏览器端计算SHA-256, 返回ArrayBuffer const hashBuffer = await crypto.subtle.digest("SHA-256", data); return bufferToHex(hashBuffer); } /** * 前端执行POW: 不断尝试nonce, 找到一个令hash前difficulty位(16进制)为"0"的nonce * @param {string} challenge - 服务端下发的随机字符串 * @param {number} difficulty - 难度, 需要hash前多少位为"0" * @returns {Promise<number>} - 找到的nonce */ async function computeProofOfWork(challenge, difficulty) { const targetPrefix = "0".repeat(difficulty); const startTime = performance.now(); // 记录开始时间 let nonce = 0; while (true) { // 计算hash const hashValue = await sha256(challenge + nonce); // 判断hash的前difficulty个字符是否是"0" if (hashValue.startsWith(targetPrefix)) { // 算到满足条件后, 计算耗时 const endTime = performance.now(); const costMs = (endTime - startTime).toFixed(2); frontLog("INFO", \`PoW computation finished. nonce=\${nonce}, cost=\${costMs} ms\`); return nonce; } nonce++; } } /** * 处理"Compute POW and Verify"按钮的点击事件 * 1. 请求/challenge获取challenge和difficulty * 2. 前端计算PoW * 3. 提交到/verify进行验证 */ document.getElementById("powButton").addEventListener("click", async () => { // 每次点击按钮, 先清空前端日志显示 logArea.textContent = ""; statusEl.textContent = "请求 challenge 中..."; frontLog("INFO", "REQUEST /challenge -> 获取challenge和difficulty"); // 记录获取challenge的开始时间 const challengeStart = performance.now(); const challengeResp = await fetch("/challenge"); const { challenge, difficulty } = await challengeResp.json(); // 获取challenge结束 const challengeEnd = performance.now(); frontLog("INFO", \`RESPONSE /challenge, 耗时=\${(challengeEnd - challengeStart).toFixed(2)} ms -> challenge="\${challenge}", difficulty=\${difficulty}\`); // 开始计算POW statusEl.textContent = \`开始计算 POW, 难度 = \${difficulty}\`; frontLog("INFO", \`开始PoW计算, difficulty=\${difficulty}, challenge="\${challenge}"\`); const nonce = await computeProofOfWork(challenge, difficulty); // 计算完成后, 请求后端验证 statusEl.textContent = \`POW计算完成 (nonce = \${nonce}), 开始验证...\`; frontLog("INFO", \`REQUEST /verify -> 提交 {challenge="\${challenge}", nonce=\${nonce}, difficulty=\${difficulty}}\`); const verifyStart = performance.now(); const verifyResp = await fetch("/verify", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ challenge, nonce, difficulty }) }); const result = await verifyResp.json(); const verifyEnd = performance.now(); frontLog("INFO", \`RESPONSE /verify, 耗时=\${(verifyEnd - verifyStart).toFixed(2)} ms -> success=\${result.success}\`); // 更新页面状态 statusEl.textContent = result.success ? "验证成功!" : ("验证失败: " + (result.error || "未知错误")); }); </script> </body> </html> `; /** * 用于验证客户端提交的(challenge + nonce)是否满足难度 * @param {string} challenge - 随机字符串 * @param {number} nonce - 前端计算出的nonce * @param {number} difficulty - 难度系数, 需要hash前多少位是"0" * @returns {boolean} - 是否满足难度要求 */ function verifyPow(challenge: string, nonce: number, difficulty: number): boolean { // 这里用node:crypto的createHash在Deno里也能用, 同样可在Node.js中复用 const hash = createHash("sha256").update(challenge + nonce).digest("hex"); const success = hash.startsWith("0".repeat(difficulty)); // 后端日志按照统一格式输出 console.log(`[INFO] verifyPow -> challenge="${challenge}", nonce=${nonce}, difficulty=${difficulty}`); console.log(`[INFO] verifyPow -> computedHash="${hash}", success=${success}`); return success; } /** * 运行Deno服务器, 处理前端访问: * - GET / -> 返回HTML_PAGE(前端页面) * - GET /challenge -> 返回challenge和difficulty * - POST /verify -> 接收前端提交的(challenge, nonce, difficulty), 验证是否满足哈希难度 */ serve(async (req) => { const { method } = req; const url = new URL(req.url); // 后端请求日志 console.log(`[INFO] REQUEST -> method=${method}, path=${url.pathname}`); // 当访问根路径时, 返回内置HTML页面 if (url.pathname === "/") { console.log("[INFO] Serving root HTML page"); return new Response(HTML_PAGE, { headers: { "Content-Type": "text/html; charset=utf-8" }, }); } // 前端获取challenge和difficulty if (url.pathname === "/challenge") { console.log("[INFO] 生成新的 challenge 与 difficulty..."); // 生成随机字符串 const challenge = crypto.randomUUID(); // 可以自行调整难度, 这里默认为4 const difficulty = 4; console.log(`[INFO] 下发给前端 challenge="${challenge}", difficulty=${difficulty}`); return new Response( JSON.stringify({ challenge, difficulty }), { headers: { "Content-Type": "application/json" } } ); } // 前端提交验证 if (url.pathname === "/verify" && method === "POST") { try { // 记录后端验证开始时间, 观察耗时 const verifyStartTime = performance.now(); // 解析前端提交的数据 const { challenge, nonce, difficulty } = await req.json(); console.log(`[INFO] 接收到验证请求 -> challenge="${challenge}", nonce=${nonce}, difficulty=${difficulty}`); // 调用verifyPow函数验证 const success = verifyPow(challenge, nonce, difficulty); console.log(`[INFO] 验证结果 -> ${success ? "SUCCESS" : "FAILURE"}`); // 统计后端验证耗时 const verifyEndTime = performance.now(); const costMs = (verifyEndTime - verifyStartTime).toFixed(2); console.log(`[INFO] 本次验证耗时 -> ${costMs} ms`); // 返回结果给前端 return new Response( JSON.stringify({ success }), { headers: { "Content-Type": "application/json" }, status: success ? 200 : 400, }, ); } catch (err) { // 异常处理 console.log(`[ERROR] Exception during verification -> ${err.message}`); return new Response( JSON.stringify({ success: false, error: err.message }), { headers: { "Content-Type": "application/json" }, status: 400, }, ); } } // 其他未匹配的路径返回404 console.log(`[WARN] 404 Not Found -> ${url.pathname}`); return new Response("Not Found", { status: 404 }); });


代码说明

  1. 前端部分 (HTML_PAGE):
  • 使用一个<button>触发流程:
  1. 发起/challenge请求拿到challengedifficulty.
  2. 调用computeProofOfWork执行工作量证明, 不断递增nonce并计算challenge + nonce的哈希, 直到满足hashValue.startsWith("0".repeat(difficulty)).
  3. challenge, nonce, difficulty提交到/verify.
    • 日志函数frontLog(level, message)同时在浏览器控制台和页面上输出日志, 方便调试.
  1. 后端部分:
  • serve方法启动Deno的HTTP服务器.
  • verifyPow函数用node:cryptocreateHash("sha256")来计算哈希, 并校验是否满足难度. 因为是node:crypto, 所以同样可以在Node.js环境下直接用这段逻辑, 只要代码结构兼容ES module/ CommonJS 即可.
  • 路由/challenge随机生成challenge, 设置difficulty, 并返回给前端.
  • 路由/verify接收前端的challenge, nonce, difficulty, 调用verifyPow验证. 验证通过就返回success=true.
  • 访问其他路径则返回404.
  1. 运行环境:
  • Deno 2.1+ (支持node:crypto以及std/http/server.ts).
  • Node.js中若想复用哈希计算逻辑, 只需同样import { createHash } from "node:crypto", 并在Node后端中搭配http或其他框架(Express/Koa)来写路由即可.

这样, 一份代码就能让你在Deno里直接试验工作量证明的原理和效果, 而核心的哈希运算逻辑也能在Node.js里无缝使用.

扫描二维码推送至手机访问。

版权声明:本文由卡卷网发布,如需转载请注明出处。

本文链接:https://www.kajuan.net/ttnews/2025/01/10086.html

分享给朋友:

相关文章

推荐几个问卷调查平台?

推荐几个问卷调查平台?

我给大家免费推荐一些市面上不多,且稳定的免费的问卷平台,这个在网上都是能够搜索到的,有的还是世界500钱企业,这里推荐的基本上都是上市的问卷公司了。上面都是可以免费去注册的,对外公开开放的,做完了直接奖励美刀的,不需要兑换卡什么的。Cint...

你每天用来涨知识的手机应用程序有哪些?

你每天用来涨知识的手机应用程序有哪些?

经过深度使用和测评,从100个APP中选出的这35个超实用的app,每一个都是最硬核最有料的涨知识神器!每天打开看看,能让你提神醒脑,眼界大开,成为朋友聚会上的话题王者!双击屏幕取走哦~先放上全部APP目录,有新闻资讯类、英语学习类、读书类...

提升自己最快的方式是什么?

提升自己最快的方式是什么?

1.稻盛和夫说过:“改变自己最快的方法就是做自己害怕的事,不敢做的事,认为自己做不到,觉得不可能的事。如果在自己的舒适区待久了,就会丧失斗志,如果想快速的改变,可以坚持去做一些对自己有益的事。2.早睡早起,坚持运动保持旺盛的精力,人生拼到最...

夸克浏览器受欢迎的原因是什么?

夸克浏览器受欢迎的原因是什么?

这是可以说的嘛~哈哈,它比较吸引我的几点是:安全无广、页面简洁、功能丰富、反应速度快......首页页面支持自定义,喜欢什么样子都可以自己调整,没有花里胡哨的各种资讯推送,热搜日报整理归纳好,想看再点开查看,看着舒适度直接拉满!实用日常工具...

有哪些是你用上了mac才知道的事?

用上了高端的Mac(已退货)才知道:原来文件夹里面的文件,你看到是8个,其实可能有12个。其中3个图标重叠在一起了,另外一个被拖动到屏幕外面了。用上了高端的Mac(已退货)才知道:原来鼠标灵敏度有问题是因为系统内置了鼠标加速度,只能用控制台...

如何判断 Java 工程师的基础知识是否扎实?

我来给你出几道大题,能答对70%,你就算基础扎实了。第一部分 Java基础(27)1. 程序本质:代码是如何被执行的?CPU、操作系统、虚拟机各司何职?2. 基础语法:从CPU角度看变量、数组、类型、运算、跳转、函数等语法3. 引用类型:同...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。