资讯动态

CHAP验证原理与iSCSI/RADIUS配置排错指南

发布时间:2026/10/27 22:12:58 来源:尧图企业网站定制
1. 从一次存储挂载卡死说起CHAP验证到底在验什么前阵子帮朋友排查一个实验室的共享存储问题现象很怪initiator 端能看到 target 广播出来的盘符配置里该填的 IP、IQN、端口全对但一登录就卡在login to portal等半天返回auth failure。换了三次密码重启了两次服务最后发现问题不在密码本身而在于他对CHAP验证的理解停留在“填个用户名密码”这一层——他以为 CHAP 是把密码发过去让对端比对实际上整条链路里根本没有明文密码在流动。CHAPChallenge Handshake Authentication Protocol挑战握手认证协议是一套质询-应答式的身份验证机制最早在点对点链路接入里被广泛采用后来被 iSCSI、RADIUS、部分嵌入式设备的登录鉴权大量沿用。它的核心价值不在于“传密码”而在于用一次性的随机数把长期密钥“藏”在哈希运算背后让旁观者即使抓到了完整报文也没法直接拿去复用。这篇文章我想讲清楚三件事CHAP 的报文和哈希到底怎么算、iSCSI 和 RADIUS 这两条最常见的落地路径怎么配、以及认证失败时该怎么一层层往下剥。适合刚接触存储认证或者正在给设备补鉴权的新手也适合被auth failure折磨过、想搞明白背后原理的老手。1.1 三段报文里流动的只有随机数和哈希CHAP 的交互非常克制标准流程只有三步。第一步认证方authenticator生成一个随机数装进 Challenge 报文发给被认证方这个随机数通常叫 challenge value长度一般 16 字节第二步被认证方拿到这个随机数把它和自己的密钥、报文标识符拼在一起做一次 MD5把算出来的 16 字节摘要装进 Response 报文回过去第三步认证方用自己手里存的同一份密钥做同样的计算两边摘要对得上就回 Success对不上就回 Failure。整个过程中密钥从来没有出现在任何一帧报文里链路上能看到的只有随机数和哈希值。这套设计的巧妙之处在于“随机数每次都不一样”。假设有人把 Challenge 和 Response 全都录下来他手里有 challenge也有对应的 response但下一次认证时认证方会重新生成一个全新的随机数旧的 response 立刻作废。这就把“截获即复用”的路堵死了。我自己第一次抓 iSCSI 的 CHAP 报文时还挺意外因为整个登录流程里能看到的敏感信息几乎为零Name 字段里只有用户名Value 字段全是看起来毫无规律的二进制。这也解释了为什么那位朋友改密码没用——如果 target 端存的密钥和 initiator 端不一致改多少次都是同样的失败因为问题出在两边的“共享密钥”没有对齐而不是密码“传错了”。1.2 密码为什么始终没有出现在链路上很多人第一次接触 CHAP 会本能地问不传密码对端怎么知道我密码对不对答案是“不需要知道”。认证方在本地保存一份密钥共享密钥被认证方也保存同一份两边各自用相同的输入做相同的运算比较输出即可。这有点像两个人各自在家做同一道菜不交换菜谱只交换成品尝一口就知道配方对不对。这种“预共享密钥”pre-shared secret模型带来一个直接后果密钥的同步是配置里最容易出错的一环。iSCSI 场景下target 端在 ACL 里存一份initiator 端在iscsid.conf里存一份两份必须逐字节一致包括大小写、特殊字符、前后空格。我见过最隐蔽的一次是复制粘贴时带进了一个不可见的尾随空格肉眼完全看不出来cat -A一显示才露馅。RADIUS 场景下更微妙服务器端往往需要拿到明文密码或者可逆加密后的密码才能参与计算这意味着后端用户库的设计必须为 CHAP 单独留一条路径不能只存 bcrypt 之类的单向哈希。这一点后面讲 RADIUS 时会展开。1.3 CHAP与PAP的分水岭一次性凭证与长期凭证把 CHAP 和 PAP 放在一起对比最能说明它到底解决了什么问题。PAP 的思路很直白客户端把用户名和密码明文塞进报文发过去服务端拿到后直接比对。实现简单排错也简单但代价是链路上任何能抓包的位置都能看到密码原文。CHAP 把“凭证”从长期密钥换成了一次性摘要即使被截获攻击者拿到的也只是针对某一次随机数的哈希。对比维度PAPCHAP密码是否上链路明文传输从不传输抗抓包复用无随机数一次性旧摘要作废服务端存储要求可直接存单向哈希需明文或可逆存储是否支持周期性重认证不支持支持可中途再次发起 Challenge实现复杂度低中涉及哈希与 ID 管理这张表里“服务端存储要求”这一行经常被忽略但它是选型时的关键。如果你的用户库只存了不可逆哈希那 CHAP 是跑不起来的因为服务端必须能拿到原始密钥去参与 MD5 运算。很多团队在迁移认证方式时栽在这上面以为改个配置开关就行结果发现后端根本取不出明文只能回头重做一套凭证存储方案。2. 把CHAP-MD5的计算过程摊开差一个字节就全错CHAP 的排错之所以让人抓狂是因为它的失败反馈极其吝啬——只有一个 Success 或 Failure不会告诉你“是 ID 错了”还是“是密钥拼错位置了”。所以真正想把它玩明白得把 Response 那 16 字节摘要的计算过程完整摊开看。CHAP 报文的通用格式是Code1 字节、Identifier1 字节、Length2 字节、Value-Size1 字节、Value变长、Name变长。Code 为 1 表示 Challenge2 表示 Response3 表示 Success4 表示 Failure。Response 报文的 Value 字段就是那段 MD5 摘要。2.1 Response哈希的拼接顺序CHAP-MD5 的摘要计算输入是三个部分按顺序拼接Identifier 单字节 共享密钥 Challenge 的 Value 值。注意这里没有任何分隔符纯粹是字节流的顺序拼接。这个细节极其重要因为一旦拼接顺序理解错或者误以为要加冒号、加换行算出来的结果就永远对不上。举个具体的例子来演示。假设 Identifier 是0x05共享密钥是字符串ChapSecret123Challenge 的 Value 是 16 字节随机数0x3A7F...。那么参与 MD5 的字节流就是05ChapSecret123的 ASCII 字节 16 字节 challenge。算出来的 16 字节摘要就是 Response 的 Value。我在第一次手写验证脚本时就是因为下意识在密钥后面加了个换行符导致怎么算都不匹配折腾了快一个小时才发现echo命令默认会带换行。这里有个容易被忽视的点Identifier 是单字节不是字符串“05”也不是十六进制的两个 ASCII 字符。有些自研实现会把它当整数转成十进制字符串拼进去结果自然全错。如果你要自己写验证工具务必用字节数组直接拼接别经过任何字符串格式化。2.2 Identifier字段在重传与并发里的真实作用Identifier 看起来只是个小字段但它在协议里承担了两个职责。第一是匹配请求与应答Response 报文里的 Identifier 必须与对应 Challenge 的 Identifier 完全一致认证方靠它把应答和之前发出的挑战对应起来。第二是参与哈希计算所以它不是纯粹的“流水号”而是安全输入的一部分。第二点经常被低估。因为 Identifier 参与了 MD5 计算所以如果中间设备比如某些负载均衡或协议转换层擅自改写 Identifier认证必然失败。我遇到过一例链路中间串了一台做协议转发的设备它为了“规范化”把 Identifier 统一改成了固定值表面上看报文还能走通但哈希对不上认证一直失败。用抓包对比两端看到的 Identifier 才定位到问题。另一个实践细节是周期性重认证。CHAP 允许认证方在会话建立之后隔一段时间再发一次 Challenge强制重新验证。这时 Identifier 会递增或在 0-255 内循环被认证方需要用新的 Identifier 重新计算。如果你的实现把 Identifier 写死成某个常量首次认证可能成功重认证阶段就会挂掉——这类问题往往在会话跑了几十分钟后才暴露排查难度更高。2.3 共享密钥的存储位置与长度边界共享密钥放哪里、多长才够是两个很实际的问题。存储位置上iSCSI 的 initiator 把它放在/etc/iscsi/iscsid.conf里target 端放在 ACL 的 auth 配置里RADIUS 场景放在用户数据库或 NAS 的本地配置里。所有这些位置的共同要求是权限收紧因为拿到密钥就等于拿到了认证能力。实测中我一般会把相关配置文件的权限设成 600属主设成服务运行用户避免同机其他账号顺手读到。长度方面iSCSI 的规范里建议 CHAP 密钥至少 12 字节但很多实现允许更短甚至 8 字节也能跑。这里要区分“能跑”和“该用”12 字节以下的密钥在离线爆破面前几乎没有抵抗力尤其当摘要算法是 MD5 时。我的建议是直接上 16 字节以上的随机字符串混合大小写、数字和符号。但要注意部分老设备的配置界面会限制特殊字符输入$、!这类符号时可能被 shell 或配置解析器吃掉稳妥的做法是先在小环境验证一遍确认设备能正确保存再批量推。还有一个坑是密钥里的特殊字符在配置文件中的转义。iscsid.conf是 INI 风格值里出现#或;可能被当成注释起始出现空格可能被截断。我一般会避免在自动化脚本里生成带这些字符的密钥宁可牺牲一点熵换取配置的确定性。3. 两条最常落地的配置路径iSCSI与RADIUS理论讲完落到实际操作。CHAP 最常见的两个战场一个是块存储的 iSCSI 登录一个是集中认证的 RADIUS。两者的报文形态不同但底层计算逻辑完全一致把一边搞透另一边基本可以平移。3.1 iSCSI initiator与target的双向CHAP配置iSCSI 的 CHAP 配置分单向和双向。单向通常指 target 验证 initiator双向则两边互验。先看 target 端以常见的 LIO 目标框架为例核心是在 ACL 上挂 auth 配置targetcli / backstores/block create namedisk1 dev/dev/sdb / iscsi/ create iqn.2024-01.lab:target1 / iscsi/iqn.2024-01.lab:target1/tpgt1/luns create /backstores/block/disk1 / iscsi/iqn.2024-01.lab:target1/tpgt1/acls create iqn.2024-01.lab:initiator1 / iscsi/iqn.2024-01.lab:target1/tpgt1/acls/iqn.2024-01.lab:initiator1 set auth useridchapuser / iscsi/iqn.2024-01.lab:target1/tpgt1/acls/iqn.2024-01.lab:initiator1 set auth passwordChapSecret123456userid是给 initiator 看的用户名password是共享密钥。注意auth userid和auth password是成对出现的缺一个都不会生效。配置完用ls逐层确认别嫌麻烦我见过太多“命令敲了但路径写错一层”的情况。initiator 端在/etc/iscsi/iscsid.conf里配置node.session.auth.authmethod CHAP node.session.auth.username chapuser node.session.auth.password ChapSecret123456 node.session.auth.username_in chapuser_in node.session.auth.password_in ChapSecret123456_in前四行是 initiator 向 target 证明自己后两行是双向认证时 target 向 initiator 证明自己。如果只需要单向后两行可以留空或注释掉。这里最典型的错位是target 端只配了单向验证 initiatorinitiator 端却把username_in/password_in也填上了结果 target 根本不发起反向 Challengeinitiator 却一直等登录超时。反过来也一样target 端开了双向initiator 没配_in字段认证会在第二阶段失败。判断方法很简单看失败发生在登录的哪个阶段日志里通常会有authorization failure的先后顺序。改完配置后记得重启iscsid服务并且清理旧的会话记录否则 initiator 可能拿缓存的旧配置去重连。iscsiadm -m session -P 3能看到当前会话的认证状态和协商参数是我排错时第一个会敲的命令。3.2 RADIUS侧CHAP-Password属性的处理逻辑RADIUS 场景下的 CHAP 有个非常关键的细节很多人配了一辈子都没搞清楚Access-Request 报文里放的不是明文密码而是 CHAP-Password 属性属性号 3它的内容是1 字节的 CHAP Identifier 16 字节的 CHAP Response。也就是说NAS 设备认证方先在本地生成 challenge让客户端算完哈希再把 ID 和哈希一起塞进 RADIUS 请求发给服务器。服务器怎么验证它必须拿到该用户的明文密码用同样的方式重算。这里就出现了前面提到的存储矛盾如果后端只存了 bcrypt 哈希服务器取不出明文CHAP 认证直接无法完成。解决办法通常是在用户库里单独维护一份可逆加密比如 AES的密码副本仅用于 CHAP 计算权限严格限制。更隐蔽的一点是 challenge 的来源。RFC 2865 规定如果 Access-Request 里带了 CHAP-Challenge 属性属性号 60服务器就用它作为 challenge如果没带服务器就用该报文头部的16 字节 Request Authenticator作为 challenge。这两种情况算出来的结果完全不同。我遇到过一次对接故障NAS 厂商的实现不发送 CHAP-Challenge 属性而服务器侧的测试脚本默认按“有 CHAP-Challenge”去算两边永远对不上。抓包确认属性列表后才发现问题所在。3.3 抓包比对怎么确认自己配的是对的CHAP 出问题时光看日志基本没用必须抓包。iSCSI 的流量过滤可以用tcpdump -i eth0 -s 0 -w iscsi.pcap port 3260然后用协议分析工具打开逐帧看 Login 阶段的 CHAP 交互。重点看三处Challenge 报文里的 Identifier 和 Value 长度、Response 报文里的 Identifier 是否与 Challenge 一致、以及 Success/Failure 的返回时机。如果手头没有图形化工具也可以用命令行快速验证哈希。把抓到的 Identifier、密钥、challenge 拼起来做一次 MD5和 Response 的 Value 比对# 假设 identifier05, secretChapSecret123456, challenge 存为二进制文件 printf \x05 /tmp/in.bin printf ChapSecret123456 /tmp/in.bin cat /tmp/challenge.bin /tmp/in.bin md5sum /tmp/in.bin算出来的摘要和抓包里的 Response Value 一致说明你理解的拼接方式是对的问题在配置同步不一致说明要么密钥不对要么拼接顺序理解有误。这个“手工复算”的动作看起来笨但它能把“配置问题”和“实现理解问题”彻底分开是排查 CHAP 最有效的一步。4. 认证失败的排查链路从现象倒推配置CHAP 失败的表现形式高度趋同但成因五花八门。我一般按“先分阶段、再分端点、最后抠字节”的顺序往下剥避免一上来就瞎改配置。4.1 密码正确却哈希不匹配的三种成因第一种是密钥不一致但人眼看不出来。典型如尾随空格、全角半角混用、密码里含#被当注释截断。用cat -A或者十六进制查看工具把两端的密钥打出来逐字节比对是最快的定位方式。第二种是编码问题。如果密钥里包含非 ASCII 字符不同实现可能按 UTF-8、GBK 或 Latin-1 解释字节流就不同了。最稳妥的做法是 CHAP 密钥只用 ASCII 可见字符别用中文或特殊符号。第三种是实现层面的拼接差异。少数设备在计算时会多做一步比如把 Name 字段也拼进哈希或者在密钥前后加填充。这类问题没法靠猜只能抓包后手工复算反推出它的实际拼接规则再调整另一端的配置去适配。理论上这是“不合规实现”但现实里老设备就是这么干的你只能迁就它。4.2 单向与双向CHAP的典型错位单向和双向的错位表现往往是“第一阶段成功第二阶段失败”或者“登录直接超时”。判断依据是看认证方有没有发出反向 Challenge。如果 target 端的 ACL 里只配了auth userid/password它就是单向认证方不会向 initiator 发起挑战此时 initiator 的_in字段配了也白配甚至可能因为等待反向挑战而超时。反过来如果 target 端启用了双向某些实现里需要额外开关initiator 必须配齐username_in/password_in且这两个值对应的是target 自己的凭据不是 initiator 的。这一层关系经常被写反把username和username_in填成同一个值结果单向能过、双向挂掉。我的习惯是命名上就区分开比如init_user和target_user配置里一眼能看出方向。4.3 会话重认证阶段才报错的隐藏问题有些故障特别阴会话建起来能正常读写跑了几十分钟突然断开日志里是认证失败。这通常和周期性重认证有关。CHAP 支持在会话中途重新发起挑战认证方会按配置的间隔iSCSI 里常见的是node.session.auth.chap_algs相关配置和重认证间隔参数再次质询。如果被认证方的实现有状态残留比如把 Identifier 缓存成了旧值或者重认证时用了过期的密钥就会在这个阶段失败。排查这类问题重点看两处一是重认证间隔是不是设得太短有些环境设成 60 秒高频重认证反而放大了实现缺陷二是会话日志里失败前的最后几次 Challenge/Response 的 Identifier 是否连续。如果 Identifier 出现跳变或重复基本可以锁定是实现的状态管理有问题这时候升级固件或者换实现往往比调配置更有效。5. CHAP的安全边界它能挡住谁挡不住谁CHAP 不是银弹用之前得清楚它的能力边界。它解决的问题是“不暴露长期密钥的前提下完成身份验证”但它不解决机密性、不解决中间人、也不解决弱密码。5.1 离线字典攻击的完整链条与成本攻击者抓到一个 Challenge 和一个 Response手里就有了 MD5 的输入和输出。虽然他不知道密钥但他可以拿字典里的候选密码逐个去算MD5(ID 候选密码 challenge)算出来和 Response 比对对上了就说明候选密码正确。这就是典型的离线字典攻击。这条链的成本完全取决于密钥强度。如果密钥是 8 位纯小写字母现代硬件几分钟就能跑完如果是 16 位混合字符的随机串成本会高到不划算。所以 CHAP 的安全性有很大一部分是“密钥强度”撑起来的而不是协议本身。我见过不少环境用12345678这种密钥等于把 CHAP 的优势全抵消了。另外要注意MD5 本身的碰撞弱点在这里影响不大因为攻击者做的是“给定输入求输出”的穷举不是构造碰撞真正的软肋是密钥熵不足。5.2 中继与重放新鲜随机数并不总是够用随机数一次性确实挡住了“原样重放”但挡不住“实时中继”。如果攻击者能位于链路中间他可以把认证方发来的 Challenge 原样转发给真正的被认证方拿到 Response 再转回去全程不破解任何密钥就完成了认证。这种攻击的前提是能介入链路属于中间人范畴CHAP 本身没有机制去检测。这也是为什么 CHAP 通常只作为“身份验证”的一环而不是“安全通道”的全部。它验证的是“对方知道密钥”但不保证“对方就是你以为的那台设备”也不保证后续数据传输不被窃听。真正需要保护数据的场景得在 CHAP 之上再叠加加密通道让整条链路的内容都不可读。5.3 在今天的架构里怎么用才不算裸奔我的实践原则有这么几条。第一密钥强度优先长度不低于 16 字节字符集尽量宽且定期轮换轮换时要做好两端同步最好有配置管理工具统一推送别靠手工改。第二开启失败次数限制和延迟防止在线暴力尝试很多实现支持“连续失败 N 次后锁定一段时间”这个开关一定要打开。第三把 CHAP 放在受控网络里用跨网段或跨机房时叠加加密通道别让认证报文裸奔在不可信链路上。第四能上更现代的认证方式就上比如基于证书的双向认证CHAP 更多是用在老设备兼容和轻量场景。最后分享一个我自己踩过的小坑有次给一批设备批量下发 CHAP 配置脚本里用sed替换密码字段结果密码里带了一个/把sed的分隔符搞乱了替换后配置文件里多出一截乱码肉眼扫过去还挺像正常配置。后来我改成用 Python 做字符串替换并且在推送后加了一步“读回校验”把配置读出来和预期值逐字节比对才算把这个隐患堵住。批量操作 CHAP 配置时读回校验这一步真的别省它比事后抓包排错便宜太多了。

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

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

免费获取报价 →
↑