CTF解题详析:DecrypttheMessage(PoemCode诗歌密码)

admin 2026-03-10 02:28:02 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详析一道CTF古典密码题,核心在于识别二战时期的诗歌密码。该密码机制基于列式换位,利用诗歌单词生成密钥。文章通过解析密文指示组定位密钥单词索引,枚举候选组合还原密钥短语,重建换位矩阵并还原明文,最终获取Flag。文中详细阐述了从密文结构分析、密钥推导到脚本实现的完整过程,具有很强的实战参考价值。 综合评分: 95 文章分类: CTF,安全工具,实战经验


cover_image

CTF 解题详析:Decrypt the Message(Poem Code 诗歌密码)

原创

破镜安全 破镜安全

破镜安全

2026年3月9日 08:00 四川

CTF 解题详析:Decrypt the Message(Poem Code 诗歌密码)

来源:Sharif University Quals CTF 2014


一、题目内容

题目附件给出了两样东西:一首英文诗,以及一段被加密的文字。

诗的内容如下:

The life that I have
Is all that I have
And the life that I have
Is yours.

The love that I have
Of the life that I have
Is yours and yours and yours.

A sleep I shall have
A rest I shall have
Yet death will be but a pause.

For the peace of my years
In the long green grass
Will be yours and yours and yours.

加密消息:

emzcf sebt yuwi ytrr ortl rbon aluo konf ihye cyog rowh prhj feom ihos perp twnb tpak heoc yaui usoa irtd tnlu ntke onds goym hmpq

观察加密结果,可以发现它由若干个由空格隔开的小写字母块组成。第一个块 emzcf 有 5 个字母,其余每个块都有 4 个字母,共 25 个块(不含第一块)。这种格式非常规则,没有任何数字或符号——提示我们这是一种以字母操作为基础的古典密码。


二、识别加密方式

面对一道未知加密方式的题目,最有效的方法是从题目名称和加密结构中寻找线索。

题目名为”Decrypt the Message”,附件中有一首诗。将”poem”和”cipher”组合搜索,可以找到一种历史密码:Poem Code(诗歌密码)

Poem Code 是二战期间英国特别行动处(SOE,Special Operations Executive)用于与潜伏特工通信的一种加密方式。其设计思路是:特工不需要随身携带密码本,只要能背诵一首诗,就能完成加密和解密。即便被捕,身上也不会有任何可疑的密码材料。

这种密码的核心机制是列式换位(Columnar Transposition),以诗歌中选取的若干单词拼接而成的”密码短语”作为换位密钥。


三、Poem Code 的加密原理

理解加密原理,是实现解密的前提。下面从头完整讲解这套机制。

3.1 第一步:将诗歌分解为单词列表

将诗歌中的所有单词按出现顺序排成一个列表,去掉标点,全部转为小写。对本题的诗歌,处理后得到:

索引  0: the        索引  1: life       索引  2: that
索引  3: i          索引  4: have       索引  5: is
索引  6: all        索引  7: that       索引  8: i
索引  9: have       索引 10: and        索引 11: the
索引 12: life       索引 13: that       索引 14: i
索引 15: have       索引 16: is         索引 17: yours
索引 18: the        索引 19: love       索引 20: that
索引 21: i          索引 22: have       索引 23: of
索引 24: the        索引 25: life       索引 26: that
索引 27: i          索引 28: have       索引 29: is
索引 30: yours      索引 31: and        索引 32: yours
索引 33: and        索引 34: yours      索引 35: a
索引 36: sleep      索引 37: i          索引 38: shall
索引 39: have       索引 40: a          索引 41: rest
索引 42: i          索引 43: shall      索引 44: have
索引 45: yet        索引 46: death      索引 47: will
索引 48: be         索引 49: but        索引 50: a
索引 51: pause      索引 52: for        索引 53: the
索引 54: peace      索引 55: of         索引 56: my
索引 57: years      索引 58: in         索引 59: the
索引 60: long       索引 61: green      索引 62: grass
索引 63: will       索引 64: be         索引 65: yours
索引 66: and        索引 67: yours      索引 68: and
索引 69: yours

共 70 个单词。

3.2 第二步:指示组(Indicator Group)

加密时,发报人从上述单词列表中选取若干个单词(通常 5 个)作为密钥。选择方法是:直接记下每个单词的索引号。为了将这些索引号编码成可传输的字母形式,将索引对 26 取模后映射到字母表(0 对应 ‘a’,1 对应 ‘b’,……,25 对应 ‘z’)。

这些编码后的字母组成指示组(Indicator Group),它是密文的第一个块,告诉接收方去哪里找密钥单词。

本题密文的第一个块是 emzcf,逐字母解码如下:

| 字母 | 字母在字母表中的索引 | 对应的起始诗歌单词索引 | | — | — | — | | e | 4 | 第 4 个单词: have | | m | 12 | 第 12 个单词: life | | z | 25 | 第 25 个单词: life | | c | 2 | 第 2 个单词: that | | f | 5 | 第 5 个单词: is |

3.3 第三步:还原密钥单词

由于指示组中每个字母只记录了索引对 26 取模后的值,实际的索引可能是 起始值起始值 + 26起始值 + 52 等。解密时,需要把所有候选单词都列出来,逐一尝试组合,直到找到使密码短语总长度恰好等于密文块数量的那个组合。

对本题,各个字母对应的候选单词如下:

| 起始索引 | 候选索引序列 | 对应单词 | | — | — | — | | 4 | 4, 30, 56 | haveyoursmy | | 12 | 12, 38 | lifeshall | | 25 | 25, 51 | lifepause | | 2 | 2, 28, 54 | thathavepeace | | 5 | 5, 31, 57 | isandyears |

密文中非指示组的块共有 25 个,所以密码短语的总字母数必须等于 25。

逐一枚举所有候选组合,计算各组合拼接后的长度,找到唯一满足条件的组合:

yours (5) + shall (5) + pause (5) + peace (5) + years (5) = 25

密码短语(Passphrase)确定为:yoursshallpausepeaceyears

3.4 第四步:生成数字密钥

将密码短语中每个位置的字母,按字母表从小到大的顺序,依次标上编号(从 0 开始),同一字母出现多次时,按从左到右的顺序依次编号。这个编号序列就是数字密钥,它决定了换位的方式。

密码短语逐位分析:

位置:  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
字母:  y  o  u  r  s  s  h  a  l  l  p  a  u  s  e  p  e  a  c  e  y  e  a  r  s
编号: 23 12 21 15 17 18  9  0 10 11 13  1 22 19  5 14  6  2  4  7 24  8  3 16 20

编号规则举例:

  • 字母 a 在密码短语中出现在位置 7、11、17、22,按从左到右编号为 0、1、2、3。
  • 字母 c 出现在位置 18,编号为 4。
  • 字母 e 出现在位置 14、16、19、21,编号为 5、6、7、8。
  • 以此类推,直到字母 y 出现在位置 0、20,编号为 23、24。

3.5 第五步:加密——列式换位

加密时,将明文(不足时用字母表 abcdef... 补齐至密码短语长度的整数倍)按行写入网格,网格的列数等于密码短语的长度(25 列)。

对本题,明文为:

ifyouthinkcryptographyistheanswertoyourproblemthenyoudonotknowwhatyourproblemis

补齐至 100 字符(需再补 22 个字符 abcdefghijklmnopqrstu,实际明文已够,这里明文刚好 78 字符,补至 100):

ifyouthinkcryptographyist &nbsp; <- 第 0 行(列 0~24)
heanswertoyourproblemthen &nbsp; <- 第 1 行
youdonotknowwhatyourprobl &nbsp; <- 第 2 行
emisabcdefghijklmnopqrstu &nbsp; <- 第 3 行(末尾为填充)

加密时,不是按行读取,而是按数字密钥指定的列顺序读取每一列。密码短语位置 j 对应的块,是网格中第 numeric_key[j] 列的所有字符(从上到下拼接)。

以位置 7(字母 a,编号为 0)为例:它对应第 0 列,从上到下读取:ihye → 块为 ihye

这就是密文中的 block[7] = ihye,即密文序列中的第 8 个块(0-indexed 为第 7 个)。

3.6 第六步:密文结构

完整密文由指示组加上 25 个块组成:

emzcf sebt yuwi ytrr ortl rbon aluo konf ihye cyog rowh prhj feom ihos perp twnb tpak heoc yaui usoa irtd tnlu ntke onds goym hmpq

四、解密过程

解密是加密的逆操作。在已知诗歌的情况下,步骤如下:

4.1 解析指示组,确定密码短语

按照 3.2 和 3.3 节的方法,从指示组 emzcf 还原出密码短语 yoursshallpausepeaceyears,并建立数字密钥:

位置 &nbsp;0 (y): 编号 23 &nbsp; &nbsp;位置 &nbsp;1 (o): 编号 12 &nbsp; &nbsp;位置 &nbsp;2 (u): 编号 21
位置 &nbsp;3 (r): 编号 15 &nbsp; &nbsp;位置 &nbsp;4 (s): 编号 17 &nbsp; &nbsp;位置 &nbsp;5 (s): 编号 18
位置 &nbsp;6 (h): 编号 &nbsp;9 &nbsp; &nbsp;位置 &nbsp;7 (a): 编号 &nbsp;0 &nbsp; &nbsp;位置 &nbsp;8 (l): 编号 10
位置 &nbsp;9 (l): 编号 11 &nbsp; &nbsp;位置 10 (p): 编号 13 &nbsp; &nbsp;位置 11 (a): 编号 &nbsp;1
位置 12 (u): 编号 22 &nbsp; &nbsp;位置 13 (s): 编号 19 &nbsp; &nbsp;位置 14 (e): 编号 &nbsp;5
位置 15 (p): 编号 14 &nbsp; &nbsp;位置 16 (e): 编号 &nbsp;6 &nbsp; &nbsp;位置 17 (a): 编号 &nbsp;2
位置 18 (c): 编号 &nbsp;4 &nbsp; &nbsp;位置 19 (e): 编号 &nbsp;7 &nbsp; &nbsp;位置 20 (y): 编号 24
位置 21 (e): 编号 &nbsp;8 &nbsp; &nbsp;位置 22 (a): 编号 &nbsp;3 &nbsp; &nbsp;位置 23 (r): 编号 16
位置 24 (s): 编号 20

4.2 将密文块按编号排序

密文块 block[位置] 对应的换位网格中的列编号就是 numeric_key[位置]。解密时,需要将这 25 个块还原到它们在网格中的原始列位置,即建立一个数组 pcode,使得 pcode[编号] = block[位置]

编号 &nbsp;0: block[7] &nbsp;= ihye &nbsp; &nbsp;<- 网格第 &nbsp;0 列
编号 &nbsp;1: block[11] = feom &nbsp; &nbsp;<- 网格第 &nbsp;1 列
编号 &nbsp;2: block[17] = yaui &nbsp; &nbsp;<- 网格第 &nbsp;2 列
编号 &nbsp;3: block[22] = onds &nbsp; &nbsp;<- 网格第 &nbsp;3 列
编号 &nbsp;4: block[18] = usoa &nbsp; &nbsp;<- 网格第 &nbsp;4 列
编号 &nbsp;5: block[14] = twnb &nbsp; &nbsp;<- 网格第 &nbsp;5 列
编号 &nbsp;6: block[16] = heoc &nbsp; &nbsp;<- 网格第 &nbsp;6 列
编号 &nbsp;7: block[19] = irtd &nbsp; &nbsp;<- 网格第 &nbsp;7 列
编号 &nbsp;8: block[21] = ntke &nbsp; &nbsp;<- 网格第 &nbsp;8 列
编号 &nbsp;9: block[6] &nbsp;= konf &nbsp; &nbsp;<- 网格第 &nbsp;9 列
编号 10: block[8] &nbsp;= cyog &nbsp; &nbsp;<- 网格第 10 列
编号 11: block[9] &nbsp;= rowh &nbsp; &nbsp;<- 网格第 11 列
编号 12: block[1] &nbsp;= yuwi &nbsp; &nbsp;<- 网格第 12 列
编号 13: block[10] = prhj &nbsp; &nbsp;<- 网格第 13 列
编号 14: block[15] = tpak &nbsp; &nbsp;<- 网格第 14 列
编号 15: block[3] &nbsp;= ortl &nbsp; &nbsp;<- 网格第 15 列
编号 16: block[23] = goym &nbsp; &nbsp;<- 网格第 16 列
编号 17: block[4] &nbsp;= rbon &nbsp; &nbsp;<- 网格第 17 列
编号 18: block[5] &nbsp;= aluo &nbsp; &nbsp;<- 网格第 18 列
编号 19: block[13] = perp &nbsp; &nbsp;<- 网格第 19 列
编号 20: block[24] = hmpq &nbsp; &nbsp;<- 网格第 20 列
编号 21: block[2] &nbsp;= ytrr &nbsp; &nbsp;<- 网格第 21 列
编号 22: block[12] = ihos &nbsp; &nbsp;<- 网格第 22 列
编号 23: block[0] &nbsp;= sebt &nbsp; &nbsp;<- 网格第 23 列
编号 24: block[20] = tnlu &nbsp; &nbsp;<- 网格第 24 列

4.3 还原明文网格,逐行读取

pcode 数组从编号 0 到 24 恰好给出了网格的第 0 列到第 24 列(从上到下各 4 个字符)。

还原时,按行读取:先读每列的第 0 个字符(构成第 0 行),再读每列的第 1 个字符(构成第 1 行),以此类推:

第 0 行(各列第 0 字符):

pcode[0][0] &nbsp;pcode[1][0] &nbsp;pcode[2][0] &nbsp;... pcode[24][0]
&nbsp; &nbsp; i &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;f &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;y &nbsp; &nbsp; &nbsp; &nbsp; ... &nbsp; &nbsp; t

完整四行:

行 0: i &nbsp;f &nbsp;y &nbsp;o &nbsp;u &nbsp;t &nbsp;h &nbsp;i &nbsp;n &nbsp;k &nbsp;c &nbsp;r &nbsp;y &nbsp;p &nbsp;t &nbsp;o &nbsp;g &nbsp;r &nbsp;a &nbsp;p &nbsp;h &nbsp;y &nbsp;i &nbsp;s &nbsp;t
行 1: h &nbsp;e &nbsp;a &nbsp;n &nbsp;s &nbsp;w &nbsp;e &nbsp;r &nbsp;t &nbsp;o &nbsp;y &nbsp;o &nbsp;u &nbsp;r &nbsp;p &nbsp;r &nbsp;o &nbsp;b &nbsp;l &nbsp;e &nbsp;m &nbsp;t &nbsp;h &nbsp;e &nbsp;n
行 2: y &nbsp;o &nbsp;u &nbsp;d &nbsp;o &nbsp;n &nbsp;o &nbsp;t &nbsp;k &nbsp;n &nbsp;o &nbsp;w &nbsp;w &nbsp;h &nbsp;a &nbsp;t &nbsp;y &nbsp;o &nbsp;u &nbsp;r &nbsp;p &nbsp;r &nbsp;o &nbsp;b &nbsp;l
行 3: e &nbsp;m &nbsp;i &nbsp;s &nbsp;a &nbsp;b &nbsp;c &nbsp;d &nbsp;e &nbsp;f &nbsp;g &nbsp;h &nbsp;i &nbsp;j &nbsp;k &nbsp;l &nbsp;m &nbsp;n &nbsp;o &nbsp;p &nbsp;q &nbsp;r &nbsp;s &nbsp;t &nbsp;u

连续读取所有行得到:

ifyouthinkcryptographyistheanswertoyourproblemthenyoudonotknowwhatyourproblemisabcdefghijklmnopqrstu

去掉末尾的填充字符 abcdefghijklmnopqrstu,有效明文为:

ifyouthinkcryptographyistheanswertoyourproblemthenyoudonotknowwhatyourproblemis

五、Flag

ifyouthinkcryptographyistheanswertoyourproblemthenyoudonotknowwhatyourproblemis

完整含填充的解密结果(也是 CTF 提交的 flag 字符串):

ifyouthinkcryptographyistheanswertoyourproblemthenyoudonotknowwhatyourproblemisabcdefghijklmnopqrstu

六、解密脚本

以下 Python 3 脚本完整复现了上述解密过程,并通过重新加密验证结果正确:

import&nbsp;itertools

ABC =&nbsp;'abcdefghijklmnopqrstuvwxyz'

def&nbsp;poem_to_wordlist(poem_text):
&nbsp; &nbsp; words = []
&nbsp; &nbsp;&nbsp;for&nbsp;line&nbsp;in&nbsp;poem_text.splitlines():
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;w&nbsp;in&nbsp;line.split():
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cleaned =&nbsp;''.join(c&nbsp;for&nbsp;c&nbsp;in&nbsp;w.lower()&nbsp;if&nbsp;c.isalpha())
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;cleaned:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; words.append(cleaned)
&nbsp; &nbsp;&nbsp;return&nbsp;words

def&nbsp;build_numeric_key(passphrase):
&nbsp; &nbsp; key = [None] * len(passphrase)
&nbsp; &nbsp; rank =&nbsp;0
&nbsp; &nbsp;&nbsp;for&nbsp;letter&nbsp;in&nbsp;ABC:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;pos, ch&nbsp;in&nbsp;enumerate(passphrase):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;ch == letter:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; key[pos] = rank
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rank +=&nbsp;1
&nbsp; &nbsp;&nbsp;return&nbsp;key

def&nbsp;decrypt(ciphertext, poem_text):
&nbsp; &nbsp; poem_words = poem_to_wordlist(poem_text)
&nbsp; &nbsp; tokens = ciphertext.split()
&nbsp; &nbsp; indicator = tokens[0]
&nbsp; &nbsp; ct_blocks = tokens[1:]
&nbsp; &nbsp; n_blocks = len(ct_blocks)

&nbsp; &nbsp; start_indices = [ABC.index(ch)&nbsp;for&nbsp;ch&nbsp;in&nbsp;indicator]

&nbsp; &nbsp; candidates = []
&nbsp; &nbsp;&nbsp;for&nbsp;start&nbsp;in&nbsp;start_indices:
&nbsp; &nbsp; &nbsp; &nbsp; pool = []
&nbsp; &nbsp; &nbsp; &nbsp; idx = start
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;while&nbsp;idx < len(poem_words):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pool.append(poem_words[idx])
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; idx +=&nbsp;26
&nbsp; &nbsp; &nbsp; &nbsp; candidates.append(pool)

&nbsp; &nbsp; results = []
&nbsp; &nbsp;&nbsp;for&nbsp;combo&nbsp;in&nbsp;itertools.product(*candidates):
&nbsp; &nbsp; &nbsp; &nbsp; passphrase =&nbsp;''.join(combo)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;len(passphrase) != n_blocks:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue

&nbsp; &nbsp; &nbsp; &nbsp; plen = len(passphrase)
&nbsp; &nbsp; &nbsp; &nbsp; pcode = [None] * plen
&nbsp; &nbsp; &nbsp; &nbsp; rank =&nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;letter&nbsp;in&nbsp;ABC:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;pos, ch&nbsp;in&nbsp;enumerate(passphrase):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;ch == letter:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pcode[rank] = ct_blocks[pos]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rank +=&nbsp;1

&nbsp; &nbsp; &nbsp; &nbsp; block_len = len(pcode[0])
&nbsp; &nbsp; &nbsp; &nbsp; plaintext =&nbsp;''
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;char_pos&nbsp;in&nbsp;range(block_len):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;block&nbsp;in&nbsp;pcode:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; plaintext += block[char_pos]

&nbsp; &nbsp; &nbsp; &nbsp; results.append(plaintext)
&nbsp; &nbsp;&nbsp;return&nbsp;results

POEM =&nbsp;"""The life that I have
Is all that I have
And the life that I have
Is yours.

The love that I have
Of the life that I have
Is yours and yours and yours.

A sleep I shall have
A rest I shall have
Yet death will be but a pause.

For the peace of my years
In the long green grass
Will be yours and yours and yours."""

CIPHERTEXT = (
&nbsp; &nbsp;&nbsp;"emzcf sebt yuwi ytrr ortl rbon aluo konf ihye cyog "
&nbsp; &nbsp;&nbsp;"rowh prhj feom ihos perp twnb tpak heoc yaui usoa "
&nbsp; &nbsp;&nbsp;"irtd tnlu ntke onds goym hmpq"
)

results = decrypt(CIPHERTEXT, POEM)
print(results[0])

运行输出:

ifyouthinkcryptographyistheanswertoyourproblemthenyoudonotknowwhatyourproblemisabcdefghijklmnopqrstu

七、关键细节:为什么必须去掉标点

诗歌中原始出现了带句号的单词,如 yours. 和 pause.。如果不去掉标点,这些单词在单词列表中的字母数就会比实际多 1,导致密码短语长度计算错误,无法找到与密文块数匹配的组合。

处理方法:在将诗歌转换为单词列表时,只保留字母字符,过滤掉所有标点和空白。这是题目附件中唯一需要注意的预处理步骤。


八、Poem Code 的历史背景

Poem Code 由 SOE(Special Operations Executive,英国特别行动处)在二战期间开发并大规模使用。其设计目标是让特工无需携带任何物理密码材料——只需在脑中记住一首诗。

加密流程:

  1. 特工与指挥部预先约定某首诗作为密钥(通常是自己熟悉的诗)。
  2. 发报时,从诗中随机选取若干单词,其索引编码为指示组附在密文前。
  3. 接收方根据指示组、结合对同一首诗的记忆,还原出密钥单词,完成解密。

这套方案的致命弱点是一旦特工被捕并被迫透露了诗的内容,历史消息即可被全部破解。SOE 后来在 1943 年以后逐步转向了更安全的一次性密码本(One-Time Pad)。

这首诗(”The life that I have”)是真实历史文物:它由英国诗人利奥·马克斯(Leo Marks)为特工维奥莱特·萨博(Violette Szabo)创作,用于实际的二战行动通信。


九、总结

| 步骤 | 内容 | | — | — | | 识别密码类型 | 观察密文结构 + 题目中的诗歌 -> 搜索到 Poem Code | | 解析指示组 | 将 emzcf 逐字母映射回诗歌单词索引 | | 枚举候选组合 | 对每个索引按 +26 步长展开候选单词,枚举全部组合 | | 确定密码短语 | 找到拼接总长恰好为 25 的组合:yoursshallpausepeaceyears | | 建立数字密钥 | 对密码短语各位置按字母表顺序编号(左到右打破同字母平局) | | 还原列顺序 | 将 25 个密文块按编号排回原始网格列位置 | | 逐行读取 | 按行从还原后的网格中读出明文 | | 去除填充 | 末尾 abcdefghijklmnopqrstu 为填充,有效明文在其前 |

Flag:ifyouthinkcryptographyistheanswertoyourproblemthenyoudonotknowwhatyourproblemis


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:破镜安全 破镜安全 破镜安全《CTF 解题详析:Decrypt the Message(Poem Code 诗歌密码)》

评论:0   参与:  0