Node.js supports three KDFs:
constpassword='123';constsalt='123';constkeyLength=10;constdigest='sha256';constiterations=1000;constinfo='info';constpbkdf2=crypto.pbkdf2Sync(password,salt,iterations,keyLength,digest);constscrypt=crypto.scryptSync(password,salt,keyLength);consthkdf=crypto.hkdfSync(digest,password,salt,info,keyLength);console.log({ pbkdf2, scrypt, hkdf });Output:
{pbkdf2: <Buffer001c9597435c167a036d>,
scrypt: <Buffer928a6aad7d2b8cf84920>,
hkdf: ArrayBuffer {[Uint8Contents]: <bbb5514b6bc05734eae0>,byteLength: 10}}Inconsistencies:
- Unlike the two more established key derivation functions, HKDF produces an
ArrayBuffer, not a Node.js Buffer. In the async case, the callback even has the same signature and same argument names (err, derivedKey), but they derived key is not a Buffer when using HKDF. - The arguments are in a different order than the arguments to
pbkdf2.
And this can absolutely lead to security issues. The output is computationally indistinguishable from a random bit sequence and all three KDFs are collision-resistant and preimage-resistant, therefore, the comparison of outputs does not necessarily need to be timing-safe. While not advisable, it is possible (depending on the application) to compare the outputs in a timing-unsafe manner without compromising the password
a.toString('hex')===b.toString('hex')to compare the output of the KDF to a known correct value, e.g., as part of a login procedure. Now change a and b to ArrayBuffer each, and suddenly, the condition is always true, since
hkdf.toString('hex')==='[object ArrayBuffer]'When scrypt was added, we had a lengthy discussion about designing KDF APIs. Quoting #21766 (comment):
cryptography API design is very important to application security in a lot of ways that aren't always immediately obvious. Node.js cryptography API security is, therefore, emphatically a security issue that affects Node.js.
Is there anything we can do about it now? Or simply label it wontfix and close the issue?
Refs: #21766
Refs: #35093
Refs: #39453
Node.js supports three KDFs:
Output:
Inconsistencies:
ArrayBuffer, not a Node.jsBuffer. In the async case, the callback even has the same signature and same argument names(err, derivedKey), but they derived key is not aBufferwhen using HKDF.pbkdf2.And this can absolutely lead to security issues. The output is computationally indistinguishable from a random bit sequence and all three KDFs are collision-resistant and preimage-resistant, therefore, the comparison of outputs does not necessarily need to be timing-safe. While not advisable, it is possible (depending on the application) to compare the outputs in a timing-unsafe manner without compromising the password
to compare the output of the KDF to a known correct value, e.g., as part of a login procedure. Now change
aandbtoArrayBuffereach, and suddenly, the condition is always true, sinceWhen scrypt was added, we had a lengthy discussion about designing KDF APIs. Quoting #21766 (comment):
Is there anything we can do about it now? Or simply label it
wontfixand close the issue?Refs: #21766
Refs: #35093
Refs: #39453