复杂和简单矛盾的加密题——江苏省第四届数据安全技术应用职业技能竞赛初赛

admin 2026-07-11 05:10:18 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文记录了江苏省第四届数据安全技术应用职业技能竞赛初赛中的一道RSA加密题解题过程。作者自认是脚本小子,但发现利用AI大模型可轻松解决此类数学题。文章详细描述了使用SageMath环境进行CGL哈希逆运算的BFS搜索,最终找到路径并解密获得FLAG。作者分享了环境搭建和完整解题代码,并对是否值得深入学习加密技术表达了迷惘。 综合评分: 78 文章分类: CTF,安全工具,AI安全,实战经验,漏洞分析


cover_image

复杂和简单矛盾的加密题——江苏省第四届数据安全技术应用职业技能竞赛初赛

原创

一只岸上的鱼 一只岸上的鱼

一只岸上的鱼

2026年7月10日 08:33 江苏

在小说阅读器读本章

去阅读

复杂和简单矛盾的加密题——江苏省第四届数据安全技术应用职业技能竞赛初赛

缘起

对于RSA加密题,我一直认为自己只能做一个脚本小子————收集每一次遇到的新题型,只希望下次比赛用得上

针对新出现的,稍微修改一点逻辑,需要根据一下新的加密算法或者推导逻辑去修改的,我都只能放弃了

但是自动AI的出现,一切都变了:

如果允许使用AI,那么这些题目就像签到题一样,虽然扔给一个大模型,基本都能解,毕竟只是一道数学题而已

如果不允许使用AI,那还是不要浪费时间,在其他题目上花点时间吧/(ㄒoㄒ)/~~

今天这题,也是没做出来了,赛后扔个ai,分分钟给了答案,这里只记录一下环境的搭建:

需要sagemath的运行环境

题目:

题干很简单,但是附件代码却基本看不懂/(ㄒoㄒ)/~~

题目附件内容我都放在了cnb:https://cnb.cool/netvvorm/items/datasecurity26

解题

解题没什么好说了,就是将附件代码和题干,全发给了大模型,没过几分钟,吐给我一个代码:

python

# CGL Hash Inversion - Complete SageMath Solution
#
# This script should be run in SageMath (not plain Python).
# It correctly implements BFS on the 2-isogeny graph to find the path.

PRIME = (2**13) * (3**7) - 1
print(f"PRIME = {PRIME}")

# Setup the field and curve
K.<i> = GF(PRIME**2, modulus=x**2&nbsp;+&nbsp;1)
Y = polygen(K)

E0 = EllipticCurve(K, [0,&nbsp;6,&nbsp;0,&nbsp;1,&nbsp;0])
start_j = E0.j_invariant()
print(f"start_j =&nbsp;{start_j}")

# Classical modular polynomial of level 2
Phi2 = classical_modular_polynomial(2).change_ring(K)
vars2 = Phi2.parent().gens()

# CGL hash function (forward)
def&nbsp;CGL_hash(msg_bits, j_start):
&nbsp; &nbsp; curr_j = j_start
&nbsp; &nbsp; prev_j =&nbsp;None
&nbsp; &nbsp;&nbsp;for&nbsp;bit&nbsp;in&nbsp;msg_bits:
&nbsp; &nbsp; &nbsp; &nbsp; f = Phi2.subs({vars2[0]: curr_j, vars2[1]: Y})
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;hasattr(f,&nbsp;'univariate_polynomial'):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; f = f.univariate_polynomial()
&nbsp; &nbsp; &nbsp; &nbsp; roots = f.roots(multiplicities=False)
&nbsp; &nbsp; &nbsp; &nbsp; valid_neighbors = [r&nbsp;for&nbsp;r&nbsp;in&nbsp;roots&nbsp;if&nbsp;r != prev_j]
&nbsp; &nbsp; &nbsp; &nbsp; valid_sorted =&nbsp;sorted(valid_neighbors, key=lambda&nbsp;r: [int(c)&nbsp;for&nbsp;c&nbsp;in&nbsp;r.list()])
&nbsp; &nbsp; &nbsp; &nbsp; prev_j = curr_j
&nbsp; &nbsp; &nbsp; &nbsp; curr_j = valid_sorted[bit]
&nbsp; &nbsp;&nbsp;return&nbsp;curr_j

# Get neighbors function
def&nbsp;get_neighbors(j):
&nbsp; &nbsp; f = Phi2.subs({vars2[0]: j, vars2[1]: Y})
&nbsp; &nbsp;&nbsp;if&nbsp;hasattr(f,&nbsp;'univariate_polynomial'):
&nbsp; &nbsp; &nbsp; &nbsp; f = f.univariate_polynomial()
&nbsp; &nbsp;&nbsp;return&nbsp;f.roots(multiplicities=False)

# BFS to find path from start_j to target_j
def&nbsp;BFS_find_path(start_j, target_j, max_depth=100):
&nbsp; &nbsp;&nbsp;from&nbsp;collections&nbsp;import&nbsp;deque

&nbsp; &nbsp; queue = deque()
&nbsp; &nbsp; queue.append((start_j,&nbsp;None, [])) &nbsp;# (curr_j, prev_j, path)
&nbsp; &nbsp; visited =&nbsp;set()
&nbsp; &nbsp; visited.add((start_j,&nbsp;None))

&nbsp; &nbsp; nodes_explored =&nbsp;0

&nbsp; &nbsp;&nbsp;while&nbsp;queue:
&nbsp; &nbsp; &nbsp; &nbsp; curr_j, prev_j, path = queue.popleft()
&nbsp; &nbsp; &nbsp; &nbsp; nodes_explored +=&nbsp;1

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;nodes_explored %&nbsp;10000&nbsp;==&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f" &nbsp;Explored&nbsp;{nodes_explored}&nbsp;nodes, depth&nbsp;{len(path)}")

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;len(path) > max_depth:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue

&nbsp; &nbsp; &nbsp; &nbsp; neighbors = get_neighbors(curr_j)
&nbsp; &nbsp; &nbsp; &nbsp; valid = [r&nbsp;for&nbsp;r&nbsp;in&nbsp;neighbors&nbsp;if&nbsp;r != prev_j]
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;prev_j&nbsp;is&nbsp;None:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; valid = neighbors &nbsp;# At first step, all 3 neighbors are valid (bits 0 and 1 use first 2)

&nbsp; &nbsp; &nbsp; &nbsp; valid_sorted =&nbsp;sorted(valid, key=lambda&nbsp;r: [int(c)&nbsp;for&nbsp;c&nbsp;in&nbsp;r.list()])

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;bit_idx&nbsp;in&nbsp;range(min(2,&nbsp;len(valid_sorted))):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; next_j = valid_sorted[bit_idx]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; new_path = path + [bit_idx]

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;next_j == target_j:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;new_path

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; state = (next_j, curr_j)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;state&nbsp;not&nbsp;in&nbsp;visited:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; visited.add(state)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; queue.append((next_j, curr_j, new_path))

&nbsp; &nbsp;&nbsp;print(f"Explored&nbsp;{nodes_explored}&nbsp;nodes, target not found.")
&nbsp; &nbsp;&nbsp;return&nbsp;None

# Target j-invariant from the problem
target_j = K(17702267&nbsp;+&nbsp;9824216*i)
print(f"target_j =&nbsp;{target_j}")

# Run BFS
print("\nRunning BFS to find path...")
path = BFS_find_path(start_j, target_j, max_depth=80)

if&nbsp;path&nbsp;is&nbsp;None:
&nbsp; &nbsp;&nbsp;print("Path not found within depth limit.")
else:
&nbsp; &nbsp;&nbsp;print(f"\nFound path:&nbsp;{path}")
&nbsp; &nbsp;&nbsp;print(f"Path length:&nbsp;{len(path)}")

&nbsp; &nbsp;&nbsp;# Verify
&nbsp; &nbsp; computed = CGL_hash(path, start_j)
&nbsp; &nbsp;&nbsp;print(f"Computed target:&nbsp;{computed}")
&nbsp; &nbsp;&nbsp;print(f"Expected target:&nbsp;{target_j}")

&nbsp; &nbsp;&nbsp;if&nbsp;computed == target_j:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("\n✓ Path verified! Now decrypting...")

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# Decrypt
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;from&nbsp;Crypto.Cipher&nbsp;import&nbsp;AES
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;from&nbsp;Crypto.Util.Padding&nbsp;import&nbsp;unpad
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;import&nbsp;hashlib

&nbsp; &nbsp; &nbsp; &nbsp; secret_path_str =&nbsp;str(path)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f"Secret path string:&nbsp;{secret_path_str}")
&nbsp; &nbsp; &nbsp; &nbsp; aes_key = hashlib.sha256(secret_path_str.encode()).digest()[:16]
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f"AES key (hex):&nbsp;{aes_key.hex()}")

&nbsp; &nbsp; &nbsp; &nbsp; ct_hex =&nbsp;"3cea38f9b42cb724edf17080682bf33f782f1a9652163a87001f0aab505ae39d86a808b0a7b817d83082ef7fdd3d195662d78c34aeda37e32f16f5303d352c12"
&nbsp; &nbsp; &nbsp; &nbsp; ct =&nbsp;bytes.fromhex(ct_hex)
&nbsp; &nbsp; &nbsp; &nbsp; iv = ct[:16]
&nbsp; &nbsp; &nbsp; &nbsp; cipher = AES.new(aes_key, AES.MODE_CBC, iv=iv)
&nbsp; &nbsp; &nbsp; &nbsp; pt = unpad(cipher.decrypt(ct[16:]),&nbsp;16)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f"\n*** FLAG:&nbsp;{pt.decode()}&nbsp;***")
&nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("Path verification FAILED!")

# If BFS doesn't find the path, try with larger depth or different strategy
print("\nDone.")

是时候准备一个sagemath环境了

我还是在cnb制作了一个环境,基于官方的docker镜像:

https://cnb.cool/ikunkun/demo/sagepreview

启动后点开demo,就是这个题解:

小结

就目前来说,对于加密解密的代码题,还是蛮纠结的,想要入门,得花不少时间和精力,

如果可以用大模型,那这大部分时候就是一签到题,花费大量力气去学感觉又不值得,迷惘中……


免责声明:

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

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

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

本文转载自:一只岸上的鱼 一只岸上的鱼 一只岸上的鱼《复杂和简单矛盾的加密题——江苏省第四届数据安全技术应用职业技能竞赛初赛》

评论:0   参与:  0