NodeJS 中的 DIGEST-MD5 实现

DIGEST-MD5 implementation in NodeJS

我正在尝试使用 NodeJS 应用程序访问本地 XMPP 服务器 (Openfire)。
我想使用 DIGEST-MD5 机制(我知道它已被宣布过时)。

我发现这篇文章解释了该机制的工作原理: https://wiki.xmpp.org/web/SASL_and_DIGEST-MD5

然而,当实现文章中描述的机制时,我计算的响应是不正确的。
我已尽力找出我做错了什么,但我似乎无法弄清楚。 我确定我的其余节是正确的,只是响应不正确。

这是我计算响应的代码:

  var x = username + ':' + realm + ':' + pswd;
  var y = crypto.createHash('md5').update(x).digest();
  var a1 = y + ':' + nonce + ':' + cnonce + ':' + authzid;
  var a2 = 'AUTHENTICATE:' + digestUri;
  var ha1 = crypto.createHash('md5').update(a1).digest("hex");
  var ha2 = crypto.createHash('md5').update(a2).digest("hex");
  var kd = ha1 + ':' + nonce + ':00000001:' + cnonce + ':auth:' + ha2;
  var z = crypto.createHash('md5').update(kd).digest("hex");

其中 z 是最终响应。
如您所见,我正在使用加密库进行哈希处理。

上面文章中提到的例子如下:

username="rob",realm="cataclysm.cx",nonce="OA6MG9tEQGm2hh",cnonce="OA6MHXh6VqTrRk",nc=00000001,qop=auth,digesturi="xmpp/cataclysm.cx",response=d388dad90d4bbd760a152321f2143af7,charset=utf-8,authzid="rob@cataclysm.cx/myResource"

当我将所有这些值插入我自己的实现时(密码为 'secret'),我计算出的响应是:

5093acf6b3bc5687231539507cc2fb20

而不是预期的 d388dad90d4bbd760a152321f2143af7.
其他例子也没有给我正确的结果。

那么,我究竟做错了什么?
任何帮助将不胜感激!

计算响应时,第三行将缓冲区 y(包含 MD5 散列)与字符串连接起来,由此 y 使用 UTF-8 隐式转换为字符串。
但是,任意字节序列(例如散列)将被 UTF8 编码 s 破坏。 here。为防止这种情况,应将各个部分连接为缓冲区而不是字符串。
相反,其余的连接(在这一行和其他行中)并不重要,因为它们是真正的字符串:

var crypto = require('crypto');

var charset = 'utf-8';
var username = 'chris';
var realm = 'elwood.innosoft.com';
var nonce = 'OA6MG9tEQGm2hh';
var nc = '00000001';
var cnonce = 'OA6MHXh6VqTrRk';
var digestUri = 'imap/elwood.innosoft.com';
var response = 'd388dad90d4bbd760a152321f2143af7';
var qop = 'auth'
var pswd = 'secret';

var x = username + ':' + realm + ':' + pswd;
var y = crypto.createHash('md5').update(x).digest();
var a1 = Buffer.concat([y, Buffer.from(':' + nonce + ':' + cnonce, 'utf8')]); // + ':' + authzid; // Concat buffers instead of strings
var a2 = 'AUTHENTICATE:' + digestUri;
var ha1 = crypto.createHash('md5').update(a1).digest('hex');
var ha2 = crypto.createHash('md5').update(a2).digest('hex');
var kd = ha1 + ':' + nonce + ':00000001:' + cnonce + ':auth:' + ha2;
var z = crypto.createHash('md5').update(kd).digest('hex');

console.log(z); // d388dad90d4bbd760a152321f2143af7

请注意,代码段中的示例数据并非来自 website linked in the question, but from RFC2831 (Using Digest Authentication as a SASL Mechanism), chapter 4:

charset=utf-8,username="chris",realm="elwood.innosoft.com",nonce="OA6MG9tEQGm2hh",nc=00000001,cnonce="OA6MHXh6VqTrRk",digest-uri="imap/elwood.innosoft.com",response=d388dad90d4bbd760a152321f2143af7,qop=auth

代码returns中指定的结果response,证明了计算结果的正确性。


我认为 website linked in the question 的例子不一致。预期结果与 RFC 示例中的结果相同,尽管摘要的一些相关输入数据不同。
巧合匹配的可能性很小 (s. collision probability for MD5),因此链接网站的输入数据和预期结果很可能不属于一起。另外,那里没有指定密码。

应用的算法在chapter 2.1.2.1 Response-value中有详细描述。请注意,与链接网站上的示例相比,RFC 示例中未指定 authzid。根据2.1.2.1章节的描述,这种情况下':' + authzid直接省略即可。

正如问题中已经提到的,MD5 以及此处使用的算法已被弃用(s. 也 RFC6331)。