Security 工具
Web 工程里那些日常的安全杂活——算哈希、估密码熵、看 JWT 声明、写 CSP、以及把抓取结果交出去之前先脱敏——以及每件事对应哪个工具。
11 工具
Web 安全里的“杂活层”
一个普通工程周里的大部分安全工作并不是对抗性的,而是机械的:挑一个摘要算法、生成一
个能满足某人策略要求的凭证、读一个令牌的声明看看认证为什么失败、写一份不会把站点搞
坏的 Content-Security-Policy、在把抓取结果粘进工单之前先清洗它。这些看上去都不像
渗透测试,而真实事故恰恰就从这里开始——失效模式是坏习惯,不是能力缺失。
能预防掉大部分坏习惯的模型,是一个被很多人压成一个概念的三分法。编码——Base64、
百分号编码、Punycode——是可逆的,且不涉及任何秘密;它改变数据的形状,从不改变其机密
性。哈希——SHA-256、SHA-512——是单向的,同样不涉及秘密;它能证明两串字节完全
相同,仅此而已。带密钥的运算——HMAC、JWT 签名、TLS——是唯一能证明某个东西由谁
产生的,而且只在密钥保持秘密期间成立。没有密钥就没有认证:一个 Base64 块、一个裸摘
要、一个解码出来的 JWT 载荷,都只是可读的声明,背后什么都没有。
第二条轴是方向。产出策略的杂活——CSP 指令、响应头、生成的密钥——只有当你在已部 署的响应里验证过那个产物之后才算做对。检查别人递给你的东西的杂活,风险刚好相反: 风险在那个产物内部装了什么。脱敏属于这一类,因为秘密离开一家公司最常见的方式是一份 附在 bug 报告上的 HAR,不是一次漏洞利用。
每种原语实际保证什么
| 原语 | 能证明 | 不提供 | 常见误用 |
|---|---|---|---|
| Base64 / URL 编码 | 让字节安全地穿过文本信道 | 机密性——谁都能反过来解 | 在载荷里“藏”一个 API 密钥 |
SHA-256 / SHA-512 | 完整性:同样的输入得到同样的摘要 | 保密性;对低熵输入的暴力破解成本 | 存储密码哈希 |
SHA-1 | 与历史校验和保持兼容 | 抗碰撞(实际上已被攻破) | 签名;对恶意输入做去重 |
| HMAC(哈希 + 密钥) | 密钥保持秘密期间的真实性 | 机密性——载荷仍然可读 | 把密钥发到浏览器 |
| Argon2id / bcrypt / scrypt | 加盐且刻意放慢的密码存储 | 速度——慢就是它的特性 | 为了“性能”换成快哈希 |
JWT 签名(HS256、RS256) | 签发方真实性——仅在验证之后 | 只解码的话,什么都不能证明 | 相信解码出来的载荷里的声明 |
Content-Security-Policy | 浏览器执行的资源加载白名单 | 修掉注入本身;非浏览器客户端 | script-src 里留着 'unsafe-inline' |
TLS + Strict-Transport-Security | 传输机密性;不会降级到 http | 关于应用逻辑的任何论断 | 把小锁图标读成“这个应用是安全的” |
需要多少熵才够
熵是 长度 × log2(池大小),所以字符池的重要程度不亚于长度。
| 字符池 | 每字符比特 | 8 字符 | 14 字符 | 20 字符 |
|---|---|---|---|---|
a–z(26) | 4.70 | 38 | 66 | 94 |
a–z0–9(36) | 5.17 | 41 | 72 | 103 |
a–zA–Z0–9(62) | 5.95 | 48 | 83 | 119 |
| 四类全用(87) | 6.44 | 52 | 90 | 129 |
低于 40 比特就是坏的;80 比特是重要账户的下限;一个随机 UUID v4 携带 122 比特。
选对工具
要指纹的话,哈希生成器 经由 Web Crypto 一次算出 SHA-1、
SHA-256、SHA-384 和 SHA-512,所以得到的摘要与
echo -n "text" | shasum -a 256 逐字节一致——用来核对一个没人记下算法的校验和是最快
的办法。没有 MD5,因为 Web Crypto 没有实现它。
要自己创建密钥时,问题是这个值最终由谁持有。
密码生成器 适合由人类或密码管理器持有、并且站点强加了长度或字符
集策略的场合;它会按你启用的字符池实时报告熵。UUID v4 生成器 适合
消费方是机器、且你想要一个不需要协商字符集的不透明标识符的场合。两者都基于 CSPRNG——
密码生成器取自 crypto.getRandomValues,UUID 生成器取自 crypto.randomUUID()——所以
都不会退回到 Math.random。
要处理令牌时,JWT 令牌解码器 把
header.payload.signature 切开,把前两段格式化输出,并把 iat、exp、nbf 渲染成
ISO 8601 并给出过期判定——一次粘贴就能定下“这是过期了,还是声明写错了?”。如果你从
日志里只抢救出一个分段,就用 Base64 编码 / 解码;Base64url 用的是 -
和 _,所以先把它们换回 + 和 /。
要处理策略时,把编写和验证分开。
Content Security Policy 生成器 在一套加固基线之上、用 14 个按
指令划分的字段拼出这个头——default-src 'self'、object-src 'none'、
base-uri 'self'、frame-ancestors 'none'——而 CSP 工具集 会逐条
指令展开讲。然后用 HTTP 头信息检查工具 证明已部署的响应确实带上
了它,该工具在服务端发起请求,展示源站原样发出的响应头。CDN 会改写响应头,而一个
<meta> 标签承载不了 frame-ancestors。
分享之前,HAR 文件查看器 用来读你自己的抓取——每条的方法、状态、
MIME 类型、大小和耗时——而 HAR 文件清洗器 负责准备那份会离开
你机器的副本,把 cookie、authorization、x-api-key 之类的头替换成 [REDACTED],
清空正文,并掩掉形似令牌的查询参数。当怀疑对象只是一个 URL 时,
URL 解析和分析工具 会解码查询串,让你看出里面是否坐着一个签名
令牌;而 IDN / Punycode 转换工具 能揭示一个仿冒域名在 xn--
形式下是不是其实用的西里尔字母。
除了头信息检查工具会把你的 URL 发到 api.sitekits.dev,以上全部在本地运行。
/zh/for/security/ 把其中大部分收在一页——通用型的那几个(UUID、
Base64、HAR 查看器)归在别的角色集合里;隐私工具集 讲的是你的
浏览器会泄露什么。
真正会引发事故的坑
解码过的令牌不是验证过的令牌
解码只能证明它是格式正确的 Base64url。验证需要签发方的密钥、一个钉死的算法,以及服务
端对 exp / nbf / iss / aud 的检查。绝不要从令牌自己的 alg 头里取算法——
alg: none 和 RS256 转 HS256 的混淆攻击就是这么成立的。
快哈希不是密码存储
对密码做一次裸 SHA-256 是一场 GPU 基准测试,不是防御;不加盐的话,一张彩虹表就能
破掉每一个用户。用哈希比较文件则是相反的情形——那里快摘要才是正确选择。
'unsafe-inline' 抵消掉 CSP 的大半
它恰好重新放行了 CSP 存在的意义所要阻止的那种被注入的内联脚本。请用 nonce 或 hash。
通配的 connect-src 是同一个陷阱的另一面:即便脚本执行被锁死,数据外传的通道仍然
敞着。
脱敏是模式匹配,不是理解
清洗器比对的是一份固定的头名清单,以及参数名中包含 token、key、secret、
password、passwd、pwd、auth、session、sig 或 signature 的那些。藏在 URL
路径段里的凭证,或者名字叫 t 的凭证,都会存活下来——而在一个已登录会话上得到零条
脱敏记录,是一个信号,不是一次通过。
熵描述的是生成器,不是那个字符串
这个公式只有在每个字符都是随机抽取的前提下才成立。P@ssw0rd!2024 有 13 个字符、四类
字符齐全,却躺在每一份破解字典里。一个字符串是怎么被生成的,才决定强度指示器的分数
有没有意义。