Ciphey 搜索内核重构:A* 模块拆分与不可见字符过滤驱动的字符串质量增强实践
【免费下载链接】Ciphey⚡ Automatically decrypt encryptions without knowing the key or cipher, decode encodings, and crack hashes ⚡项目地址: https://gitcode.com/gh_mirrors/ci/Ciphey
导读
本文以 docs/changes/2024-07-10-astar-refactor.md 变更记录为骨架,系统拆解 Ciphey 对 A* 解码搜索实现的一次关键重构:将原本内聚的搜索代码拆分为核心算法与辅助函数两个模块,并增强字符串质量评估——对包含超过 50% 不可见字符的候选字符串立即拒绝,从而提升内存效率与搜索聚焦度。读完本文,你将掌握 Ciphey A* 搜索器的模块边界划分、字符串质量评分公式与阈值语义、对应的测试用例设计,以及该重构与启发式简化、字符串剪枝等后续演进之间的承接关系。
一、重构背景与目标
Ciphey 的搜索器(searcher)负责决定“下一步尝试哪些解码器、按什么顺序尝试”。在其搜索内核中,A* 是最优先采用的算法(src/searchers/mod.rs 中search_for_plaintext直接以独立线程启动astar::astar)。随着启发式函数、统计学习与并行扩展等特性不断叠加,astar.rs单文件日益臃肿,职责混杂。
本次重构的核心目标有两个:
- 代码组织优化:将 A* 实现拆分为“搜索算法本体”与“支撑性工具函数”两个文件,让启发式计算、质量评估、统计维护等逻辑从搜索主循环中剥离;
- 字符串质量增强:在
calculate_string_quality中引入不可见字符占比阈值,对超过 50% 不可见字符的字符串直接返回最低质量分 0.0,使这类候选在进入解码路径之前即被淘汰。
从实际收益看,这次重构属于典型的“整理+加固”:它不改变 A* 的搜索范式,而是让搜索过程中的每一次节点扩展(node expansion)都更快地丢弃低价值分支。
二、模块拆分:从单文件到职责分离
重构最直观的产出是src/searchers/目录下的文件边界变化,其结构为:
- src/searchers/astar.rs:A* 核心搜索算法实现,包括节点定义、优先级队列、并行批处理与主搜索循环;
- src/searchers/helper_functions.rs:支撑 A* 的辅助函数,涵盖启发式计算、字符串质量评估、解码器统计与序列模式判断;
- src/searchers/mod.rs:通过
mod astar;、mod bfs;、mod helper_functions;声明三个子模块,并将astar以pub对外暴露(src/searchers/mod.rs)。
2.1 astar.rs:核心搜索算法
重构后的 src/searchers/astar.rs 只负责“搜索”本身,内部包含四个关键构件:
AStarNode节点(src/searchers/astar.rs):持有当前解码状态state、已付出代价cost(即搜索树深度 g)、综合代价total_cost(f = g + h),以及下一次要尝试的解码器名next_decoder_name;- 最小堆优先级队列:基于
BinaryHeap反向排序实现ThreadSafePriorityQueue,并封装push、pop、extract_batch等线程安全操作(src/searchers/astar.rs); expand_node节点扩展(src/searchers/astar.rs):先执行“decoder 标记”解码器,再回退到全量解码器;期间依次执行空文本跳过、calculate_string_worth质量过滤、seen_strings去重与update_decoder_stats统计更新;astar主循环(src/searchers/astar.rs):以 Rayon 并行批处理(PARALLEL_BATCH_SIZE = 10)扩展节点,识别__RESULT__标记节点并通过crossbeam通道发送结果,在top_results模式下还会将明文写入wait_athena_storage。
2.2 helper_functions.rs:辅助函数模块
src/searchers/helper_functions.rs 是本次重构新增的模块,聚合了原本散落在搜索实现中的工具函数:
generate_heuristic:A* 启发式(src/searchers/helper_functions.rs);calculate_string_quality/calculate_string_worth:字符串质量评估与可解码性判定(src/searchers/helper_functions.rs);calculate_non_printable_ratio:非可打印字符比例统计(src/searchers/helper_functions.rs);update_decoder_stats/get_decoder_success_rate:解码器成功率的进程内统计(src/searchers/helper_functions.rs);is_common_sequence:解码器序列常见度判断(src/searchers/helper_functions.rs);check_if_string_cant_be_decoded:BFS 路径的剪枝判定(src/searchers/helper_functions.rs)。
该模块同时被 A* 与 BFS 两条搜索路径复用——src/searchers/bfs.rs 导入了check_if_string_cant_be_decoded用于节点过滤。这正是“单一职责 + 跨算法复用”的直接体现。
三、字符串质量评估增强:不可见字符过滤
本次重构的第二个重点是calculate_string_quality的行为增强。
3.1 质量评分公式
重构后的函数逻辑(src/searchers/helper_functions.rs)分为两级:
第一级:不可见字符硬性过滤
let non_printable_ratio = calculate_non_printable_ratio(s); if non_printable_ratio > 0.5 { return 0.0; // Return lowest quality for strings with >50% invisible chars }一旦字符串中不可见(非可打印)字符占比超过 50%,质量分直接归零。由于后续calculate_string_worth以 0.2 为及格线(见 3.3),这类字符串会在节点扩展阶段被continue跳过,从根本上阻止解码器对垃圾输入的空转。
第二级:长度驱动的分段评分
if s.len() < 3 { 0.1 } else if s.len() > 5000 { 0.3 } else { 1.0 - (s.len() as f32 - 100.0).abs() / 900.0 }- 长度小于 3 的字符串质量固定为 0.1(过短,信息量不足);
- 长度超过 5000 的字符串质量固定为 0.3(过长,多为噪声);
- 其余字符串以 100 字符为最优长度中心,质量随
|len - 100|线性衰减,900 为归一化分母。
3.2 非可打印字符比例的实现
calculate_non_printable_ratio(src/searchers/helper_functions.rs)统计的是控制字符(排除\n、\r、\t等常规空白)与非 ASCII 字符的占比:
let non_printable_count = text .chars() .filter(|&c| { (c.is_control() && c != '\n' && c != '\r' && c != '\t') || !c.is_ascii() }) .count(); non_printable_count as f32 / text.len() as f32空字符串被定义为 1.0(全不可打印)。Hello World与123!@#\n\t的比例为 0.0,而\u{0}\u{1}\u{2}的比例为 1.0。注意这里的“不可见字符”是从控制字符角度判定的;仓库另有一份独立的不可见字符表 src/storage/invisible_chars/chars.txt,由 src/storage/mod.rs 加载为INVISIBLE_CHARS集合,用于 CLI 明文输出时的安全提示(见 src/cli_pretty_printing/mod.rs),二者用途不同、互为补充。
3.3 与既有过滤机制的协同
本次增强并非孤立修改,而是与字符串剪枝体系协同工作:
- A路径*:
expand_node在每次解码产出新文本后调用calculate_string_worth(&text[0])(src/searchers/astar.rs),质量分低于 0.2 即跳过该结果并标记解码失败;随后才进行哈希去重(calculate_hash+DashSet)与节点入队; - BFS 路径:
check_if_string_cant_be_decoded综合三项判据——长度 ≤ 2、非可打印比例 > 0.3、质量分 < 0.2——任一命中即判定不可解码(src/searchers/helper_functions.rs)。
从源码结构看,A* 的calculate_string_worth只依赖质量分阈值,而 BFS 的check_if_string_cant_be_decoded阈值更激进(0.3 比例即拒绝),两条路径对低质量字符串的容忍度并不相同,属于有意为之的差异化策略。
3.4 新增测试用例
重构同步新增了针对不可见字符过滤的单元测试test_calculate_string_quality_with_invisible_chars(src/searchers/helper_functions.rs),用三组输入验证阈值语义:
"Hello\u{0}\u{0}\u{0}\u{0}World"(4/14 ≈ 28.6% 不可见)→ 质量分 > 0.0,通过;"\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}Hello"(7/12 ≈ 58.3% 不可见)→ 质量分等于 0.0,被拒绝;- 全不可见字符串 → 质量分等于 0.0,被拒绝。
配套的test_calculate_non_printable_ratio(src/searchers/helper_functions.rs)则验证了比例计算本身,包括常规文本为 0.0、混合内容约为 0.1666、纯控制字符为 1.0、空字符串为 1.0 等边界情况。
四、权衡分析:收益与代价
关联文档对该重构的 Trade-off 有明确陈述,结合源码可进一步展开:
优势
- 内存效率提升:>50% 不可见字符的字符串在进入解码器之前即被淘汰,配合 A* 的
seen_strings去重集合(PRUNE_THRESHOLD = 100000,见 src/searchers/astar.rs),显著减少搜索空间的膨胀; - 可维护性增强:辅助函数独立成模块后,
astar.rs的主循环可读性明显改善,函数级单元测试(如启发式、质量评估)可以直接针对helper_functions编写,无需构造完整搜索场景; - 跨算法复用:
check_if_string_cant_be_decoded被 BFS 引用即是模块拆分带来的直接红利。
代价
- 模块复杂度略增:新增一个文件意味着模块间函数调用增加,存在轻微的性能开销(跨模块调用对分支预测与内联的扰动);
- 需要持续校准阈值:50% 不可见字符阈值若设置不当,可能误伤少数“长得奇怪但有效”的编码文本(如部分二进制编码产物),因此必须由测试用例持续守护。
五、结合 A* 全链路理解重构的价值
要真正理解这次字符串质量增强的意义,需要把它放回 A* 的完整决策链路中。
5.1 启发式与质量惩罚
当前实现的generate_heuristic(src/searchers/helper_functions.rs)采用加法式评分,包含四个分量:
- 解码器热度分量:
base_score += 1.0 - decoder.get_popularity(),热度越高贡献越低; - 成功率代理分量:
base_score += (1.0 - get_decoder_success_rate(name)) * 0.25,用进程内成功率(见update_decoder_stats,src/searchers/helper_functions.rs)作为热度的补充信号; - 深度惩罚:
(0.05 * (1.0 + depth/20) * depth)²,深度越大惩罚越激进,防止搜索在无意义路径上越走越深; - 字符串质量分量:
base_score += (1.0 - quality) * 0.5,低质量字符串直接推高启发值,使其在优先级队列中后置。
可见calculate_string_quality的输出同时影响两处:generate_heuristic中的质量惩罚项,以及expand_node中的硬性淘汰判定。硬过滤在前、软惩罚在后,构成“先拒绝、后降权”的双层防线。
5.2 并行扩展与统计学习
A* 主循环每次从队列批量取出至多 10 个节点(PARALLEL_BATCH_SIZE),用 Rayonpar_iter并行执行expand_node(src/searchers/astar.rs)。每个节点的成功/失败都会回调update_decoder_stats,实时更新该解码器的成功次数与总尝试次数,并进而影响后续节点的启发值——这是“统计学习”机制的进程内闭环。文档中提到的“将成功率持久化到磁盘、实现跨会话学习”仍是未完成的 TODO(见 src/searchers/helper_functions.rs 的注释)。
5.3 动态剪枝
当seen_strings规模超过当前阈值时,A* 会清空已见集合并按搜索进度收紧阈值(src/searchers/astar.rs):
let progress_factor = new_depth as f32 / MAX_DEPTH as f32; let new_threshold = INITIAL_PRUNE_THRESHOLD - (progress_factor * 5000.0) as usize;即阈值从 100000 起,随深度(MAX_DEPTH = 100)线性下调至 95000,越深入搜索越频繁剪枝。字符串质量增强与此机制互为补充:质量过滤从“源头”减少进入seen_strings的垃圾字符串,动态剪枝则从“存量”控制集合规模。
六、重构的后续演进脉络
将本次变更与同目录下的其他变更记录对照,可以看到一条清晰的演进主线:
- 2024-07-10-astar-simplified-heuristic-rewrite.md(docs/changes/2024-07-10-astar-simplified-heuristic-rewrite.md):将启发式从“多因子乘法惩罚”改为“加法式简化模型”,移除 CipherIdentifier 依赖,新增深度惩罚与热度分量——这正是重构后
helper_functions.rs中generate_heuristic的形态来源; - 2024-07-10-improve-string-pruning.md(docs/changes/2024-07-10-improve-string-pruning.md):将
check_if_string_cant_be_decoded扩展为“长度 + 非可打印比例 + 质量分”三重判据,与本文的 50% 阈值增强同属字符串剪枝主题; - 2024-07-10-remove-decoder-popularity.md(docs/changes/2024-07-10-remove-decoder-popularity.md):移除冗余的
get_decoder_popularity函数,改为以解码器自身popularity属性与成功率代理,进一步收紧了辅助函数模块的 API 面。
这些变更与本次重构共享同一目标:让 A在更少的节点上做更聪明的决策*。质量评估负责“少生成垃圾节点”,启发式负责“优先生成好节点”,剪枝负责“控制节点总量”。
七、未来改进方向
关联文档列出的 Future Improvements 与源码现状高度吻合:
- 成功率持久化:
DECODER_SUCCESS_RATES目前仅为Lazy<Mutex<HashMap>>的进程内结构(src/searchers/helper_functions.rs),写入磁盘的 TODO 尚未实现;一旦落地,即可实现跨会话学习,让启发式基于历史数据而非单次运行; - 更精细的语言检测:当前质量评估仅依赖长度与控制字符比例,文档建议引入更成熟的语言/文本检测手段(如 n-gram 或熵分析)以提升对“随机噪声”与“真实编码”的区分度;
- 工具函数进一步下沉:
is_common_sequence、质量评估等通用函数可继续沉淀在helper_functions模块中,供 BFS、未来的 Beam Search 等其他搜索算法复用。
结语
2024-07-10的这次重构,是 Ciphey 搜索内核从“能用”走向“可控”的关键一步。通过astar.rs与helper_functions.rs的职责切分,搜索算法、启发式、质量评估与统计维护各归其位;通过calculate_string_quality对 50% 不可见字符的硬性拒绝,搜索资源被集中投放到真正值得解码的候选上。对于希望深入理解 Ciphey 搜索机制或借鉴其架构思路的开发者,建议按 src/searchers/mod.rs → src/searchers/astar.rs → src/searchers/helper_functions.rs 的顺序阅读源码,并以 src/searchers/helper_functions.rs 的测试集作为行为规格说明,即可快速建立完整认知。
【免费下载链接】Ciphey⚡ Automatically decrypt encryptions without knowing the key or cipher, decode encodings, and crack hashes ⚡项目地址: https://gitcode.com/gh_mirrors/ci/Ciphey
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考