密码加密这词在移动端开发里,存在感一直不太强,但它恰恰是账号体系里最值得较真的部分。最近我围绕“分析58同城App的密码加密”做了一次系统性的梳理,把登录链路里所有涉及密码的环节都顺了一遍。先说明立场:这类分析不是为了找到某家产品的漏洞然后搞破坏,而是安全研究者和开发者的日常功课——搞清楚一款成熟的生活服务类App到底怎么保护用户的核心资产。这篇文章不会贴任何攻击工具的操作步骤,而是把分析思路、加密模式识别、常见坑位,以及从开发者视角该怎么做,一次性讲透。无论你是移动端研发、安全测试新人,还是单纯好奇“我的密码在App里到底经历了什么”的普通用户,都能从中拿到你要的东西。1. 为什么密码加密值得专门拎出来分析1.1 密码是账号系统的命门生活中很多App的账号体系早已不只有密码,手机验证码、生物识别、第三方登录都在分流密码的使用频率。但不管入口怎么变,密码依然是账号恢复、绑定变更、手势验证这些场景里绕不开的“最终凭据”。一旦密码这一环出问题,用户损失的往往不止一个App的账号——大量用户在多个平台复用同一个密码,这是密码泄露后最可怕的连锁反应。所以分析密码加密,本质上是在分析一款产品对自己用户资产的负责程度。一个App的密码处理逻辑写得够不够好,直接反映了整个研发团队的安全水位。我看到过不少产品,业务功能很完善,但登录请求里的密码处理方式还停留在十年前的水平,这种落差恰恰是安全分析最有价值的地方。1.2 三个位置,三种威胁密码在移动App里不是只存在一瞬间的。从用户输入到最后落库,它会经过三个位置,每个位置面临的威胁完全不同:客户端本地:攻击者可能逆向安装包、读源码、抓内存,目标是拿到硬编码密钥或者理解加密逻辑。传输链路:攻击者可能在WiFi热点、运营商链路、DNS劫持场景中监听流量,目标是截获密码明文或可重放的密文。服务端存储:攻击者可能通过数据库泄露、日志泄露、备份泄露拿到存储内容,目标是从存储的数据里还原出原始密码。分析密码加密,就是把这三个位置全部走一遍,看每一个环节是否存在可以被低成本击穿的破绽。大多数时候,真正的安全问题并不在加密算法本身,而在于算法被错误地使用。1.3 分析的目标是防御水位,不是攻击路径这里有个很容易跑偏的点。分析一款App的密码加密,如果脑子里只有“怎么绕过”,那方向就错了。防御视角的问题应该是:如果我是服务端开发,这个方案会不会被我自己推翻?如果数据库被人拷走了,用户密码能不能保住?如果客户端被逆向,密钥能不能藏得住?整套分析下来,输出的是缺陷清单和改进建议,而不是利用手法。这也是我希望文章读者建立的第一认知:掌握分析方法,最终是为了写出更结实的代码,而不是为了证明别人有多脆弱。2. 先摸清楚密码加密的完整链路2.1 一次登录请求的完整生命周期分析任何App的密码加密,第一步永远是先画出登录请求的生命周期,而不是急着看算法。一次典型的生活服务类App登录请求,大概是这样的:用户在输入框输入手机号和密码。客户端拿到原始密码字符串,进入本地预处理逻辑。预处理结果被组装进登录请求体。请求通过网络传输到服务端网关。服务端解析请求,还原或校验密码凭据。校验通过后,服务端生成登录态(Token/Session)。后续请求携带Token访问业务接口。密码加密分析要关注的,是第2步到第5步之间发生了什么。很多刚入门的分析师一上来就盯着某个加密函数的实现细节,其实更聪明的做法是先搞清楚:密码在这个流程里,到底经历了哪些变换?每次变换的目的是什么?这些变换有没有副作用?我习惯把这个生命周期拆成“客户端处理”和“服务端处理”两个半场来看,两边的责任边界往往就是问题的滋生地。2.2 常见客户端侧加密模式与识别为了帮你快速建立识别能力,我把这些年看到的客户端密码处理方式整理成一张对照表。这张表覆盖了绝大多数App的实际做法,包括58同城这类生活服务App在内的主流产品,基本都能归到某一行里。模式常见做法安全性特征能不能防住什么明文直传密码字段直接放原始字符串等于裸奔什么都防不住单次简单哈希password = md5(input)哈希固定、可重放、可查彩虹表只防不懂技术的人乱翻数据库固定盐哈希password = md5(salt + input)盐值写死在客户端代码里稍微增加彩虹表成本,但盐一旦从攻击者手里拿到就失效对称加密AES加密密码,密钥硬编码在客户端可逆、密钥明牌基本等于把家门钥匙贴在门上非对称加密客户端用RSA公钥加密,服务端私钥解密传输中途截获时得到的是密文,服务端才能还原能防监听,但防不住服务端自己出问题混合模式RSA交换密钥 + AES加密 + 时间戳/随机盐复杂度高,重放成本上升前提是密钥管理和时序设计都要严谨这张表有个核心结论:客户端做的任何处理,都不能替代服务端的存储防护。客户端能做的极限,只是降低密码在传输过程中被直接读取的风险,或者防止密码以明文形式出现在日志里。真正的安全大头永远在服务端。2.3 服务端到底应该存什么分析密码加密,不能只看客户端那半场,服务端的存储策略是更重要的观察对象。理论上,服务端存储密码只有一条黄金法则:存单向哈希,不存可逆密文,更不存明文。什么叫单向哈希?就是f(password)能算出来,但反过来从f(password)推不出原始密码。常见的实现有bcrypt、scrypt、Argon2这类专门为密码设计的慢哈希算法。它们的特点是计算速度故意“慢”,慢到攻击者即使拿到数据库,要暴力枚举一个密码也需要付出巨大代价。相比之下,MD5和SHA-1这种通用摘要算法虽然也属于单向哈希,但它们在设计时优先考虑计算速度,这就导致暴力破解成本极低——普通的GPU几分钟就能跑完几亿个候选密码。加了盐会好一些,但只要算法本身算得快,依然可以被大规模并行破解。服务端选择存哈希还是存加密后的密文,这两者的区别是本质性的。密文可逆,意味着一旦私钥或密钥泄露,所有用户密码直接暴露;哈希不可逆,密钥泄露也不等于密码泄露,攻击者还得面对暴力破解这道高墙。3. 一次高层级密码分析方法论实践:思路与步骤3.1 步骤一:边界界定与信息收集在真正开始分析之前,先花三十分钟把边界界定清楚,能省下后面的大量返工。需要确定的事情包括:目标是Android端还是iOS端?目标App的版本是什么?登录方式是密码登录还是验证码登录优先?分析范围是否包含第三方登录?这些信息决定了后续观察的优先级。比如目标产品主推手机号验证码登录,那密码加密的分析重点就应该放在“账号绑定”和“密码重置”这两个低频但关键的流程上,而不是死磕主登录入口。信息收集阶段不要一上来就打开抓包工具看请求。更合理的顺序是先看应用的权限声明和公开API文档,再研究登录页面的逻辑入口,最后才是针对接口层面的数据流。很多加密参数命名、接口字段结构在这个阶段就能猜个八九不离十。3.2 步骤二:顺着登录流程走一遍所谓“走一遍”,就是从用户输入密码开始,把每一个可能接触密码的代码路径都摸出来。注意,是每一条路径,而不是只走默认的那条。我见过太多分析报告只覆盖了“正常登录”一条链路,结果真正的问题藏在“忘记密码”流程里。忘记密码时用户要重新设置新密码,这个提交过程走的是另一套接口,加密处理往往是开发时后补的,质量参差不齐。实际做法是列一个流程清单,包括:首次登录、切换账号、修改密码、找回密码、第三方绑定设置密码、手势密码设置与校验。每个流程都要回答同一个问题:密码输入后,在客户端被做了什么变换?在请求体里以什么形式出现?服务端是否会记录这个值的日志?3.3 步骤三:逐个验证加密点顺着清单走完流程,你会得到若干个“密码接触点”。接下来要做的,是判断每个接触点上的密码变化到底属于哪种模式。验证方法其实不需要复杂的工具,通过观察输入相同密码时输出是否一致,就能判断是否存在随机盐。再结合客户端的资源文件,基本能判断出加密类型。这里不是让你去分析可执行文件找密钥,而是基于行业公开的常见实践做合理推断——很多App的密码加密方式在其开发者文档、SDK选型、技术分享里都能找到线索。判断完模式以后,还需要问三个问题:这个加密结果能不能重放?密钥或盐值是否可以从客户端安装包中单独提取?反转或暴力破解这个结果需要多少成本?这三个回答组合在一起,就构成了该加密点的安全评级。3.4 形成结构化的评估记录分析过程的产出不只是“发现了什么”,更是一份结构化的评估记录。我建议每次评估都包含以下字段:接触点名称(如“登录请求-密码字段”)加密模式判断是否有随机盐/时间戳等变化因子客户端密钥是否容易被提取传输通道是否严格走加密链路服务端存储形态推测风险等级:高/中/低建议方向:传输约束、客户端改造、服务端存储改造不要小看这种记录习惯,它能让分析报告从“我觉得这里不安全”变成“这里存在可复现的隐患,整改优先级建议如下”。这份东西对你自己的复盘、对团队排期,都有实际价值。4. 分析时最常撞见的几个典型问题4.1 硬编码密钥:客户端里没有秘密如果说密码加密分析里有什么问题出现频率最高,那一定是硬编码密钥。不少App把AES加密密码的密钥直接写死在客户端源代码里,或者放在一个名为constants.java的配置文件里。这种做法在概念上有一个根本性误区:很多人觉得加密就是“把数据变成别人看不懂的东西”,却忽略了加密靠的是密钥,而不是算法本身。密钥一旦和密文放在同一个包里,攻击者要做的就只是反编译而已。这就好比你把重要的东西锁进保险箱,然后把钥匙用胶带贴在保险箱侧面。分析时遇到对称加密模式,第一反应就应该是:密钥藏在哪?如果答案在客户端安装包内能拿到,那这个加密方案基本等于没有加密,唯一的作用是增加攻击者的阅读障碍,而不是数学障碍。4.2 弱哈希与全局固定盐哈希算法选型也是个高频问题。我现在看到新项目还在用裸MD5或SHA-1存密码,都会觉得不可思议,但真实世界就是这么魔幻。更隐蔽的坑是固定盐,也就是全站所有用户共用同一个盐值。固定盐有个致命伤:虽然它能让相同密码产生不同的哈希结果(至少从直观上),但只要盐值暴露一次,所有用户的哈希安全水平就退回无盐时代。盐的本质是增大彩虹表攻击的成本,必须做到“一客一盐”,每个用户独立随机生成,才有实际意义。分析时判断哈希方案强不强,就看三点:算法是不是专门的密码哈希算法?计算速度是不是刻意变慢了?盐是不是每个用户单独生成?三点全部满足才算合格。4.3 重放与登录态设计缺陷密码加密的分析不该只盯着静态算法,还要关注动态过程中是否可能存在重放攻击。所谓重放攻击,就是攻击者截获一次合法请求后,不改动任何内容,直接把同一份请求原样再发送一次,营造一个合法登录请求。如果密码在客户端被处理成固定哈希或固定密文,且请求里没有时间戳、随机数、挑战值这类变化因子,那么攻击者截获一次请求就能无限次伪造登录。分析时最需要确认的就是:同一个密码的第二次请求,和第一次请求是否完全一样?如果一样,说明缺少防重放机制。4.4 日志与全局记录导致密码泄漏这个坑在代码review时比在加密算法分析时更容易发现。很多开发为了调试方便,会在登录模块里加上日志输出,直接把密码明文或密码哈希打进日志文件。平时这些日志也许没什么人看,但一旦日志系统被拖库,或者日志被接入第三方分析平台,密码就跟着一起泄了。我在分析流程里会特别留意所有出现“password”关键字的日志路径。只要发现日志里出现了密码相关的原始值,不管是大写还是小写、明文还是哈希,都会标记为高优先级问题。因为哈希虽然不会直接暴露密码,但配合固定盐或弱哈希,泄露面会急剧扩大。5. 给开发者的密码加密优化建议5.1 传输层:一切的前提是严格链路先说最关键的一条:不管你的密码在客户端做了多少层处理,传输层都必须严格走加密链路。什么叫严格?连接建立时必须校验服务端证书,不能忽略证书错误继续请求,不能允许客户端降级到非加密协议。有些开发为了省事,会在代码里加上“允许所有证书”的调试逻辑,结果忘了关掉就发布了。这种问题在分析中一旦发现就是最高危信号——等于给攻击者开了一条明路。传输层的安全是“1”,客户端和服务端的加密工作是“0”,没有那个“1”,后面加再多零都没意义。5.2 客户端:卸载不该承担的责任客户端在密码安全里承担的责任经常被误解。正确认知是:客户端防不住真正有技术能力的攻击者,它只能提高攻击门槛,并避免密码以明文形式被内部系统或日志不经意记录。不要用对称加密保护密码,密钥在客户端里无法保密不要指望客户端哈希替代服务端哈希,客户端哈希的目的是防日志泄漏而不是防暴力破解如果确实需要客户端预处理,优先选择带随机盐的单向哈希,并让盐值参与整个请求体的组装永远不要硬编码密钥、盐值、私钥调试日志中禁止打印密码、密文、Token等敏感数据这里要说个实操细节:客户端加一层哈希的真正价值,很多时候是让密码不出现在服务端接入层的访问日志里。如果客户端直接传明文,那么任何记录请求体的网关南向日志都会把密码落盘。加了哈希之后,日志里至少不是原始密码,泄露面会小很多。5.3 服务端:只存加盐慢哈希服务端密码存储选型,现在的主流答案非常明确:Argon2id优先,bcrypt次之,再往后才是加了高强度参数的PBKDF2。这些算法的共同特点是设计时就考虑了暴力破解的对抗,计算复杂度可控,内存占用可以调高,让GPU并行破解的优势被大幅削弱。服务端还有一个容易被忽略的原则:永远不要把密码校验逻辑放在Web层,而是放在专门的认证模块。很多安全事件的根本原因是密码哈希的校验在业务代码里随手实现,缺少统一的错误处理、限速和风控联动,给了攻击者从容爆破的机会。5.4 补上防爆破和令牌机制加密方案再强,也挡不住有人用僵尸网络做分布式撞库。因此分析总结出一个结论:密码加密必须和账号风控配合,才会形成完整的防护闭环。具体到实现上,要做好三件事:一是登录接口必须有频率限制和异常IP/设备识别;二是对连续失败的登录尝试要及时踢出验证码或临时锁账号;三是登录成功后的Token要做生命周期管理,不能一次签发永不过期,而且要绑定设备指纹和客户端版本。没有这些配套机制,前面花大力气设计的密码加密价值会被直接削弱。6. 自检清单与个人经验分享6.1 密码加密自检速查表为了方便你在自己的项目里做快速审计,我整理了一份可以直接对照的自检清单,每一行都是分析过大量真实案例后浓缩出来的问题:检查项达标标准常见失败状态传输协议全链路加密,证书严格校验曾经允许所有证书,HTTPSURLConnection可降级客户端预处理不带明文密码进网络层请求体直接出现原始密码客户端密钥安装包内无法提取私钥/盐值硬编码AES密钥、固定全局盐哈希算法Argon2id/bcrypt/scrypt等慢哈希MD5、SHA-1、无盐SHA-256盐值策略每用户随机盐,盐长不低于16字节全站固定盐、盐为空防重放请求带随机数或时间戳并校验同一密码密文完全可重放日志输出全链路日志不包含密码相关值调试日志打印password字段服务端校验登录接口限速、失败计数无限次无延迟尝试密码这张表适合打印出来贴在工位上。我自己在每个安全项目启动前,都会先拿这张表做个初步摸底,效率比漫无目的地翻代码高得多。6.2 这几个坑,我见得太多了先说日志泄漏。有一次分析一个外包项目,加密算法本身设计得还不错,客户端做了AES加盐、服务端也用了bcrypt,看起来挺像回事。结果排查日志时发现,服务端在统一网关层打印了每个进来的请求体,密码字段虽然不是明文,但AES加密后的密文和前几段密钥都被完整记了下来。这相当于把保险箱和钥匙的照片放在同一个相册里。再说固定盐。有个开发辩解过:盐值是写死在服务端的,外部攻击者看不到,为什么不行?但安全评估从来不看“你觉得谁看不到”,而是看“如果数据泄露,攻击者拿到之后是不是立刻就能用”。服务端的固定盐藏得再深,也防不住数据库被端走后连配置一起被读。最后说防重放。很多App的密码加密都会加时间戳,这确实是个好习惯。但我见过一个案例,服务端拿到时间戳后只校验它是否在一定范围内,却忘了记录这个时间戳是否已经被使用过。结果攻击者稍微改一下时间戳就能绕过过期校验。防重放不只是“带个时间戳”,而是必须确保每个随机因子只能被使用一次。6.3 分析密码加密的最终心法讲了这么多,最后落到一个心法上:分析密码加密,永远不要先研究算法,永远先画流程图,后看代码。画图能让你看清密码在系统里流动的每一个落点,而只看代码很容易陷入“这个函数写得挺安全”的局部错觉。我自己在分析58同城App密码加密的整个过程里,最深的体会是:密码安全从来不是某个算法、某段代码的胜利,而是整个系统在多个环节共同守住底线的结果。传输层守住明文,客户端守住密钥,服务端守住哈希存储,日志系统守住敏感数据,风控机制守住暴力破解,每一道防线都缺一不可。如果你现在正想动自己的密码方案,我的建议是从服务端存储开始改起,这一步收益最大、风险最低。先把哈希算法换成慢哈希,再把固定盐升级为随机盐,最后才是调整客户端的预处理逻辑和传输链路。按这个顺序推进,你每完成一步,用户的密码安全水位都会实打实地上一个台阶。
密码加密这词在移动端开发里,存在感一直不太强,但它恰恰是账号体系里最值得较真的部分。最近我围绕“分析58同城App的密码加密”做了一次系统性的梳理,把登录链路里所有涉及密码的环节都顺了一遍。先说明立场:这类分析不是为了找到某家产品的漏洞然后搞破坏,而是安全研究者和开发者的日常功课——搞清楚一款成熟的生活服务类App到底怎么保护用户的核心资产。这篇文章不会贴任何攻击工具的操作步骤,而是把分析思路、加密模式识别、常见坑位,以及从开发者视角该怎么做,一次性讲透。无论你是移动端研发、安全测试新人,还是单纯好奇“我的密码在App里到底经历了什么”的普通用户,都能从中拿到你要的东西。1. 为什么密码加密值得专门拎出来分析1.1 密码是账号系统的命门生活中很多App的账号体系早已不只有密码,手机验证码、生物识别、第三方登录都在分流密码的使用频率。但不管入口怎么变,密码依然是账号恢复、绑定变更、手势验证这些场景里绕不开的“最终凭据”。一旦密码这一环出问题,用户损失的往往不止一个App的账号——大量用户在多个平台复用同一个密码,这是密码泄露后最可怕的连锁反应。所以分析密码加密,本质上是在分析一款产品对自己用户资产的负责程度。一个App的密码处理逻辑写得够不够好,直接反映了整个研发团队的安全水位。我看到过不少产品,业务功能很完善,但登录请求里的密码处理方式还停留在十年前的水平,这种落差恰恰是安全分析最有价值的地方。1.2 三个位置,三种威胁密码在移动App里不是只存在一瞬间的。从用户输入到最后落库,它会经过三个位置,每个位置面临的威胁完全不同:客户端本地:攻击者可能逆向安装包、读源码、抓内存,目标是拿到硬编码密钥或者理解加密逻辑。传输链路:攻击者可能在WiFi热点、运营商链路、DNS劫持场景中监听流量,目标是截获密码明文或可重放的密文。服务端存储:攻击者可能通过数据库泄露、日志泄露、备份泄露拿到存储内容,目标是从存储的数据里还原出原始密码。分析密码加密,就是把这三个位置全部走一遍,看每一个环节是否存在可以被低成本击穿的破绽。大多数时候,真正的安全问题并不在加密算法本身,而在于算法被错误地使用。1.3 分析的目标是防御水位,不是攻击路径这里有个很容易跑偏的点。分析一款App的密码加密,如果脑子里只有“怎么绕过”,那方向就错了。防御视角的问题应该是:如果我是服务端开发,这个方案会不会被我自己推翻?如果数据库被人拷走了,用户密码能不能保住?如果客户端被逆向,密钥能不能藏得住?整套分析下来,输出的是缺陷清单和改进建议,而不是利用手法。这也是我希望文章读者建立的第一认知:掌握分析方法,最终是为了写出更结实的代码,而不是为了证明别人有多脆弱。2. 先摸清楚密码加密的完整链路2.1 一次登录请求的完整生命周期分析任何App的密码加密,第一步永远是先画出登录请求的生命周期,而不是急着看算法。一次典型的生活服务类App登录请求,大概是这样的:用户在输入框输入手机号和密码。客户端拿到原始密码字符串,进入本地预处理逻辑。预处理结果被组装进登录请求体。请求通过网络传输到服务端网关。服务端解析请求,还原或校验密码凭据。校验通过后,服务端生成登录态(Token/Session)。后续请求携带Token访问业务接口。密码加密分析要关注的,是第2步到第5步之间发生了什么。很多刚入门的分析师一上来就盯着某个加密函数的实现细节,其实更聪明的做法是先搞清楚:密码在这个流程里,到底经历了哪些变换?每次变换的目的是什么?这些变换有没有副作用?我习惯把这个生命周期拆成“客户端处理”和“服务端处理”两个半场来看,两边的责任边界往往就是问题的滋生地。2.2 常见客户端侧加密模式与识别为了帮你快速建立识别能力,我把这些年看到的客户端密码处理方式整理成一张对照表。这张表覆盖了绝大多数App的实际做法,包括58同城这类生活服务App在内的主流产品,基本都能归到某一行里。模式常见做法安全性特征能不能防住什么明文直传密码字段直接放原始字符串等于裸奔什么都防不住单次简单哈希password = md5(input)哈希固定、可重放、可查彩虹表只防不懂技术的人乱翻数据库固定盐哈希password = md5(salt + input)盐值写死在客户端代码里稍微增加彩虹表成本,但盐一旦从攻击者手里拿到就失效对称加密AES加密密码,密钥硬编码在客户端可逆、密钥明牌基本等于把家门钥匙贴在门上非对称加密客户端用RSA公钥加密,服务端私钥解密传输中途截获时得到的是密文,服务端才能还原能防监听,但防不住服务端自己出问题混合模式RSA交换密钥 + AES加密 + 时间戳/随机盐复杂度高,重放成本上升前提是密钥管理和时序设计都要严谨这张表有个核心结论:客户端做的任何处理,都不能替代服务端的存储防护。客户端能做的极限,只是降低密码在传输过程中被直接读取的风险,或者防止密码以明文形式出现在日志里。真正的安全大头永远在服务端。2.3 服务端到底应该存什么分析密码加密,不能只看客户端那半场,服务端的存储策略是更重要的观察对象。理论上,服务端存储密码只有一条黄金法则:存单向哈希,不存可逆密文,更不存明文。什么叫单向哈希?就是f(password)能算出来,但反过来从f(password)推不出原始密码。常见的实现有bcrypt、scrypt、Argon2这类专门为密码设计的慢哈希算法。它们的特点是计算速度故意“慢”,慢到攻击者即使拿到数据库,要暴力枚举一个密码也需要付出巨大代价。相比之下,MD5和SHA-1这种通用摘要算法虽然也属于单向哈希,但它们在设计时优先考虑计算速度,这就导致暴力破解成本极低——普通的GPU几分钟就能跑完几亿个候选密码。加了盐会好一些,但只要算法本身算得快,依然可以被大规模并行破解。服务端选择存哈希还是存加密后的密文,这两者的区别是本质性的。密文可逆,意味着一旦私钥或密钥泄露,所有用户密码直接暴露;哈希不可逆,密钥泄露也不等于密码泄露,攻击者还得面对暴力破解这道高墙。3. 一次高层级密码分析方法论实践:思路与步骤3.1 步骤一:边界界定与信息收集在真正开始分析之前,先花三十分钟把边界界定清楚,能省下后面的大量返工。需要确定的事情包括:目标是Android端还是iOS端?目标App的版本是什么?登录方式是密码登录还是验证码登录优先?分析范围是否包含第三方登录?这些信息决定了后续观察的优先级。比如目标产品主推手机号验证码登录,那密码加密的分析重点就应该放在“账号绑定”和“密码重置”这两个低频但关键的流程上,而不是死磕主登录入口。信息收集阶段不要一上来就打开抓包工具看请求。更合理的顺序是先看应用的权限声明和公开API文档,再研究登录页面的逻辑入口,最后才是针对接口层面的数据流。很多加密参数命名、接口字段结构在这个阶段就能猜个八九不离十。3.2 步骤二:顺着登录流程走一遍所谓“走一遍”,就是从用户输入密码开始,把每一个可能接触密码的代码路径都摸出来。注意,是每一条路径,而不是只走默认的那条。我见过太多分析报告只覆盖了“正常登录”一条链路,结果真正的问题藏在“忘记密码”流程里。忘记密码时用户要重新设置新密码,这个提交过程走的是另一套接口,加密处理往往是开发时后补的,质量参差不齐。实际做法是列一个流程清单,包括:首次登录、切换账号、修改密码、找回密码、第三方绑定设置密码、手势密码设置与校验。每个流程都要回答同一个问题:密码输入后,在客户端被做了什么变换?在请求体里以什么形式出现?服务端是否会记录这个值的日志?3.3 步骤三:逐个验证加密点顺着清单走完流程,你会得到若干个“密码接触点”。接下来要做的,是判断每个接触点上的密码变化到底属于哪种模式。验证方法其实不需要复杂的工具,通过观察输入相同密码时输出是否一致,就能判断是否存在随机盐。再结合客户端的资源文件,基本能判断出加密类型。这里不是让你去分析可执行文件找密钥,而是基于行业公开的常见实践做合理推断——很多App的密码加密方式在其开发者文档、SDK选型、技术分享里都能找到线索。判断完模式以后,还需要问三个问题:这个加密结果能不能重放?密钥或盐值是否可以从客户端安装包中单独提取?反转或暴力破解这个结果需要多少成本?这三个回答组合在一起,就构成了该加密点的安全评级。3.4 形成结构化的评估记录分析过程的产出不只是“发现了什么”,更是一份结构化的评估记录。我建议每次评估都包含以下字段:接触点名称(如“登录请求-密码字段”)加密模式判断是否有随机盐/时间戳等变化因子客户端密钥是否容易被提取传输通道是否严格走加密链路服务端存储形态推测风险等级:高/中/低建议方向:传输约束、客户端改造、服务端存储改造不要小看这种记录习惯,它能让分析报告从“我觉得这里不安全”变成“这里存在可复现的隐患,整改优先级建议如下”。这份东西对你自己的复盘、对团队排期,都有实际价值。4. 分析时最常撞见的几个典型问题4.1 硬编码密钥:客户端里没有秘密如果说密码加密分析里有什么问题出现频率最高,那一定是硬编码密钥。不少App把AES加密密码的密钥直接写死在客户端源代码里,或者放在一个名为constants.java的配置文件里。这种做法在概念上有一个根本性误区:很多人觉得加密就是“把数据变成别人看不懂的东西”,却忽略了加密靠的是密钥,而不是算法本身。密钥一旦和密文放在同一个包里,攻击者要做的就只是反编译而已。这就好比你把重要的东西锁进保险箱,然后把钥匙用胶带贴在保险箱侧面。分析时遇到对称加密模式,第一反应就应该是:密钥藏在哪?如果答案在客户端安装包内能拿到,那这个加密方案基本等于没有加密,唯一的作用是增加攻击者的阅读障碍,而不是数学障碍。4.2 弱哈希与全局固定盐哈希算法选型也是个高频问题。我现在看到新项目还在用裸MD5或SHA-1存密码,都会觉得不可思议,但真实世界就是这么魔幻。更隐蔽的坑是固定盐,也就是全站所有用户共用同一个盐值。固定盐有个致命伤:虽然它能让相同密码产生不同的哈希结果(至少从直观上),但只要盐值暴露一次,所有用户的哈希安全水平就退回无盐时代。盐的本质是增大彩虹表攻击的成本,必须做到“一客一盐”,每个用户独立随机生成,才有实际意义。分析时判断哈希方案强不强,就看三点:算法是不是专门的密码哈希算法?计算速度是不是刻意变慢了?盐是不是每个用户单独生成?三点全部满足才算合格。4.3 重放与登录态设计缺陷密码加密的分析不该只盯着静态算法,还要关注动态过程中是否可能存在重放攻击。所谓重放攻击,就是攻击者截获一次合法请求后,不改动任何内容,直接把同一份请求原样再发送一次,营造一个合法登录请求。如果密码在客户端被处理成固定哈希或固定密文,且请求里没有时间戳、随机数、挑战值这类变化因子,那么攻击者截获一次请求就能无限次伪造登录。分析时最需要确认的就是:同一个密码的第二次请求,和第一次请求是否完全一样?如果一样,说明缺少防重放机制。4.4 日志与全局记录导致密码泄漏这个坑在代码review时比在加密算法分析时更容易发现。很多开发为了调试方便,会在登录模块里加上日志输出,直接把密码明文或密码哈希打进日志文件。平时这些日志也许没什么人看,但一旦日志系统被拖库,或者日志被接入第三方分析平台,密码就跟着一起泄了。我在分析流程里会特别留意所有出现“password”关键字的日志路径。只要发现日志里出现了密码相关的原始值,不管是大写还是小写、明文还是哈希,都会标记为高优先级问题。因为哈希虽然不会直接暴露密码,但配合固定盐或弱哈希,泄露面会急剧扩大。5. 给开发者的密码加密优化建议5.1 传输层:一切的前提是严格链路先说最关键的一条:不管你的密码在客户端做了多少层处理,传输层都必须严格走加密链路。什么叫严格?连接建立时必须校验服务端证书,不能忽略证书错误继续请求,不能允许客户端降级到非加密协议。有些开发为了省事,会在代码里加上“允许所有证书”的调试逻辑,结果忘了关掉就发布了。这种问题在分析中一旦发现就是最高危信号——等于给攻击者开了一条明路。传输层的安全是“1”,客户端和服务端的加密工作是“0”,没有那个“1”,后面加再多零都没意义。5.2 客户端:卸载不该承担的责任客户端在密码安全里承担的责任经常被误解。正确认知是:客户端防不住真正有技术能力的攻击者,它只能提高攻击门槛,并避免密码以明文形式被内部系统或日志不经意记录。不要用对称加密保护密码,密钥在客户端里无法保密不要指望客户端哈希替代服务端哈希,客户端哈希的目的是防日志泄漏而不是防暴力破解如果确实需要客户端预处理,优先选择带随机盐的单向哈希,并让盐值参与整个请求体的组装永远不要硬编码密钥、盐值、私钥调试日志中禁止打印密码、密文、Token等敏感数据这里要说个实操细节:客户端加一层哈希的真正价值,很多时候是让密码不出现在服务端接入层的访问日志里。如果客户端直接传明文,那么任何记录请求体的网关南向日志都会把密码落盘。加了哈希之后,日志里至少不是原始密码,泄露面会小很多。5.3 服务端:只存加盐慢哈希服务端密码存储选型,现在的主流答案非常明确:Argon2id优先,bcrypt次之,再往后才是加了高强度参数的PBKDF2。这些算法的共同特点是设计时就考虑了暴力破解的对抗,计算复杂度可控,内存占用可以调高,让GPU并行破解的优势被大幅削弱。服务端还有一个容易被忽略的原则:永远不要把密码校验逻辑放在Web层,而是放在专门的认证模块。很多安全事件的根本原因是密码哈希的校验在业务代码里随手实现,缺少统一的错误处理、限速和风控联动,给了攻击者从容爆破的机会。5.4 补上防爆破和令牌机制加密方案再强,也挡不住有人用僵尸网络做分布式撞库。因此分析总结出一个结论:密码加密必须和账号风控配合,才会形成完整的防护闭环。具体到实现上,要做好三件事:一是登录接口必须有频率限制和异常IP/设备识别;二是对连续失败的登录尝试要及时踢出验证码或临时锁账号;三是登录成功后的Token要做生命周期管理,不能一次签发永不过期,而且要绑定设备指纹和客户端版本。没有这些配套机制,前面花大力气设计的密码加密价值会被直接削弱。6. 自检清单与个人经验分享6.1 密码加密自检速查表为了方便你在自己的项目里做快速审计,我整理了一份可以直接对照的自检清单,每一行都是分析过大量真实案例后浓缩出来的问题:检查项达标标准常见失败状态传输协议全链路加密,证书严格校验曾经允许所有证书,HTTPSURLConnection可降级客户端预处理不带明文密码进网络层请求体直接出现原始密码客户端密钥安装包内无法提取私钥/盐值硬编码AES密钥、固定全局盐哈希算法Argon2id/bcrypt/scrypt等慢哈希MD5、SHA-1、无盐SHA-256盐值策略每用户随机盐,盐长不低于16字节全站固定盐、盐为空防重放请求带随机数或时间戳并校验同一密码密文完全可重放日志输出全链路日志不包含密码相关值调试日志打印password字段服务端校验登录接口限速、失败计数无限次无延迟尝试密码这张表适合打印出来贴在工位上。我自己在每个安全项目启动前,都会先拿这张表做个初步摸底,效率比漫无目的地翻代码高得多。6.2 这几个坑,我见得太多了先说日志泄漏。有一次分析一个外包项目,加密算法本身设计得还不错,客户端做了AES加盐、服务端也用了bcrypt,看起来挺像回事。结果排查日志时发现,服务端在统一网关层打印了每个进来的请求体,密码字段虽然不是明文,但AES加密后的密文和前几段密钥都被完整记了下来。这相当于把保险箱和钥匙的照片放在同一个相册里。再说固定盐。有个开发辩解过:盐值是写死在服务端的,外部攻击者看不到,为什么不行?但安全评估从来不看“你觉得谁看不到”,而是看“如果数据泄露,攻击者拿到之后是不是立刻就能用”。服务端的固定盐藏得再深,也防不住数据库被端走后连配置一起被读。最后说防重放。很多App的密码加密都会加时间戳,这确实是个好习惯。但我见过一个案例,服务端拿到时间戳后只校验它是否在一定范围内,却忘了记录这个时间戳是否已经被使用过。结果攻击者稍微改一下时间戳就能绕过过期校验。防重放不只是“带个时间戳”,而是必须确保每个随机因子只能被使用一次。6.3 分析密码加密的最终心法讲了这么多,最后落到一个心法上:分析密码加密,永远不要先研究算法,永远先画流程图,后看代码。画图能让你看清密码在系统里流动的每一个落点,而只看代码很容易陷入“这个函数写得挺安全”的局部错觉。我自己在分析58同城App密码加密的整个过程里,最深的体会是:密码安全从来不是某个算法、某段代码的胜利,而是整个系统在多个环节共同守住底线的结果。传输层守住明文,客户端守住密钥,服务端守住哈希存储,日志系统守住敏感数据,风控机制守住暴力破解,每一道防线都缺一不可。如果你现在正想动自己的密码方案,我的建议是从服务端存储开始改起,这一步收益最大、风险最低。先把哈希算法换成慢哈希,再把固定盐升级为随机盐,最后才是调整客户端的预处理逻辑和传输链路。按这个顺序推进,你每完成一步,用户的密码安全水位都会实打实地上一个台阶。