资讯动态

Python破解手势密码:穷举逻辑与哈希比对实战

发布时间:2026/10/26 21:15:57 来源:尧图企业网站定制
简介这份资源面向对Android安全机制感兴趣的学习者与Python开发者聚焦于通过Python脚本还原Android锁屏手势密码这一具体问题。Android系统会将手势轨迹转换为十六进制数字串并做SHA1加密结果保存在/data/system/gesture.key中资源围绕该机制提供了一套可运行的破解思路与配套文件适合具备一定Python基础、希望理解哈希校验与暴力枚举原理的读者参考。压缩包共12个文件约61KB以6个xml配置文件为主另含key密钥样本、py主程序脚本、png示意图与md说明文档结构紧凑、便于快速定位核心代码与样本数据。目前已有1223人学习下载。读者可从中获得完整的脚本实现、真实gesture.key样本、加密与解密流程的对照说明以及枚举耗时与结果输出示例有助于理解手势密码的存储形式与穷举验证方法并可作为移动安全入门练习的参考素材。1. 从一次翻车说起为什么我要认真研究手势密码的穷举逻辑几年前接手一个移动端安全评估的活儿客户给了一台测试机锁屏是九宫格手势密码。当时我第一反应是“这玩意儿能有多难”结果上手才发现自己连 Android 手势密码的存储格式都没搞清楚白白耗了大半天。后来把 Python 脚本跑通、把哈希比对逻辑理顺才真正理解这套机制的设计边界在哪里。这篇内容面向三类人做移动端安全评估、想搞懂手势密码穷举原理的攻防方向读者需要判断“自己写的脚本靠不靠谱”的开发者以及刚接触这块、想照着复现一遍的新手。我会把 Python 破解手势密码的完整思路、脚本结构、参数怎么调、失败时看什么一次讲透。手势密码这个话题看起来小众但它背后涉及的哈希存储、空间枚举、性能取舍是移动端安全里非常典型的一课。2. 手势密码的存储机制先搞懂它在比什么2.1 九宫格手势密码的编码规则Android 的锁屏手势密码本质是把九宫格的九个点按顺序连成一条路径每个点用数字 0 到 8 表示最终得到一个数字序列。比如从左上角开始、经过中间、到右下角序列就是 0-4-8。系统在用户设置手势时会把这个序列做一次转换生成一个 SHA-1 哈希值存到系统文件里。这里有个关键细节存储的不是原始序列而是序列经过特定算法处理后的哈希。所以想破解不能直接读明文只能走“枚举所有可能序列 → 逐个算哈希 → 和目标哈希比对”这条路。九宫格一共九个点理论上任意长度、任意顺序的组合数量是巨大的但 Android 对手势密码有两条硬约束第一同一个点不能重复使用第二如果两个点之间隔着第三个点而第三个点还没被用过那这条连线是非法的。这两条约束把搜索空间从天文数字压缩到了几十万级别这也是为什么穷举在工程上可行。我一般会先把这个空间算清楚再决定脚本怎么写不然很容易在错误的假设上浪费时间。2.2 哈希比对的核心逻辑存储文件里保存的是一段 SHA-1 值脚本要做的就是生成一个候选序列 → 按系统同样的规则算出它的 SHA-1 → 和文件里的值比对。听起来简单但坑在于“按系统同样的规则”这几个字。序列转字节的方式、盐值有没有参与、哈希前的拼接顺序任何一处对不上算出来的值就永远匹配不了。常见的思路是先用一个自己设置的手势做对照实验设一个已知序列读出存储的哈希然后用脚本反推直到脚本算出的值和存储值一致再拿这个脚本去跑真实目标。这一步叫“校准”跳过校准直接跑大概率是白跑。提示校准阶段务必用自己可控的设备或测试环境确认脚本逻辑无误后再用于授权范围内的目标。2.3 为什么 Python 适合做这件事选 Python 不是因为性能而是因为迭代快。手势密码破解的核心工作量在枚举和哈希计算这两块 Python 都有成熟的库脚本改起来也方便。真正跑大批量枚举时可以用多进程把九宫格空间切块并行Python 的 multiprocessing 足够应付这个量级。如果追求极致速度可以把核心循环用 C 扩展重写但对大多数评估场景来说纯 Python 加多进程已经够用。我见过有人一上来就纠结语言性能结果连哈希规则都没校准对这是典型的顺序搞反了。3. 脚本拆解从空间枚举到哈希碰撞的完整链路3.1 合法手势序列的生成算法生成合法序列是整条链路的第一步也是最容易写错的一步。核心是深度优先搜索从任意一个点出发每次尝试连接一个未使用的点连接前检查“中间点”规则。所谓中间点规则是指如果起点和终点在同一条直线上且中间隔着一个点那个中间点必须已经被访问过否则这条边非法。比如 0 到 2 中间隔着 1如果 1 还没被用过0 直接连 2 就是非法的。这个规则用一张预定义的“中间点表”来实现最清晰。# 九宫格中间点表key 为 (起点, 终点)value 为中间隔着的点 # 只列出有中间点的组合其余组合中间点为空 MIDDLE { (0, 2): 1, (2, 0): 1, (0, 6): 3, (6, 0): 3, (0, 8): 4, (8, 0): 4, (1, 7): 4, (7, 1): 4, (2, 6): 4, (6, 2): 4, (2, 8): 5, (8, 2): 5, (3, 5): 4, (5, 3): 4, (6, 8): 7, (8, 6): 7, } def is_valid_move(path, start, end): 判断从 start 连到 end 是否合法 if end in path: return False # 点不能重复使用 mid MIDDLE.get((start, end)) if mid is not None and mid not in path: return False # 中间点未被访问连线非法 return True def generate_sequences(min_len4, max_len9): 生成所有合法手势序列长度在 min_len 到 max_len 之间 results [] def dfs(path): if len(path) min_len: results.append(tuple(path)) if len(path) max_len: return for nxt in range(9): if is_valid_move(path, path[-1], nxt): path.append(nxt) dfs(path) path.pop() for start in range(9): dfs([start]) return results这段代码里MIDDLE表是规则的核心is_valid_move负责单步合法性判断dfs做深度优先遍历。min_len默认取 4是因为 Android 手势密码最短就是 4 个点低于 4 的序列系统根本不接受枚举它们纯属浪费算力。max_len取 9 是上限。实际跑的时候这个函数生成的序列数量在几十万量级具体数字取决于长度范围脚本里可以打印出来确认不要凭感觉估。3.2 序列到哈希的转换与比对拿到候选序列后下一步是按系统规则算出哈希。这里的关键是搞清楚序列怎么变成字节串。一种常见做法是把每个点的数字直接转成字符再拼接另一种是转成字节。不同系统版本、不同厂商的实现可能有差异所以校准这一步不能省。下面是一个比对函数的骨架target_hash是从存储文件里读出来的值encode_func是序列转字节的策略校准时可以换几种策略试。import hashlib def sequence_to_bytes(seq, encode_func): 把手势序列转成待哈希的字节串encode_func 决定转换策略 return encode_func(seq) def crack(target_hash, sequences, encode_func): 遍历候选序列返回哈希匹配的序列 for seq in sequences: raw sequence_to_bytes(seq, encode_func) if hashlib.sha1(raw).hexdigest() target_hash: return seq return Noneencode_func可以写成lambda s: .join(str(x) for x in s).encode()也可以写成lambda s: bytes(s)具体用哪个靠校准阶段确定。crack函数是单进程版本逻辑直白适合先跑通再优化。校准的时候自己设一个手势读出哈希用这个函数跑一遍能匹配上就说明encode_func选对了。匹配不上就换策略别急着怀疑枚举逻辑。3.3 多进程加速与空间切分单进程跑几十万次 SHA-1在现代机器上其实也就几十秒到几分钟但如果你要反复调试、或者目标空间更大多进程能明显缩短等待。切分思路很简单把起始点分成九组每个进程负责一组起始点对应的子树。这样切分的好处是任务粒度均匀不会出现某个进程早早跑完、其他进程还在苦干的情况。from multiprocessing import Pool def worker(start_point): 单个进程负责以某个点开头的所有序列 local_results [] def dfs(path): if len(path) 4: local_results.append(tuple(path)) if len(path) 9: return for nxt in range(9): if is_valid_move(path, path[-1], nxt): path.append(nxt) dfs(path) path.pop() dfs([start_point]) return local_results def generate_parallel(): 九进程并行生成每个进程负责一个起始点 with Pool(processes9) as pool: chunks pool.map(worker, range(9)) return [seq for chunk in chunks for seq in chunk]worker函数把原来generate_sequences里的 DFS 逻辑搬了进来只是固定了起始点。Pool(processes9)开九个进程正好对应九个起始点pool.map会把结果收集起来再展平。这里要注意进程数不是越多越好超过 CPU 核心数反而会因为调度开销变慢九进程在多数机器上是合理值核心少的机器可以降到 4 或 6把起始点合并处理。4. 避坑与排查那些让我多熬两小时的细节4.1 哈希永远匹配不上现象脚本跑完整个空间返回None但目标哈希确实是从设备上读出来的。原因通常有两个一是encode_func策略和系统实现不一致序列转字节的方式错了二是读出来的哈希本身带了额外字符比如换行符或空格导致字符串比对失败。解决办法是先做校准实验用自己设的已知手势验证encode_func读哈希时用strip()清掉首尾空白再比对。我一般会在脚本里加一行打印目标哈希的长度SHA-1 的十六进制是 40 个字符长度不对就说明读取环节有问题。4.2 枚举结果数量对不上预期现象生成的序列数量比预想的少很多或者多很多。原因多半是MIDDLE表漏了组合或者min_len设错了。九宫格的中间点组合是固定的漏一个就会少掉一整批合法序列。解决办法是把MIDDLE表和九宫格画出来逐条核对另外打印不同长度区间的序列数量和公开的参考值对照。数量对不上时先查表再查长度边界最后才怀疑 DFS 逻辑。4.3 多进程跑完结果有重复现象并行版本生成的序列里出现重复项。原因是切分方式没对齐比如多个进程的起始点范围有重叠或者pool.map的结果展平逻辑写错。解决办法是确保每个起始点只由一个进程负责展平时用集合去重做二次校验。正常情况下按起始点切分不会产生重复出现重复基本是切分逻辑写岔了。4.4 脚本在目标环境跑不动现象脚本在自己机器上跑得好好的换到评估环境就报错或极慢。原因可能是目标环境的 Python 版本低、缺少依赖库或者 CPU 核心数少导致多进程反而拖慢。解决办法是提前确认目标环境的 Python 版本和可用库把多进程数做成可配置参数核心少就降进程数。另外纯标准库实现的脚本移植性最好尽量少依赖第三方包。4.5 校准通过但真实目标失败现象校准实验完全通过但拿真实目标的哈希去跑就是匹配不上。原因可能是目标设备的系统版本对哈希规则做了调整或者存储文件读取时拿到的不是最终哈希。解决办法是换几个不同版本、不同厂商的测试设备做交叉校准确认规则差异读取存储文件时核对文件路径和字段确保拿到的是手势密码对应的那个值而不是其他锁屏方式的值。5. 结论与落地把脚本跑通之后该做什么把整条链路走通之后我对这件事的判断是手势密码的穷举在工程上完全可行难点不在算法而在“规则对齐”和“环境适配”这两件脏活上。脚本本身不复杂生成合法序列、算哈希、比对三块逻辑加起来一两百行但校准环节和避坑环节花掉的时间远超写代码本身。如果你要复现我的建议是按这个顺序来先在自己的测试设备上设一个已知手势读出哈希把encode_func校准到能匹配再用单进程版本跑通全流程确认结果正确最后才上多进程加速并且把进程数做成参数方便在不同机器上调整。下面这张表是我在实际评估中总结的参数对照调脚本时可以直接参考参数建议值说明min_len4Android 手势最短长度低于此值系统不接受max_len9九宫格上限processes4 到 9按 CPU 核心数调整核心少取低值encode_func校准确定序列转字节策略必须与目标系统一致哈希算法SHA-1与存储文件中的算法保持一致回过头看我在这件事上最大的教训是不要跳过校准直接跑全量。第一次做的时候我凭感觉写了个encode_func跑完几十万次枚举没匹配上还以为是枚举逻辑错了回头查了半天 DFS最后发现是序列转字节的方式差了一个字符。从那以后我每次做这类哈希比对都强制先走一遍“已知输入 → 已知输出”的校准确认脚本和系统规则对齐了再动真实目标。这个习惯帮我省下的时间远比多写几行校准代码多得多。希望这份拆解能帮到你少走我当年那段弯路。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑