SSH服务器验证失败:交换哈希格式是否正确?
SSH客户端服务器验证失败调试建议
1. 逐字节校验交换哈希H的构造
严格对照RFC4253第8节的拼接顺序:V_C || V_S || I_C || I_S || K_S || e || f || K,每个字段必须用原始二进制格式,不能用字符串或十六进制转储:
V_C/V_S:SSH版本字符串的原始字节(比如SSH-2.0-OpenSSH_9.2p1),注意不要包含换行符,可通过Wireshark抓包确认服务器实际发送的版本内容I_C/I_S:客户端和服务器的SSH_MSG_KEXINITpayload完整二进制数据(不含消息头字节)K_S:服务器公钥blob的完整二进制(SSH_MSG_KEXDH_REPLY中的host key字段,包含ssh-ed25519的长度前缀、字符串内容、公钥数据)e/f:客户端和服务器的DH公钥,必须是大整数的**网络字节序(Big-Endian)**二进制,长度要匹配DH组模数长度(比如group14是2048位,对应256字节,不足时前面补0)K:DH共享密钥,RFC要求用大整数K的网络字节序二进制。注意OpenSSL的DH_compute_key返回的是原始二进制(网络字节序),需确认长度是否匹配模数长度
将拼接后生成的H的十六进制值,和OpenSSH客户端同场景下生成的H对比(可通过ssh -vvv日志或Wireshark抓包获取),定位拼接差异。
2. 验证ssh-ed25519公钥格式处理
服务器host key blob严格遵循RFC8709格式:
string "ssh-ed25519"
string ed25519公钥(32字节)
处理时注意:
- 从blob中提取公钥时,需跳过前4字节的字符串长度、
ssh-ed25519字符串本身,取剩下的32字节作为原始公钥数据 - 用OpenSSL的
ED25519_PUBKEY_new_bytestring导入公钥后,检查返回值是否非NULL,再用ED25519_PUBKEY_get_raw_public_key导出公钥,和blob中的公钥部分对比,确认导入正确
3. 检查OpenSSL签名验证函数的调用
服务器发送的signature字段格式为:
string "ssh-ed25519"
string 签名数据(64字节)
验证时需注意:
- 待验证数据是交换哈希H的原始32字节二进制,不能用十六进制字符串
- 正确的调用流程示例:
// 提取64字节签名数据(跳过signature blob中的长度前缀和"ssh-ed25519"字符串) unsigned char sig_data[64]; // ... 从数据包中提取sig_data的代码 ... // 导入服务器32字节原始公钥 ED25519_PUBKEY *pubkey = ED25519_PUBKEY_new_bytestring(server_raw_pubkey, 32); if (!pubkey) { /* 公钥导入失败,排查数据提取逻辑 */ } // 验证签名:H是SHA256生成的原始哈希,len为32 int verify_ret = ED25519_verify(H, 32, sig_data, pubkey); // 返回1为验证成功,0为失败,-1为函数调用错误 ED25519_PUBKEY_free(pubkey);
4. 抓包对比OpenSSH客户端的KEX流程
用OpenSSH客户端连接服务器并开启调试:ssh -vvv user@kali-ip,从日志中提取:
- 交换哈希H的十六进制值(日志中会输出
HMAC hash <hex>) - 客户端/服务器DH公钥e/f的十六进制值
- 服务器host key的十六进制值
将自己的客户端生成的这些值和OpenSSH的逐一对比,快速定位数据生成或传输环节的错误。
5. 编写独立测试程序隔离问题
写一个极简测试程序:
- 硬编码从抓包中获取的
V_C、V_S、I_C、I_S、K_S、e、f、K的原始二进制数据 - 按RFC4253拼接生成H并计算SHA256哈希
- 用服务器的签名数据和公钥做验证
如果测试程序验证成功,说明主程序中存在数据提取或拼接的逻辑错误;如果仍失败,说明对RFC规范的理解存在偏差。
内容的提问来源于stack exchange,提问作者Ruben Boero
相关产品推荐
相关产品推荐

