字符集 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.,;:?! (71 个字符)。
easy 都是简单替换密码。
网站提供加密工具,可用 chosen-plaintext 查询建立替换表。以下脚本保存为 script.py,要求每个字符对应固定宽度 hex token,且加密面板位于返回 HTML 的第二个匹配项;输出缺失、截断或冲突都会中止。站点请求与 Text 6 的完整替换表未复现,正文密文中的固定答案保留为题目记录。
import subprocessimport reimport sysfrom urllib.parse import urlencode
C = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.,;:?! "
def encrypt(text_id, text, cookie=""): cmd = ["curl", "--silent", "--show-error", "--fail", "--max-time", "10"] if cookie: cmd += ["-b", cookie] # form 编码保留空格、+、& 与非 ASCII 文本的边界 cmd += [f"https://www.trytodecrypt.com/decrypt.php?id={text_id}", "-d", urlencode({"text": text, "encrypt": "Encrypt"})] result = subprocess.run(cmd, capture_output=True, text=True, timeout=12, check=True) panels = re.findall(r"panel-body[^>]*>([0-9a-fA-F]+)</div>", result.stdout) if len(panels) < 2: raise ValueError("Encryption response does not contain two expected hex panels") return panels[1].upper()
18 collapsed lines
def build_mapping(text_id, step, cookie=""): if step <= 0: raise ValueError("step must be positive") mapping = {} for i in range(0, len(C), 20): batch = C[i:i + 20] enc = encrypt(text_id, batch, cookie) if len(enc) != len(batch) * step: raise ValueError("Truncated or variable-width oracle output") for j, ch in enumerate(batch): token = enc[j * step:(j + 1) * step] if token in mapping and mapping[token] != ch: raise ValueError("Substitution collision") mapping[token] = ch return mapping
def decode(ct, step, mapping): ct = ct.strip().upper() if step <= 0 or len(ct) % step or not re.fullmatch(r"[0-9A-F]*", ct): raise ValueError("Invalid ciphertext width or hex syntax") return "".join(mapping[ct[i:i + step]] for i in range(0, len(ct), step))
if __name__ == "__main__": text_id, step = int(sys.argv[1]), int(sys.argv[2]) ct = sys.stdin.read() if not sys.stdin.isatty() else input("密文: ") print(decode(ct, step, build_mapping(text_id, step)))Text 1
131017171A48221A1D170F
偏移 2。
C = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.,;:?! "OFF = 2def dec(s): r = [] for i in range(0, len(s), 2): r.append(C[int(s[i:i+2], 16) - OFF]) return "".join(r)
print(dec("131017171A48221A1D170F"))hello world
Text 2
4A3E374A4973483F3D3E4A
偏移 45(charset 索引偏移);给定密文中空格 73 减 45 得到索引 70。
C = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.,;:?! "def dec(s, OFF=45): r = [] for i in range(0, len(s), 2): r.append(C[int(s[i:i+2], 16) - OFF]) return "".join(r)
print(dec("4A3E374A4973483F3D3E4A"))thats right
Text 3
0A0B1339150B1139070A0B13390510
偏移 -13(58 mod 71)。解码时减去 -13,再对 71 取模。
C = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.,;:?! "def dec(s, OFF=-13): r = [] for i in range(0, len(s), 2): r.append(C[(int(s[i:i+2], 16) - OFF) % len(C)]) return "".join(r)
print(dec("0A0B1339150B1139070A0B13390510"))now you know it
Text 4
0C02D8010D0C02D8010606D8101402FCD80F0603D8FC0600DA
C = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.,;:?! "ct = "0C02D8010D0C02D8010606D8101402FCD80F0603D8FC0600DA"plaintext = []for value in bytes.fromhex(ct): index = (30 - value) % 256 if not 0 <= index < len(C): raise ValueError("Invalid charset index") plaintext.append(C[index])print("".join(plaintext))is this too easy for you?
Text 5
90DE633F425148DE51546CDE725466DE3F2A6936DE4263CCDEAB362A3372DE39545DDE633F36DE51366F63DE545136D8
C = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.,;:?! "ct = "90DE633F425148DE51546CDE725466DE3F2A6936DE4263CCDEAB362A3372DE39545DDE633F36DE51366F63DE545136D8"plaintext = []for value in bytes.fromhex(ct): if (value - 12) % 3: raise ValueError("Ciphertext is outside the affine image") index = (value - 12) // 3 if not 0 <= index < len(C): raise ValueError("Invalid charset index") plaintext.append(C[index])print("".join(plaintext))I think now you have it. Ready for the next one?
Text 6
4D586CFC2DB449D47B0CF99C3BC46CFC7B0C
固定 4-hex 替换表。
$ python script.py 6 4密文: 4D586CFC2DB449D47B0CF99C3BC46CFC7B0C*********lucky guy