一个客户端用共享密钥认证成功,另一个却拒绝,而两边的字符看上去一模一样。它们很可能就是同样的字符;区别在于文件对它们套用了哪条解码规则。引号内的字符串按字面处理,以 0x 开头的记号按十六进制读取,以 0s 开头的记号按 Base64 读取。三种写法里只有一种表达了你想要的意思,所以修复通常是一次转换,而不是换一个更好的密码。

症状指向编码,而不是密码算法

当两端持有的字节不同时,协商依然成功,随后的证明却失败了。日志这时报的是完整性或 HMAC 失败,而不是算法不匹配,因为提案已经选定,只有认证环节对不上。那一行从不提及密钥的形态。

阶段就是线索。失败出现在提案选定之后,说明双方除了密钥材料之外都已谈妥;而「没有选定提案」属于策略或算法套件的问题。密码看起来正确、完整性校验却失败,正是编码不一致暴露出来的样子。

同一个密钥,文件有三种读法

对四字节的值 key1,十六进制和 Base64 是同一串字节的两种重新编码,只有开头的字符告诉解析器它拿到的是哪一种。swanctl 的凭据遵循同样的约定,因此在两个接口之间搬动时,只有前缀一起走,值才保持原意。

10.20.0.5 %any : PSK "key1"
10.20.0.5 %any : PSK 0x6b657931
10.20.0.5 %any : PSK 0sa2V5MQ==

那个块里有两个细节最费时间。十六进制数字不区分大小写,所以 0x6b657931 和 0x6B657931 是同一个值;但字符数为奇数时不会被拒绝,而是被悄悄读错,切口之后的每个字节都跟着错位。Base64 的填充属于值本身,所以漏掉结尾 = 符号的字符串会解码成别的字节,而不是大声报错。

已经解码过一次的配置已经把值转换过了,再解码一次就会得到不同的字节,认证失败看起来就像密码错了。对任何有歧义的内容,加引号是安全的方向,因为即使内容以编码标记开头,它仍然按字面处理。

Base64 文本从哪里来

配置导出和厂商门户常常直接给已经编码好的密钥,通常放在一个从来不是用来手输的数据字段里。要么把这个值拆开,把解码后的文本放进引号里保存;要么保留编码,并在前面加上标记。两边各做一半正是出问题的地方。两条路都只有在整个值完整复制下来时才成立。

两个方向各是一条命令,值得把它们留在工单里,作为产生这个存储值的准确操作。按这个顺序执行:

printf '%s' 'a2V5MQ==' | base64 -d
printf '%s' 'key1' | base64

把编码后的文本当成明文粘贴,是代价最高的错误。文件按写下的字符原样做哈希,派生出的密钥比对方算出来的大约长三分之一,而且每次尝试都以同样的方式失败,没有任何警告,因为解析器只是照着给它的规则执行。

确认每个对端实际使用的是哪些字节

先读守护进程对连接的看法,再看认证结果,而不是从错误字符串里猜原因。比较存储长度是很快的合理性检查,因为编码后的文本比它代表的字节要长。然后确认匹配的是哪一行身份,因为正确的密钥放在错误的对端下面,失败起来和错误的密钥一模一样。行的顺序决定这件事:先匹配到的胜出,所以靠近顶部的一条宽泛的 %any 行,会悄悄替换掉你刚改过的那条具体记录。

文件正确之后要重新加载,而不是重启。ipsec rereadsecrets 会刷新 IKE 守护进程的那份副本,而 swanctl 的配置用它自己的命令加载凭据。重启会丢掉所有已建立的关联,也毁掉你正想做的比较。

这些检查都不需要暴露密钥。一个长度、一个身份选择器和一行位置,就能解释这类失败的大多数情况,用不着任何人去做十六进制转储。

改动文件,但不要掩盖结果

把文件限制在守护进程的账户下,并让在用密钥远离工单、聊天和截图,因为粘贴出来的密钥是泄露,不是诊断。它还会比产生它的那次事故活得更久。

从你能控制配置的客户端测试,确认认证成功,再确认隧道确实在传输流量。重新加载修好了握手、路由却仍然不通,那是另一个问题,而密钥一停止变动它就会显形。

相关文章