429 纪念日:只给 SHA-256 的口令红包

01 / 谜面

没有棋盘格、没有助记词,只有一串 SHA-256。8 位纯数字意味着候选空间正好 10^8 个,能做的事只有一件:穷举。 这一期共有两个 hash:09:30 发布的是主红包,10:44 那条讨论里又出现了一个——那是后来做实验时替换进去的。

02 / 微博

发布 原微博 ↗
今天是2026年4月29日,简单纪念一下最近在使用各种AI时常遇见的429报错w 我准备了12份zfb小红包,8位纯数字口令的SHA-256是:3c126f9a0270bd6bbec46d5c1b8774b95407ef8145c1a0a16b5d87a511b9b9b0
讨论 原微博 ↗
早上我发了条微博“我准备了12份zfb小红包,8位纯数字口令的SHA-256是:4ca61510d48223968bd9ddda33733e46a882dd9ff9e58005746c19797f03acee ​​​” 微博发出时间是09:30,下面是12个zfb红包被领取的时间: 09:33:03 09:33:11 09:33:14 09:33:17 09:33:21 09:33:25 09:33:28 09:33:32 09:33:36 09:33:39 09:33:43 09:33:47 领取红包的速度算是比较快的,我怀疑是硅基朋友。但也有群友觉得是有人解出来后转发了,于是讨论了好一会儿,还没有什么结论。 红包口令发明文是秒没的,这个我以前试过。但加了哈希后虽然爆破空间不算大,但写脚本和运算也需要些时间。而且从第一个领红包到结束持续不到1分钟,这也是奇怪的。有群友觉得就是我粉丝比较多而已。 但我觉得还是要做一些实验。具体的实验办法还没想好。
复盘 原微博 ↗
试验了一下,我觉得或许可以排除是“关键词触发”的全自动化解谜题领红包行为。 这是怎么验证的呢,实际上在上一条微博:http://t.cn/AXJ7pRGu 中我又新发了一个红包,替换了口令的hash值。但我说的是“早上我发了条微博”,并且下面也说红包已经被领完了。如果是“关键词触发”的,或许不会被影响,而是继续提取谜题然后领取红包。 不过也不排除AI抓到这条微博之后意识到这个红包可能已经被领光了所以没有尝试解题。但不管怎样,我可能高估了目前硅基粉丝的水平。也就是说,这还是一个需要人类意识到这条信息可能是一个有奖励的谜题,然后主动去寻求AI帮助的过程。 但解出来之后是怎么领红包的,从时间上看大概率还是有机器参与。
03 / 解答与复盘(含剧透)

谜面

微博正文给的是「8 位纯数字口令的 SHA-256」,配图是一张自制的 429 纪念日海报:背景铺满红色的 Too Many Requests,海报上写着同一串 SHA-256。 所以谜面本身就是解题说明——它明确告诉你口令的形态(8 位、纯数字)和验证方式(自己算一遍 SHA-256 比对)。

#!/usr/bin/env python3
"""穷举 8 位纯数字,撞出给定 SHA-256 的原像。

8 位纯数字只有 10^8 个候选。SHA-256 的「不可反推」挡得住原像攻击,
挡不住候选空间这么小的穷举。
实测:6 进程、纯 Python hashlib(无 GPU、无 SIMD 技巧),
跑完 0~99999999 全空间约 34 秒。

用法:
    python brute_sha8.py 3c126f9a0270bd6bbec46d5c1b8774b95407ef8145c1a0a16b5d87a511b9b9b0
    python brute_sha8.py <hash1> <hash2> -p 8
"""
import argparse
import hashlib
import multiprocessing as mp
import time

N = 100_000_000


def work(pack):
    lo, hi, targets = pack
    sha = hashlib.sha256
    found = []
    for i in range(lo, hi):
        d = sha(b"%08d" % i).digest()
        if d in targets:
            found.append((i, targets[d]))
    return found


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("hashes", nargs="+", help="一个或多个 SHA-256(十六进制)")
    ap.add_argument("-p", "--processes", type=int, default=6)
    args = ap.parse_args()

    targets = {}
    for h in args.hashes:
        h = h.strip().lower()
        if len(h) != 64:
            raise SystemExit(f"✗ 不是 SHA-256(长度 {len(h)}):{h}")
        targets[bytes.fromhex(h)] = h

    procs = max(1, args.processes)
    step = N // procs
    packs = [(k * step, N if k == procs - 1 else (k + 1) * step, targets) for k in range(procs)]

    t0 = time.time()
    hits = []
    with mp.Pool(procs) as pool:
        for part in pool.imap_unordered(work, packs):
            hits.extend(part)
    dt = time.time() - t0

    print(f"0~{N - 1} 全空间 · {procs} 进程 · 耗时 {dt:.1f}s")
    hits.sort()
    for i, h in hits:
        print(f"  {h[:16]}… -> {i:08d}")
    for h in targets.values():
        if not any(x[1] == h for x in hits):
            print(f"  {h[:16]}… -> 未命中(候选不在 8 位纯数字里)")


if __name__ == "__main__":
    main()

怎么解:穷举 10^8

8 位纯数字只有 10^8 个候选。SHA-256 是设计成不可反推的,但设计的是一般情况下的原像攻击;候选空间小到这一步,穷举就是最优解。 实测数据:六个 Python 进程、纯 hashlib(无 GPU、无 SIMD 技巧),跑完 0~99999999 全空间耗时 34.3 秒。也就是说,「写脚本和运算需要些时间」这句话里的「些时间」,在今天的普通机器上就是半分钟量级——当年那三分钟的抢空速度,从算力上完全解释了得通。 脚本附在本节下方,直接跑就能复现(会依次报出两个 hash 对应的 8 位数字)。

那两个 hash 是什么关系

同一期里出现了两个不同的 SHA-256,容易看混,这里按时间线拆开:

  • 09:30 发布那条微博,hash 是 3c126f9a…,对应 12 份红包。
  • 09:33:03 ~ 09:33:47 这 12 份被陆续领完。
  • 10:44 他发了第二条微博讨论这件事:引用「早上我发了条微博……」时用的是另一个 hash 4ca61510…,并说红包已经被领完了。
  • 12:46 第三条微博交代了真相:10:44 那条里其实新发了一个红包、并把 hash 值换成了新的,但行文仍伪装成在说早上那条。

那场实验:排除「关键词触发」

10:44 那条是个诱饵。它同时满足两个条件:包含一段可以当谜题破解的 hash,以及一句「红包已经被领完了」。 逻辑是这样的——如果存在一个「看到含口令的微博就自动解题领红包」的机器人,它应该无视「已领完」这句话,照样把新 hash 解出来、去领那个新红包。 结果是没有动静。 他在 12:46 的复盘里给出的结论是:

  • 可以基本排除「关键词触发」式的全自动化解谜领红包行为;
  • 也可能只是 AI 读到「已领完」就判断不值得动手了;
  • 他自认「可能高估了目前硅基粉丝的水平」——这仍然是一个需要人类先意识到「这可能是个有奖励的谜题」,然后主动去求助 AI 的过程。
  • 但解出来之后怎么领的,从时间上看大概率还是有机器参与。

结果

12 份红包,从第一份被领(09:33:03)到最后一份(09:33:47),历时 44 秒;从微博发出算起不到三分钟。 发布时间是 09:30,第一条领取记录是 09:33:03 —— 中间那三分钟,正好和现在一台普通机器穷举 10^8 个 SHA-256 的时间处在同一量级。

04 / 口令校验

主红包 · 12 份(09:30 那条微博里的 hash)

8 位纯数字,空格与全角都无所谓

实验红包(10:44 讨论微博里替换进去的那个 hash)

8 位纯数字