This repository has been archived on 2026-05-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
obsidian/金山办公作业/Week06/客户端密钥派生.md
T

5.8 KiB
Raw Blame History

tags, create time
tags create time
go
frontend
security
pbkdf2
salt-challenge
client-side-derivation
2026-04-25 14:45

客户端密钥派生:盐值挑战方案详解

概述

本文为「登录安全流程」的进阶篇,讲解金融类高安全场景下采用的客户端密钥派生方案(盐值挑战 / Salt Challenge)。核心思路:前端在发送密码之前,先用后端下发的随机盐值做 PBKDF2 派生,让传输层上永远不出现原始密码。

核心类比:餐厅里的"一次性餐巾纸"

想象你去一家高安全级别的餐厅点餐——点餐的过程就等同于"登录"。

普通餐厅(标准 HTTPS 登录)

你 → 把订单写在纸条上 → 服务员拿走 → 厨房照着做

对应到代码:用户输入密码 → 浏览器直接发 {username, password} → 后端验证。

HTTPS 就像餐厅的监控摄像头 + 玻璃隔断,服务员看不到纸条内容。但如果有坏蛋偷偷装了针孔摄像头(HTTPS 被攻破),他拍到的就是你完整的订单,可以直接拿去厨房冒充你点餐。

高级餐厅(盐值挑战方案)

现在来看完整的盐值挑战流程——这就是高级餐厅的做法。


完整流程拆解

第一步:前端先"要盐"

func (h *Handler) GetChallenge(w http.ResponseWriter, r http.Request) {
    salt := generateRandomSalt(32)                            // 随机生成 32 位盐值
    cache.Set("challenge:"+r.RemoteAddr, salt, 1*time.Minute)  // 存到缓存,1 分钟后自动清除
    json.NewEncoder(w).Encode(LoginChallenge{
        Salt:     salt,
        ExpireAt: time.Now().Add(time.Minute).Unix(),         // 告诉前端什么时候过期
    })
}

生活类比:服务员递给你一张"一次性餐巾纸"

  • 你刚落座(r.RemoteAddr = 你的桌号),服务员就递给你一张随机印了乱码的餐巾纸(salt)
  • 这张纸只有效 1 分钟(ExpireAt),过期作废
  • 餐厅后台会记住:哪张纸给了哪桌,什么时候过期
代码 类比 为什么重要
generateRandomSalt(32) 餐巾纸上随机印的乱码 每次都不一样,两个不同桌的人拿到的纸完全不同
cache.Set(..., 1*time.Minute) 1 分钟后餐厅收回这张纸 过期后就算有人偷了这张纸也没用
"challenge:"+r.RemoteAddr 按桌号记录哪张纸给了谁 防止有人拿别人桌的纸来冒充

第二步:前端拿到盐后"做记号"

// 前端拿到 salt = "a7xK9mZp..." 后:
// 用 PBKDF2 算法,把 用户密码 + 盐值 混合运算
// 得到一个派生密钥,比如 "e4f8b2d1c9a3..."
// 把这个派生密钥发给后端登录

生活类比:你在餐巾纸上用"暗语"写订单

  • 你在餐巾纸的乱码旁边,用只有你和厨房能懂的暗语写下你要点的菜(密码)
  • 你不是直接写"红烧肉",而是写暗语"8 号食材 + 3 号做法"
  • 厨房拿到纸条后,用同样的暗语表反查,知道你点了什么

PBKDF2 在这里的角色: 是一套单向暗语转换规则。

  • 你(前端)用这套规则把"红烧肉"变成"8 号食材 + 3 号做法"
  • 厨房(后端)用同样的规则验证这个暗语对不对
  • 但反过来——有人只看到"8 号食材 + 3 号做法",猜不出原菜名是什么(因为 PBKDF2 是单向的)

第三步:后端"对暗语" + 撕纸

func (h *Handler) Login(w http.ResponseWriter, r *http.Request) {
    // 1. 前端先用盐值做 PBKDF2 → 得到派生密钥
    // 2. 后端用同样的盐 + 数据库里的密码做 PBKDF2 → 对比两者是否一致
    // 3. 用完这张盐,立即作废(一次性使用)
}

生活类比:厨房验证 + 用完即撕

  • 厨房拿到餐巾纸上的暗语
  • 同时从自己的菜单上,用同样的规则算一遍 → 看暗语是否匹配
  • 算完这张纸直接撕掉(盐值一次性使用),哪怕有人偷了这张已经撕掉的纸也没用

全程对比

【普通登录 — 普通餐厅】
┌──────────┐     ┌──────────┐     ┌──────────┐
│  你(用户)  │────▶│ 服务员    │────▶│  厨房    │
│  密码:1234 │     │ (HTTPS)  │     │  验证    │
└──────────┘     └──────────┘     └──────────┘
                        ▲
                   如果有摄像头
                   拍到完整密码 ❌

【盐值挑战 — 高级餐厅】
┌──────────┐     ┌──────────┐     ┌──────────┐
│  你(用户)  │────▶│ 盐值纸条  │────▶│ 厨房     │
│  先拿盐→   │     │ (1分钟   │     │ 对暗语   │
│  再做变换→ │     │ 一次性)  │     │ 用完撕掉 │
│  发密钥    │────▶│          │     │          │
└──────────┘     └──────────┘     └──────────┘
                        ▲
                   如果有摄像头
                   只拍到"暗语" + "废纸" ✅

安全性总结

"即使 HTTPS 被绕过(极少见),攻击者也只能看到客户端派生的密钥,无法还原原始密码。"

翻译: 就算餐厅的摄像头被拆了(HTTPS 被攻破),坏蛋拍到的也只是你写在餐巾纸上的"暗语"和"废纸"。没有那套 PBKDF2 转换规则和原始盐值,他无法反推出你最初输入的密码,更不可能在 1 分钟后再次使用。


关联笔记