Files
pbf/tasks/pbf/fiddecode.md
2026-03-17 19:48:15 +08:00

2.5 KiB

from Crypto.Cipher import AES import hashlib import struct

Example implementation updated for the full signed int32 legacy fid domain.

This keeps the algorithm reversible for all observed NavSea legacy fid values.

MIN_VAL = -(1 << 31) MAX_VAL = (1 << 31) - 1 N = MAX_VAL - MIN_VAL + 1 # 4294967296

ROUNDS = 10 DOMAIN_BITS = 32 DOMAIN_SIZE = 1 << DOMAIN_BITS

def _prf(key: bytes, round_no: int, value: int) -> int: """ AES-based PRF, returns 16-bit integer. """ cipher = AES.new(key, AES.MODE_ECB) data = struct.pack(">IQ", round_no, value) block = hashlib.sha256(data).digest()[:16] out = cipher.encrypt(block) return int.from_bytes(out[-2:], "big")

def _feistel_permute(x: int, key: bytes, encrypt: bool) -> int: """ 32-bit Feistel permutation. """ if not (0 <= x < DOMAIN_SIZE): raise ValueError("x out of 32-bit domain")

l = (x >> 16) & 0xFFFF
r = x & 0xFFFF

rounds = range(ROUNDS) if encrypt else reversed(range(ROUNDS))

for rnd in rounds:
    if encrypt:
        f = _prf(key, rnd, r)
        l, r = r, l ^ f
    else:
        f = _prf(key, rnd, l)
        l, r = r ^ f, l

return ((l << 16) | r) & 0xFFFFFFFF

def encrypt_number(x: int, key: bytes) -> int: """ Encrypt x in [-2147483648, 2147483647] Return non-negative integer in [0, N-1] """ if len(key) not in (16, 24, 32): raise ValueError("AES key must be 16, 24, or 32 bytes") if not (MIN_VAL <= x <= MAX_VAL): raise ValueError("input out of range")

# Map original range to [0, N-1]
r = x - MIN_VAL

# Cycle-walking into [0, N-1]
while True:
    r = _feistel_permute(r, key, True)
    if r < N:
        return r

def decrypt_number(y: int, key: bytes) -> int: """ Decrypt non-negative integer y in [0, N-1] Return original integer in [-2147483648, 2147483647] """ if len(key) not in (16, 24, 32): raise ValueError("AES key must be 16, 24, or 32 bytes") if not (0 <= y < N): raise ValueError("encrypted value out of range")

r = y

# Reverse cycle-walking
while True:
    r = _feistel_permute(r, key, False)
    if r < N:
        return r + MIN_VAL

if name == "main": key = b"example-secret!!" # 16 bytes

samples = [
    -2147483648,
    -1,
    0,
    1,
    12345678,
    2147483647,
]

for x in samples:
    enc = encrypt_number(x, key)
    dec = decrypt_number(enc, key)
    print(f"x={x}, enc={enc}, dec={dec}, ok={x == dec}")