LongTime

第一节:题目

题目是

from hashlib import sha256
from secret import flag

MASK = (1 << 128) - 1
SEED = 0x6A09E667F3BCC908BB67AE8584CAA73B
ADD = 0x9E3779B97F4A7C15F39CC0605CEDC835
ROUNDS = (1 << 64) + 20261015


def step(x):
    x ^= (x << 13) & MASK
    x ^= x >> 7
    x ^= (x << 17) & MASK
    return (x ^ ADD) & MASK


state = SEED
for _ in range(ROUNDS):
    state = step(state)

key = sha256(state.to_bytes(16, "big")).digest()
ciphertext = bytes(a ^ b for a, b in zip(flag, key))

print(f"ciphertext = {ciphertext.hex()}")

一个 128 位的状态机。从 SEED 出发,一轮执行一次step:

MASK = (1 << 128) - 1
def step(x):
    x ^= (x << 13) & MASK
    x ^= x >> 7
    x ^= (x << 17) & MASK
    return (x ^ ADD) & MASK

三次XOR 异或ADD,循环 ROUNDS = (1 << 64) + 20261015 次
最后的 state 取 sha256 当流密钥,和 flag 异或得到密文:

ciphertext = ba1bf5fe6c4592c3dc10ef3c4ff9c512724782ac32465e442c6c1f014c145b43

显然,这个计算量非常大,很耗时。不过题目都说了真的要循环下去吗?那肯定不是这么走的。

第二节:过程

这道题是用仿射变换+快速幂来跳过循环的
(说明:以下内容在AI的辅助理解下完成,这真的没有学到啊...)

在了解之后,我知道了
仿射变换把 重复 2^64 次”变成求一个矩阵的 N 次幂”
似乎就简单很多了

GF(2) 上的仿射变换可以用多一个常数坐标的办法变成线性变换。把状态扩成 129 维:

v = (x, 1)

映射变成
M = [ L   ADD ]
    [ 0    1  ]

原来 128 位的线性部分放左上角,ADD 放最后一列,最后一行只有右下角是 1,保证常数坐标永远是 1。这样每一步都是 129×129 的矩阵乘法。

然后用二进制快速幂。矩阵元素只有 0 和 1,用位运算实现一次乘法很快。

取结果向量的低128位sha256与密文XOR即可得到答案。

脚本:

import hashlib

MASK = (1 << 128) - 1
SEED = 0x6A09E667F3BCC908BB67AE8584CAA73B
ADD = 0x9E3779B97F4A7C15F39CC0605CEDC835
ROUNDS = (1 << 64) + 20261015
CT = bytes.fromhex("ba1bf5fe6c4592c3dc10ef3c4ff9c512724782ac32465e442c6c1f014c145b43")


def step(x):                          # 一次仿射变换 x -> Lx ^ ADD
    x ^= (x << 13) & MASK
    x ^= x >> 7
    x ^= (x << 17) & MASK
    return (x ^ ADD) & MASK


def apply(A, v):                      # 矩阵乘向量:v 的每个 1 位挑一列异或起来
    r = 0
    while v:
        r ^= A[(v & -v).bit_length() - 1]
        v &= v - 1
    return r


def mul(A, B):                        # A·B 的第 j 列 = A 作用在 B 的第 j 列上
    return [apply(A, b) for b in B]


def matpow(A, e):
    R = [1 << j for j in range(129)]  # 单位阵的列
    while e:
        if e & 1:
            R = mul(R, A)
        A = mul(A, A)
        e >>= 1
    return R


# 补一维:状态 (x, 1),前 128 列是 L 的列,最后一列是 (ADD, 1)
M = [step(1 << j) ^ ADD for j in range(128)] + [ADD | (1 << 128)]

# 自检:小指数下矩阵幂必须和逐轮模拟一致
for E in (0, 1, 2, 3, 17, 1000):
    d = SEED
    for _ in range(E):
        d = step(d)
    assert apply(matpow(M, E), SEED | (1 << 128)) & MASK == d, E
print("self-check OK")

state = apply(matpow(M, ROUNDS), SEED | (1 << 128)) & MASK
key = hashlib.sha256(state.to_bytes(16, "big")).digest()
print(f"state = {state:032x}")
print(bytes(a ^ b for a, b in zip(CT, key)))

结果:

self-check OK
state = e8edcc3b102b68396e398e3d9d4256e5
b'flag{affine_xorshift_skips_fast}'

最后 flag:flag{affine_xorshift_skips_fast}