验证 nodeJS 中的身份服务器 3/4 哈希



我正在尝试编写一个模仿nodeJSidentity server 3验证功能的库,但我正在努力验证生成的缓冲区。

  1. 我不确定为什么,但我得到了一个完全不同的长度缓冲区,尽管遵循我认为是等效的缓冲区。
  2. 作为异步任务运行的pbkdf2函数在迭代中可能具有不同的行为。
  3. pbkdf2函数可能正在实现不同版本的sha256,或者只是不是hmac。
  4. 我已经搞砸了缓冲区管理并在盐/子键之间吐槽。
  5. 从这个意义上说,复制可能不像上述施加的blockcopy那样工作identity server 3

虽然请注意,我尝试验证的哈希值直接取自从ABP boilerplate启动的单独应用程序中的Identity Server 3,但根据我自己的研究,我不相信他们实现了自定义哈希算法或更改了设置。我用来转换的c#代码参考可以在这里找到:

https://github.com/aspnet/Identity/blob/rel/2.0.0/src/Microsoft.Extensions.Identity.Core/PasswordHasher.cs#L248

随着对身份服务器 2 等效项的一些进一步研究,它使用更普通的算法进行检查,我注意到人们报告说他们必须更改编码,但在测试中这仍然无法正常工作。

使用此处类中包含的 hashpassword 函数进行进一步测试表明,返回的缓冲区长度为 61,而在验证解码缓冲区的大小为 84 时,听起来像某种形式的不匹配编码或丢失字节的东西。

下面是我的散列和验证类。

import crypto from 'crypto';
import util from 'util';
const pbkdf2Async = util.promisify(crypto.pbkdf2);
export default class HashPasswordv3 {   
async verifyPassword(password, hashedPassword) {
let decodedBuffer = null;
if (hashedPassword) {
decodedBuffer = Buffer.from(hashedPassword, 'base64');
}
let iteration = 10000;
let key = decodedBuffer[0];
let saltLength = this.readNetworkByteOrder(decodedBuffer, 9);
if (saltLength < 128 / 8) {
return false;
}
let salt = new Buffer(saltLength);
// take the salt from the stored hash in the database.
// we effectively overwrite the bytes here from our random buffer.
decodedBuffer.copy(salt, 13, 0, saltLength);
console.log(salt);
let subkeyLength = hashedPassword.length - 13 - saltLength;
if (subkeyLength < 128 / 8) {
return false;
}
let expectedSubkey = new Buffer(subkeyLength);
decodedBuffer.copy(expectedSubkey, 0, 13 + saltLength, expectedSubkey.length);
console.log(expectedSubkey);
let acutalSubkey = await pbkdf2Async(password, salt, 10000, 32, 'sha256');
console.log(acutalSubkey);
console.log(this.areBuffersEqual(acutalSubkey, expectedSubkey));
}
async hashPassword(password) {
try {
// Create a salt with cryptographically secure method.
let salt = await crypto.randomBytes(16);
let subkey = await pbkdf2Async(password, salt, 10000, 32, 'sha256');
let outputBytes = new Buffer(13 + salt.length + subkey.length);
// Write in the format marker
outputBytes[0] = 0x01;
// Write out the byte order
this.writeNetworkByteOrder(outputBytes, 1, 1);
this.writeNetworkByteOrder(outputBytes, 5, 10000);
this.writeNetworkByteOrder(outputBytes, 9, salt.length);
salt.copy(outputBytes, 13, 0, 16);
subkey.copy(outputBytes, 13 + salt.length, 0, subkey.length);
console.log(outputBytes.toString('base64'));

} catch (e) {
console.log(e);
}
}
/**
* Writes the appropriate bytes into available slots
* @param buffer
* @param offset
* @param value
*/
writeNetworkByteOrder(buffer, offset, value) {
buffer[offset + 0] = value >> 0;
buffer[offset + 1] = value >> 8;
buffer[offset + 2] = value >> 16;
buffer[offset + 3] = value >> 24;
}
/**
* Reads the bytes back out using an offset.
* @param buffer
* @param offset
* @returns {number}
*/
readNetworkByteOrder(buffer, offset) {
return ((buffer[offset + 0]) << 24)
| ((buffer[offset + 1]) << 16)
| ((buffer[offset + 2]) << 8)
| ((buffer[offset + 3]));
}
/**
* Confirms if two byte arrays are equal.
* @param a
* @param b
* @returns {boolean}
*/
byteArraysEqual(a, b) {
if (Buffer.compare(a, b)) {
return true;
}
if (a == null || b == null || a.Length !== b.Length) {
return false;
}
let areSame = true;
for (let i = 0; i < a.Length; i++) {
areSame &= (a[i] === b[i]);
}
return areSame;
}
/**
* Checks to see if the buffers are equal when read out from uint.
* @param a
* @param b
*/
areBuffersEqual(bufA, bufB) {
let len = bufA.length;
if (len !== bufB.length) {
return false;
}
for (let i = 0; i < len; i++) {
if (bufA.readUInt8(i) !== bufB.readUInt8(i)) {
return false;
}
}
return true;
}
}

实现如下,可用于测试:

import identityHasher from '../IdentityServer3/HashPasswordv3';
const hasher = new identityHasher();
let result = await hasher.verifyPassword('test', 'AQAAAAEAACcQAAAAEGKKbVuUwa4Y6qIclGpTE95X6wSw0mdwhMjXMBpAnHrjrQlHngJCgeuTf52w91UruA==');

你的实现在逻辑上是正确的,但有几个小问题,所有这些都与算法实现无关:

第一

decodedBuffer.copy(salt, 13, 0, saltLength);

应该是

// copy data from "decodedBuffer" buffer to "salt" buffer,    
// from position 13, up to position 13 + saltLength of "decodedBuffer"
// to position 0 of "salt" buffer
decodedBuffer.copy(salt, 0, 13, 13 + saltLength);

仅仅因为它可以满足您的需求(从源数组中的位置 13 中提取盐),而您当前的版本执行完全不同的操作。我想你搞砸了这个函数的签名。

第二

let subkeyLength = hashedPassword.length - 13 - saltLength;

您已经在使用缓冲区,但使用hashedPassword的长度,即 base-64 字符串。这是不正确的(因为 base-64 字符串的长度和它表示的字节数组的长度不同),应该是:

let subkeyLength = decodedBuffer.length - 13 - saltLength;

第三

decodedBuffer.copy(expectedSubkey, 0, 13 + saltLength, expectedSubkey.length);

与第一个相同的故事,应该是:

decodedBuffer.copy(expectedSubkey, 0, 13 + saltLength, 13 + saltLength + expectedSubkey.length);

通过此更改,它将按预期工作。

相关内容

最新更新