传统登录系统像一家要求实名登记的酒店——你得交出邮箱、手机号、密码,前台把这些信息抄进登记簿。登记簿越多,失窃风险越大。2024年全球数据泄露平均成本已达488万美元,而凭证泄露是头号元凶。
一位开发者提出了反直觉的设计:服务器不存任何能追溯到真人的信息。用户靠12个单词的助记词(seed phrase)生成密钥对,公钥作为匿名身份,私钥永不出浏览器。登录时只验证3个随机单词的哈希值——服务器连这3个词本身都看不到。
助记词从哪来:2048个单词的数学游戏
这套方案借用了硬件钱包的BIP-39标准。128位随机熵被映射成12个英文单词,词库固定2048个。每个单词携带约11比特信息,12个单词组合出2^128种可能——暴力破解需要的时间比宇宙年龄还长。
代码实现很克制:
import { generateMnemonic } from '@scure/bip39'; import { wordlist } from '@scure/bip39/wordlists/english.js'; const mnemonic = generateMnemonic(wordlist, 128); // "witch collapse practice feed shame open despair creek road again ice least"
@scure/bip39库经过审计,零依赖。对于接触密钥材料的代码,这是底线要求。
生成的助记词看起来随机,实则完全确定。同一串熵永远产出同一组单词,这是"确定性钱包"的核心——用户只需背下12个词,就能在任何设备恢复身份。
密钥派生:同一串词,不同应用不同钥匙
助记词本身不直接当私钥。开发者用HMAC-SHA-256加入"应用专属标签",确保同一组12个词在不同服务生成不同密钥对。这像是同一把万能钥匙的齿形,进了不同锁孔自动变形。
Ed25519曲线负责最后的签名运算。32字节私钥确定性地产生32字节公钥,计算速度比传统ECDSA快数倍。公钥上传服务器成为用户ID,私钥锁在浏览器本地存储——甚至不碰cookie。
密钥派生的完整流程:
async function deriveKeyPair(mnemonic: string) { const seed = mnemonicToSeedSync(mnemonic); const derived = hmac(sha256, new TextEncoder().encode('your-app-name-ed25519'), seed); const privateKey = derived.slice(0, 32); const publicKey = await ed.getPublicKeyAsync(privateKey); return { publicKey: bytesToHex(publicKey), // 服务器可见 privateKey: bytesToHex(privateKey), // 永不出浏览器 }; }
这里有个产品细节:开发者特意用slice(0, 32)截断HMAC输出,而非直接使用SHA-256的完整结果。Ed25519只需要256位私钥,多出的字节是冗余攻击面。
登录验证:服务器只认识哈希,不认识单词
最精巧的设计在登录环节。系统不索要完整助记词,而是随机挑选3个位置,要求用户提供对应单词。这3个词在浏览器端本地哈希后上传,服务器比对预存的哈希列表。
服务器最终存储的数据结构极其干净:
{ "username": "a1b2c3d4", "publicKey": "8f7a9b2c1d3e4f5a...", "mnemonicHashes": [ "e3b0c44298fc1c14...", "d7a8fbb307d78094...", "..." ] }
没有邮箱。没有密码哈希。没有个人身份信息。数据库泄露?攻击者拿到的是一堆无法逆向的哈希值,以及和真人毫无关联的公钥字符串。
3个单词的验证强度足够吗?助记词有12个位置,每次随机抽3个,组合数是C(12,3)=220种。配合哈希迭代,在线暴力破解被有效遏制。而离线攻击需要同时拿到服务器哈希库和用户设备——私钥根本不联网。
用户体验的隐性成本
技术方案再优雅,也得过用户这一关。12个单词的备份是单点故障:丢了就永远失去账户,没有"忘记密码"按钮。开发者需要在安全性和容错率之间找平衡——也许支持助记词分片存储,或社交恢复机制。
另一个细节:2048词库全是英文。非英语用户的记忆负担被放大,本地化词库又会削弱互操作性。BIP-39标准的多语言支持存在,但不同语言词库不兼容,这是加密货币钱包至今没完全解决的痛点。
浏览器端的密钥存储也有讲究。LocalStorage容易被XSS攻击读取,IndexedDB稍好但不够理想。成熟的实现会考虑Web Crypto API的不可提取密钥(non-extractable keys),让JavaScript代码都无法直接访问私钥明文。
这套方案最激进的假设是:用户愿意为自己的安全负责。没有平台兜底,没有客服申诉,12个单词就是全部。这像极了加密货币的自我托管(self-custody)哲学——自由和安全是同一枚硬币的两面。
一位测试用户在GitHub Issues留言:「我试着把助记词存进密码管理器,然后意识到这违背了设计的初衷。现在我把它写在纸上,锁进了抽屉。」
你会选择记住12个单词,还是继续信任服务器的密码重置邮件?
热门跟贴