trytodecrypt.com — hard (13-18)

tryptodecrypt.com

Hard 级别的密钥嵌入在密文结构本身。

Text 13

59656A6B6F9F656A67746767

首字节 0x59 (89) 为全局 key。后续每字节减 key 得 charset 位置。

1
2
3
4
5
def decode_text13(ct):
"""首字节为全局 key"""
key = int(ct[:2], 16)
data = [int(ct[i:i+2], 16) for i in range(2, len(ct), 2)]
return "".join(C[b - key] for b in data)
chim cheree

Text 14

6F5657A6606B7D9C7480649D7A6B757D9C70816B6CB4

前 3 字节 [0x6F, 0x56, 0x57] 为旋转 key。后续每字节依次减去对应 key。

1
2
3
4
5
def decode_text14(ct):
"""前 3 字节为旋转 key"""
keys = [int(ct[i:i+2], 16) for i in range(0, 6, 2)]
data = [int(ct[i:i+2], 16) for i in range(6, len(ct), 2)]
return "".join(C[data[i] - keys[i % 3]] for i in range(len(data)))
Take the blue pill!

Text 15

574168755997984F7A7E76AD6954A662538F764F7A5C4F876544

前 6 位 hex 是三个 2 位子密钥 (57, 41, 68)。子密钥交替加密后续字符(密钥 1 加密第 4/7/10...个字符)。字符加密 = 子密钥 + 字符专用偏移量。

第一层:静态替换表

每个字符硬编码一个唯一的偏移量,跟字符集索引无关:

1
2
3
a→0x27  b→0x0b  c→0x41  d→0x45  e→0x0e ...
A→0x1e B→0x13
空格→0x12 句号→0x03

这就是个查表替换,只不过替换结果不是另外一个字符,是一个数字。

第二层:类维吉尼亚加密

拿替换后的数字,加上循环密钥(57→41→68→57→…),得到最终密文:

1
2
3
4
5
A  → 查表得 0x1e  → +密钥 0x57 → 0x75
l → 查表得 0x18 → +密钥 0x41 → 0x59
i → 查表得 0x2f → +密钥 0x68 → 0x97
c → 查表得 0x41 → +密钥 0x57 → 0x98
...

加密工具虽然每请求随机化(Hard 特性),但是同一个字符在同一个位置的加密结果每次都一样。所以:

  1. 加密 "aaaaaa" → 看密文的重复规律,发现每 3 个字节一个周期 → 密钥长度 3
  2. 加密 "aaaaaaaaaaaa" → 同一位置加密 "a" 多次 → 确认 "a" 的密文总是 0x7e(当密钥=0x57时)
  3. 加密 "ba"、"ca"、"da"… → 算出每个字符的偏移量
  4. 密钥值本身:从密文前 6 位直接读出来的(这就是为什么说密钥嵌在密文结构里)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
CHAR_OFF = {
'a': 0x27, 'b': 0x0b, 'c': 0x41, 'd': 0x45, 'e': 0x0e,
'f': 0x10, 'g': 0x11, 'h': 0x05, 'i': 0x2f, 'j': 0x1c,
'k': 0x16, 'l': 0x18, 'm': 0x04, 'n': 0x35, 'o': 0x3e,
'p': 0x37, 'q': 0x1d, 'r': 0x1f, 's': 0x15, 't': 0x21,
'u': 0x1a, 'v': 0x23, 'w': 0x00, 'x': 0x0c, 'y': 0x3b,
'z': 0x30, '.': 0x03, ' ': 0x12, 'A': 0x1e, 'B': 0x13,
}
# 反转: 偏移 -> char
OFF_CHAR = {v: k for k, v in CHAR_OFF.items()}

def decode_text15(ct):
"""前 6 hex 为 3 子密钥, data - key[i%3] 得偏移查表"""
keys = [int(ct[i:i+2], 16) for i in range(0, 6, 2)]
data = [int(ct[i:i+2], 16) for i in range(6, len(ct), 2)]
plain = ""
for i, d in enumerate(data):
off = d - keys[i % 3]
plain += OFF_CHAR.get(off, "?")
return plain
Alice and Bob are here.

Text 16

32632E3149844B82115794BA78AD87C36DA01148707080C65459255C2C6487B02851

每 4 位 hex 一组(2 位偏移 + 2 位编码字符)。编码字符 - 偏移 = 字符集索引。

编码:0=00, 1=01, ..., 9=09, a=0A, ..., z=23, A=24, ..., Z=3D, space=46, fullstop=40

1
2
3
4
5
6
7
8
9
def decode_text16(ct):
"""4-hex 一组 (2偏移 + 2编码)"""
pt = ""
for i in range(0, len(ct), 4):
off = int(ct[i:i+2], 16)
enc = int(ct[i+2:i+4], 16)
cp = enc - off
pt += C[cp] if 0 <= cp < len(C) else "?"
return pt
N3XT CRYPT0 5TUFF

Text 17

5D2EAF346C9271B7489BBA3A52326752248C2255826771378D741E48205A

密文前半为密钥,后半为编码数据。密钥 - 编码数据 = 字符集索引。

1
2
3
4
5
6
7
def decode_text17(ct, reverse=False):
"""前半 key 后半数据, key - 数据"""
half = len(ct) // 2
keys = [int(ct[i:i+2], 16) for i in range(0, half, 2)]
data = [int(ct[i:i+2], 16) for i in range(half, len(ct), 2)]
pt = "".join(C[keys[i] - data[i]] for i in range(len(data)))
return pt[::-1] if reverse else pt
bazinga he said

Text 18

35445FA0D18F47618981AE5D3A98A5138EAE2A303A5D688B6C4461703B902F308F5F125F7725

与 Text 17 相同算法,但明文在加密前被反转了。

1
2
3
def decode_text18(ct):
"""同 Text 17 但结果反转"""
return decode_text17(ct, reverse=True)
5TL1 9aKu p03z U2a5