🔐 哈希长度扩展:拼接式签名为什么一文不值

很多系统做消息完整性校验时,图省事会这么干:把密钥和消息直接拼起来,再算个哈希当签名。比如 sig = SHA-256(secret + message),服务器收到请求后自己重算一遍比对,一致就认定是可信来源发的。乍看没毛病,secret 藏在里面,攻击者不知道密钥就算不出合法签名。问题出在哈希算法自身的结构上。

SHA-256 这类 Merkle-Damgård 系哈希是分块处理的:消息被切成 64 字节的块逐块喂入,每块的输出作为内部状态继续参与下一块计算。于是出现一个致命特性:知道某个消息的哈希值,就等于拿到了处理完该消息后的内部状态。拿到状态就能假装"我已经算到这儿了",继续往后面追加任意内容并算出新哈希,全程不需要知道 secret。唯一要猜的是 secret 的长度,因为它决定中间那段填充怎么写,而枚举几十种长度对工具来说毫无压力。这就是哈希长度扩展攻击(Hash Length Extension)。

现实中最出名的受害者是 2009 年的 Flickr API:签名方案就是 md5(secret + 参数),攻击者用一个合法请求的签名就能伪造出带额外参数的请求签名。今天仍能看到不少自研 Webhook 签名、游戏防作弊校验在用这种拼串哈希,几乎一测一个准。

为什么 HMAC 不怕?HMAC 不是简单拼接,它把密钥派生出两个掩码,一个垫在消息前面算内层哈希,一个垫在结果前面算外层哈希,密钥被两层结构封住,长度扩展无从下手。防御口径很朴素:完整性校验一律用 HMAC,或者干脆用 SHA-3 这类海绵结构哈希;永远不要自己发明 hash(secret + message) 的变体。正经平台的 Webhook 签名(比如 Stripe)用的就是 HMAC-SHA256,不是没有原因的。

🧪 场景分析:某订单接口用 sign = sha256(secret + "user=alice&price=100") 做签名,你截获了一个合法请求,参数和 sign 都可见。能否在不知道 secret 的情况下,伪造出追加 &discount=90(打一折)的请求并算出合法签名?

m) 就能从内部状态继续追加。用 hashpump 这类工具,枚举猜出 secret 长度后,算出 H(secret补齐填充

💡 核心记忆:hash(secret + message) 会把哈希内部状态整个交出去,消息完整性校验请认准 HMAC。

#Security