验证nodeJS中的Identity Server 3/4哈希

时间:2018-04-13 08:33:05

标签: javascript c# node.js buffer identityserver4

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

  1. 我不知道为什么,但我完全得到了一个不同长度的缓冲区,尽管我遵循了相信的等价物。
  2. 作为异步任务运行的pbkdf2函数可能在迭代中具有不同的行为。
  3. pbkdf2函数可能正在实现sha256的不同版本,或者只是不是hmac。
  4. 我搞砸了缓冲区管理并在salt / subkey之间进行了调整。
  5. blockcopy
  6. 的上述施加中,此处的复制可能与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时,听起来像某种形式的不匹配编码或丢失字节的东西。 / p>

    以下是我的哈希和验证课程。

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

1 个答案:

答案 0 :(得分:2)

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

第一

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

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