多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

登录加密方案拆解:从算法选型到服务端存储的完整信任链

登录加密方案拆解:从算法选型到服务端存储的完整信任链 最近帮一个团队梳理某款聊天应用的登录认证链路重点看了加密算法相关的那部分实现。整个流程走下来最大的感受是不少登录加密方案本质上属于纸糊的——表面上看算法用了不少AES、RSA、SHA全套齐活但仔细一推敲参数设计、密钥管理、服务端校验都有漏洞。这篇文章就把整个分析思路和通用结论记录下来既是一次复盘也希望对做客户端或后端的同学有所帮助。无论你是在做IM、电商还是工具类应用登录加密的设计逻辑其实是共通的。这次分析适用的读者面很广客户端开发想知道传输层的密码该怎么处理后端开发想确认服务端存储和验证逻辑是否严谨安全测试同学可以参考一套可落地的排查方法。我会把原理、抓包观察、方案落地和常见漏洞串起来讲尽量让不搞安全的同学也能看懂该直接抄的配置和代码也会给出来。1. 登录加密到底在防什么1.1 从一次登录失败的表象说起开始分析之前我一直有个观点登录加密不是把密码变复杂就算完事它真正要解决的是客户端到服务端的这条链路上每一步的信息泄露和身份伪造问题。我们看的是一个真实项目里常见的现象——用户反馈登录总是被异地风险拦截但服务端查日志又找不到明显问题。顺着这个线索往下查发现请求里的密码字段并不是明文但也只是做了简单的可逆加密密钥还硬编码在安装包里。这个配置的安全性基本上等于把家门钥匙放在门口脚垫底下。这类问题的本质在于开发团队把加密和安全画了等号以为只要不是明文传输攻击者就没办法。实际上登录链路上要防守的对象不是一个而是三个完全不同的风险面每个风险面需要的技术手段都不一样。1.2 登录链路中的三个关键风险面第一个风险面是传输层也就是客户端到服务器之间的这一段网络通道。这段通道如果走的是明文HTTP或者在TLS配置上存在弱点攻击者通过抓包工具就能直接看到密码或者密文数据。更麻烦的是即使密码被加密过只要加密算法和密钥可预测攻击者照样可以在抓包后离线破解。传输层保护是整个登录安全的地基地基没打牢上面做再多应用层加密都是白费。第二个风险面是客户端本体也就是App或Web端所在的设备环境。攻击者可以通过反编译安装包、HOOK运行时函数、读取本地缓存等方式拿到加密算法逻辑、密钥、盐值这些敏感参数。在实际的客户端安全测试中最常遇到的情况就是有人把AES密钥直接写死在代码里自以为没人能看见。客户端一旦被攻破所有依赖应用层加密的保护都会瞬间失效因此客户端能做的主要是提高逆向成本和密钥存储的安全性。第三个风险面是服务端存储与校验环节。密码到达服务端后是明文入库还是做哈希数据库泄露后攻击者能否从密文反推出原始密码登录成功后签发的Token有没有过期时间退出登录后Token是否真的失效这些问题都归服务端管。很多项目加密传输做得不错结果数据库一泄露所有用户密码的哈希记录都整整齐齐躺在里面而且是无盐MD5一秒能破解几亿次这才是真正的灾难。1.3 一个重要的结论加密不等于认证分析做到后面我开始意识到登录加密方案设计的核心不是把什么字段加密而是如何证明这个请求确实来自合法用户并且没有被中途篡改。加密只是手段认证才是目的。密码经过哈希处理后传输目的是让获取到密文的人无法还原明文而添加时间戳、随机数、签名校验是为了防止攻击者把截获的请求原封不动地重放一遍这本质上属于认证问题。换句话说判断一个登录方案是否合格要同时问三个问题密文是否不可逆防泄露密文是否不可重用防重放请求是否不可伪造防冒充如果一个方案三个问题只能回答上一个那这个方案就不算完整。下面几个章节会围绕这三个问题把常见算法和实际方案逐个拆开分析。2. 登录场景的加密算法选型拆解2.1 哈希算法密码不能只做一次哈希哈希算法在登录场景里主要有两个用处一是客户端把密码通过哈希变换后再传输避免明文暴露二是服务端存储密码的哈希值即使数据库泄露也无法还原明文。哈希算法的特点是不可逆、定长输出、雪崩效应也就是说哪怕输入只改一个字符输出也会面目全非。很多人第一个想到的哈希算法是MD5或SHA-1但这里必须说清楚这俩算法已经不适合直接用于密码存储和登录场景了。原因在于算力提升和彩虹表攻击。彩虹表是一张预计算好的明文→哈希对照表攻击者拿到无盐MD5的哈希值后直接查表就能还原出常见弱密码。即便加了一层盐如果盐值固定不变也只要预计算一套就能反向破解所有账户。正确的做法是选择专为密码设计的高强度哈希算法常见的有bcrypt、scrypt和Argon2。这类算法的共同点是有意放慢计算速度通过提高单次哈希的计算成本来对抗暴力破解。以bcrypt为例它内部内置了加盐逻辑每次哈希生成的盐都是随机的并且支持成本因子cost factor调节。成本因子从4到31每增加1计算时间大约翻倍。实践环境里我习惯把成本因子设为10到12登录一次的耗时控制在几百毫秒内普通用户感知不到但攻击者暴力猜测一个密码的成本会被拉高几个数量级。2.2 对称加密字段加密与AES的坑对称加密在登录链路中通常用于加密传输过程中的敏感字段比如把密码或手机号用AES加密后再放入请求体。对称加密的特点是加密和解密用同一个密钥速度快适合大量数据。但正因为密钥只有一个它的一切安全性都押在密钥的保管上。在分析目标应用时我在客户端安装包里找到了一个硬编码的AES密钥而且工作模式是ECB。这里有两个致命问题。第一个问题是ECB模式下相同的明文会得到相同的密文密码字段如果都是8位纯数字攻击者看到两个相同的密文块就能推断出明文结构相似。第二个问题更直接——密钥写死在客户端任何人反编译一下安装包就能拿到相当于用公开的秘密做加密几乎没有安全性可言。相比之下AES的CBC模式需要用随机IV初始化向量来保证相同明文每次加密结果都不同GCM模式则额外附带认证标签能检测密文是否被篡改。如果要自己在应用层做对称加密优先选GCM模式并确保IV每次都是随机生成的。但必须强调应用层对称加密只能是辅助手段真正的安全底座还得靠TLS传输加密和合理的密钥管理体系任何想绕开HTTPS自己设计加密的思路都值得警惕。2.3 非对称加密密钥协商与签名验签非对称加密的特点是加密和解密使用不同的密钥公钥可以公开私钥必须保密。登录场景里它的典型应用有两种一是用服务端公钥加密密码或协商密钥二是用私钥对请求内容做签名服务端用公钥验证签名从而确认数据没被篡改且确实来自合法客户端。分析过程中我发现一种常见的错误用法客户端用服务端公钥加密密码看起来安全但攻击者只要也能拿到同一个公钥就能构造一个加密后的密码提交给服务端。这问题倒不大因为密码毕竟不可逆真正的风险在于服务端无法区分这条请求是从真实App发出的还是攻击者脚本构造的。所以单靠公钥加密不够通常需要配合客户端签名或设备指纹来增强来源可信度。更普遍的非对称加密应用场景是HTTPS背后的TLS握手。TLS通过非对称算法完成密钥交换和身份验证之后用对称算法加密业务数据。整个登录过程只要跑在完整的HTTPS链路上传输层的安全就基本有保障了。应用层再做一道加密更多时候是防协议重放、防撞库探测这类细化需求而不是替代HTTPS。3. 从流量视角还原一次登录请求3.1 观察到的登录请求结构安全分析里最常用的一个动作就是抓包——通过代理工具观察App发出的真实请求。这次分析中我首先看的是登录接口的完整URL、请求头和请求体结构。目标登录接口走的是HTTPS数据字段采用JSON格式请求体包含了用户名、密码密文、时间戳、设备编码等字段。这里有个容易忽略的点即使全链路都是HTTPS也不代表请求内容完全不可见。如果手机系统里安装了攻击者受信根证书或者App没有做证书校验代理工具一样能解密HTTPS流量看到明文。对分析者来说只要能在App和服务器之间架起代理就能观察到请求参数格式对开发者来说这意味着HTTPS加密看不到内容这个习惯性认知并不绝对成立证书固定机制才是在客户端侧补上这块短板的关键。从观测到的请求结构看开发团队显然是知道要保护密码的他们把密码用AES加密后放进了一个字段。问题在于十六进制的密文由于密钥硬编码在抓包工具里完全可以被解密还原出原始密码。换句话说加密环节形同虚设。3.2 密码字段的加密方式推断推断一个接口的密码加密方式通常有三个抓手看密文长度、看多次请求同一密码的密文是否一致、看是否存在IV等辅助参数。假设密码经过AES加密如果每次请求同样的密码得到的密文完全一样基本可以断定没有使用随机IV很可能就是ECB模式或CBC固定IV。在这次的例子里我拿测试账号连续用同一个密码登录几次发现密文完全一致再结合安装包里翻到的硬编码密钥和ECB标记结论就非常明确了。真正合规的客户端密码处理常见做法是加盐哈希或挑战应答不会让密文随着每次请求保持固定不变。即便应用层不做这些HTTPS传输层已经能解决明文被窃听的问题剩下的重点应该是防止数据库泄露后的密码还原而不是在客户端花大力气做可逆加密。3.3 Token机制与会话管理登录成功之后服务端返回的通常不是密码相关数据而是一个授权凭证。多数现代应用采用的是Token方案客户端把Token保存在本地之后的请求通过Header携带。对这个环节的分析主要看三件事Token是否有有效期、是否绑定设备或会话、退出登录时是否在服务端失效。我在这次分析里注意到一个细节服务端签发的Token有效期设置为30天而且没有续期和刷新机制。这意味着一旦Token被窃取攻击者可以在长达一个月的时间内持续使用受害者的会话这种风险要比密码本身的加密强度危险得多。常规做法是引入短期Access Token和长期Refresh Token的组合Access Token几分钟或几小时过期Refresh Token用于静默续期再配合设备绑定和异常风控把Token泄露后的危害窗口缩到最小。4. 一套相对可靠的登录加密方案怎么落地4.1 传输层与证书校验如果你问我登录加密方案第一步应该做什么答案永远只有一个先把全链路HTTPS处理好。这一步没做好应用层做再多加密都没有意义。HTTPS的关键点不仅是装了证书这么简单至少还要确认三件事。第一协议版本不能低于TLS 1.2TLS 1.0和1.1已经被官方标记为不安全的旧版本。第二证书链校验必须打开App不能忽略证书错误。第三对于安全要求较高的App应该考虑证书固定把服务端公钥证书的指纹预先写在客户端里拒绝任何伪造证书的中间人连接。配置上如果用的是常见后端框架如Spring Boot或Node.js的Express可以直接套用业界推荐的TLS配置组比如支持TLS 1.2以上、禁用弱加密套件、开启HSTS响应头。客户端方面iOS的URLSession和Android的OkHttp都有对应的证书固定设置方式花不了太多代码量但能显著提升抓包和中间人攻击的门槛。4.2 挑战应答模式的设计细节传输层搞定之后应用层可以做一道挑战应答来替代简单的密码密文上传。挑战应答的思路是服务端先下发一个随机挑战值客户端用真实密码加上这个挑战值一起做哈希再把哈希结果回传。这样即使攻击者截获了一次请求中的哈希值也无法重放到第二次因为挑战值每次不同。我在实战中更推荐的一种变形是客户端只保存用户输入的密码在内存里用随机盐和密码做一次高强度哈希然后把这个哈希值作为实际密码参与挑战应答服务端存储的也是这个哈希值的再次加盐哈希。好处是客户端内存里不保留原始密码明文数据库里也不会出现原始的bcrypt结果最大程度减少密码在多环节暴露的风险。当然挑战应答模式要求服务端能记住每次签发的挑战值并且校验一次性使用防重放攻击。实现上可以在服务端用一个短时缓存来存挑战值设置5分钟过期同时记录使用过的挑战值避免同一个挑战被反复提交。4.3 服务端存储与校验策略服务端存储是登录安全的最后一公里。无论客户端做了什么加密服务端都必须假设客户端是不可信的并且假设数据库存在被拖库的可能。因此用户密码在服务端的存储格式必须是慢哈希加随机盐比如bcrypt或Argon2。注册时调用bcrypt生成哈希登录时把用户传入的密码做同样的哈希运算再用内置的比较函数进行校验。校验逻辑的高频踩坑点有两个。一是用普通字符串比较来比对哈希值这样容易被时序攻击探测正确做法是用哈希比较函数比如Node.js的crypto.timingSafeEqual它会保证比较耗时与内容差异无关。二是登录失败的提示不区分用户不存在和密码错误防止攻击者通过枚举接口确认用户是否注册。实践中接口统一返回账号或密码错误注册接口加验证码和频率限制都是很有必要的细节。此外还有登录失败次数的限制策略。攻击者暴力破解是固定密码不停尝试因此我习惯在同一账号维度做登录频率控制连续失败5次后锁定账号15分钟同时记录IP和设备信息触发风控。这个策略看似简单却能挡住绝大多数自动化撞库。5. 常见问题与排查思路速查5.1 高频问题清单下面这张表记录了我在分析各类登录加密方案时遇到的问题排行也是这次分析过程的浓缩总结。问题现象常见原因风险等级推荐方案密码以明文存在于请求日志或数据库服务端直接记录明文极高日志脱敏存储改慢哈希密码密文每次请求都相同AES使用固定IV或ECB模式密钥硬编码极高改用随机IV密钥移到服务端或密钥系统管理数据库存的是无盐MD5哈希还在用老旧哈希方案极高迁移到bcrypt/Argon2增加成本因子逐步滚动重哈希登录成功后Token长期有效Token过期时间设置过长高Access Token短时有效配合Refresh Token刷新App忽略HTTPS证书错误开发阶段遗留或图省事高开启证书校验增加证书固定登录接口无失败次数限制缺少风控策略高账号锁定、IP限速、验证码请求里能明显看到设备号和固定盐值盐值被硬编码在客户端中使用随机盐客户端不参与存储逻辑5.2 我常用的排查顺序面对一个已有的登录系统我不会上来就翻代码而是按下面的顺序做快速排查。首先起一个代理环境把App的登录流量完整走一遍确认传输层是否被加密、请求体里有哪些字段、密码密文格式是什么。这一步能解决有没有做基本保护的问题。其次用同一个测试账号连续登录多次观察密码密文是否变化如果完全不变基本可以锁定对称加密的密钥或IV静态问题。然后试着反编译客户端安装包全局搜索密钥、盐值、加密逻辑的硬编码位置这一步速度很快很多问题当场就能找到实锤。最后到服务端抓存储结构和登录日志检查密码字段的存储形式以及Token的过期策略。这套排查顺序不是我凭空设计的而是从攻击者视角反向梳理出来的路径传输层能不能看到→应用层密文能不能还原→数据库泄露后能不能破解→会话凭证能不能长期复用。沿着这条链走一遍登录加密方案的成色基本就清楚了。5.3 两个容易被忽视的细节再讲两个这次分析过程中让我印象深刻的细节。第一个是服务端日志里的密码明文泄露。某个模块在调试阶段打印了完整请求体结果线上环境日志配置忘了改密码明文直接每天落盘几百万次。排查监控报警时才发现这个隐患。安全方案设计得再好一个日志配置就能全部击穿。所以我在所有项目的交付清单里都加了一条日志打印必须做全局脱敏过滤包含password、token、secret等关键字的字段一律打星号。第二个细节是旧密码的平滑迁移问题。很多现存系统的用户密码都是旧算法存储的比如无盐MD5全量改造成bcrypt需要用户重新登录一次才能拿到明文。实际操作中我见过一种比较稳妥的过渡策略用户登录时不直接改成bcrypt而是先把无盐MD5的哈希值升级为bcrypt具体做法是把旧哈希当作一个中间值再用bcrypt加盐重哈希登录时先验证bcrypt层再验证旧哈希层逐步把所有的旧记录都滚动到新算法。这样可以避免用户无感知下线同时最终达到全量安全存储的目标。整个分析做下来我对登录加密算法设计这件事的理解比之前清晰了很多它从来不是一个单点技术问题而是一条覆盖传输层、客户端、服务端的完整信任链。单一环节做得再强也架不住其他环节拖后腿。我个人在实际项目里最深的体会是先从最容易被忽略的服务端存储和日志入手再到传输层和客户端最后再回头审视整个流程——这条路走一遍能发现的问题往往比想象的要多得多。希望这篇记录对你分析自己的登录系统也有帮助。
返回列表