【问题标题】:Verifying Identity Server 3/4 hash in nodeJS在 nodeJS 中验证 Identity Server 3/4 哈希
【发布时间】:2018-09-23 13:18:03
【问题描述】:

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

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

虽然请注意,我尝试验证的哈希直接取自 Identity Server 3 中的一个单独的应用程序,该应用程序是从 ABP boilerplate 开始的,但根据我自己的研究,我不相信他们实现了自定义哈希算法或已更改设置。我用来转换的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==');

【问题讨论】:

    标签: javascript c# node.js buffer identityserver4


    【解决方案1】:

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

    第一

    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);
    

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

    【讨论】:

    • 我认为对于缓冲区来说可能就像这样简单,要修复字节比较,如果可行,我会接受。
    • 按预期工作,非常感谢您的回答。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-17
    • 1970-01-01
    • 2016-08-31
    • 2018-07-23
    • 2019-04-18
    • 2017-06-26
    相关资源
    最近更新 更多