简体   繁体   English

使用nodejs生成ASP.NET webpages_membership密码

[英]Generate ASP.NET webpages_membership Password using nodejs

There is existing system is working on C#, but we have decided to move some modules of c# website into nodejs, so i will be able to login through c# and nodejs too. 现有系统正在研究C#,但我们决定将c#网站的一些模块移动到nodejs中,所以我也可以通过c#和nodejs登录。 using c# registration it generated some random password using some existing library of asp.net it will store password into "webpages_membership" table. 使用c#注册它使用一些现有的asp.net库生成一些随机密码,它会将密码存储到“webpages_membership”表中。

C# generated random password : "JWvppSSfnzOQ+uMd+BORpT/8aQorC8y05Bjbo/8w/9b/eiG4WLzUFRQSSiKZqo3C" is hashed password for "123456" string. C#生成的随机密码: “JWvppSSfnzOQ + uMd + BORpT / 8aQorC8y05Bjbo / 8w / 9b / eiG4WLzUFRQSSiKZqo3C”是“123456”字符串的哈希密码。

so now there is some other module which will be now in nodejs but rest of the things will be in c# only. 所以现在有一些其他模块现在将在nodejs中,但其余部分将只在c#中。 so login now i have to login through node. 所以现在登录我必须通过节点登录。

I am trying to compare c# generated password in nodejs using following library https://www.npmjs.com/package/aspnet-identity-pw 我试图使用以下库比较nodejs中的c#生成的密码https://www.npmjs.com/package/aspnet-identity-pw

but it returns False. 但它返回False。

c# generated password for "123456" => "JWvppSSfnzOQ+uMd+BORpT/8aQorC8y05Bjbo/8w/9b/eiG4WLzUFRQSSiKZqo3C" c#生成的密码为“123456”=>“JWvppSSfnzOQ + uMd + BORpT / 8aQorC8y05Bjbo / 8w / 9b / eiG4WLzUFRQSSiKZqo3C”

Please help me to achieve same in nodejs. 请帮我在nodejs中实现相同功能。

nodejscode nodejscode

var passwordHasher = require('aspnet-identity-pw');

var hashedPassword = passwordHasher.hashPassword('123456');
console.log(hashedPassword);

var isValid = passwordHasher.validatePassword('JWvppSSfnzOQ+uMd+BORpT/8aQorC8y05Bjbo/8w/9b/eiG4WLzUFRQSSiKZqo3C', hashedPassword);
console.log("Result:"+isValid);
//Return False

I tried same thing in php using below code which is working fine, using below php code i am able to compare c# generated password and also able to generate new password from php and able to login from C#. 我在php中使用下面的代码尝试了同样的事情,这是正常工作,使用下面的PHP代码,我能够比较c#生成的密码,并能够从PHP生成新密码,并能够从C#登录。

Working PHPcode for reference: 使用PHPcode作为参考:

<?php

/*
 * Author  : Mr. Juned Ansari
 * Date    : 15/02/2017 
 * Purpose : It Handles Login Encryption And Decryption Related Activities
 */

class MembershipModel {

    function bytearraysequal($source, $target) {
        if ($source == null || $target == null || (strlen($source) != strlen($target)))
            return false;
        for ($ctr = 0; $ctr < strlen($target); $ctr++) {
            if ($target[$ctr] != $source[$ctr])
                return false;
        }
        return true;
    }
    //This Function is Used to verifypassword
    function verifypassword($hashedPassword, $password) {

        $PBKDF2IterCount = 1000; // default for Rfc2898DeriveBytes
        $PBKDF2SubkeyLength = 32; // 256 bits       
        $SaltSize = 16; // 128 bits


        if ($hashedPassword == null) {
            return false;
            //show_error("hashedPassword is null");
        }
        if ($password == null) {
            return false;
            //show_error("Password is null");
        }

        $hashedPasswordBytes = base64_decode($hashedPassword);

        if (strlen($hashedPasswordBytes) != 48) {
            return false;
        }

        $salt = substr($hashedPasswordBytes, 0, $SaltSize);

        $storedSubkey = substr($hashedPasswordBytes, $SaltSize, $PBKDF2SubkeyLength);

        $generatedSubkey = $this->encript('sha1', $password, $salt, $PBKDF2IterCount, $PBKDF2SubkeyLength, true);

        return $this->bytearraysequal($storedSubkey, $generatedSubkey);
    }

    function encript($algorithm, $password, $salt, $count, $key_length, $raw_output = false) {
        $algorithm = strtolower($algorithm);
        if (!in_array($algorithm, hash_algos(), true))
            return false;
        //show_error('PBKDF2 ERROR: Invalid hash algorithm.');
        if ($count <= 0 || $key_length <= 0)
            return false;
        //show_error('PBKDF2 ERROR: Invalid parameters.');

        $hash_length = strlen(hash($algorithm, "", true));
        $block_count = ceil($key_length / $hash_length);

        $output = "";
        for ($i = 1; $i <= $block_count; $i++) {

            $last = $salt . pack("N", $i);

            $last = $xorsum = hash_hmac($algorithm, $last, $password, true);

            for ($j = 1; $j < $count; $j++) {
                $xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
            }
            $output .= $xorsum;
        }
        return substr($output, 0, $key_length);
    }

}
---------------------------------
echo MembershipModel::verifypassword("JWvppSSfnzOQ+uMd+BORpT/8aQorC8y05Bjbo/8w/9b/eiG4WLzUFRQSSiKZqo3C","123456");
//Returns True for every c# generated password

$salt = openssl_random_pseudo_bytes(16);
$dev = MembershipModel::encript('sha1', $Password, $salt, 1000, 32, true);
$HashedPassword = base64_encode($salt.$dev);

You can port your working PHP code to Node.js using the built-in crypto module. 您可以使用内置的crypto模块将工作的PHP代码移植到Node.js。

Creating a hash: 创建哈希:

In your PHP MembershipModel::encript method you're using a PBKDF2 implementation to create a key. 在PHP MembershipModel::encript方法中,您使用PBKDF2实现来创建密钥。 We can create the same key in Node.js with crypto.pbkdf2Sync . 我们可以使用crypto.pbkdf2Sync在Node.js中创建相同的密钥。

function kdf(password, salt, count=1000, keyLen=32, hash='sha1') {
    return crypto.pbkdf2Sync(password, salt, count, keyLen, hash);
}

Now we can write a function that uses kdf and returns the salt and key, base64 encoded - the same format as your PHP and C# code. 现在我们可以编写一个使用kdf的函数并返回salt和key,base64编码 - 与PHP和C#代码的格式相同。

function hashPassword(password) {
    var salt = crypto.randomBytes(16);
    var key = kdf(password, salt, 1000, 32, 'sha1');
    var sk = Buffer.concat([salt, key]);
    return sk.toString('base64');
}

For the salt I've used crypto.randomBytes which is a CSPRNG function (creates secure pseudo-random data). 对于salt,我使用了crypto.randomBytes这是一个CSPRNG函数(创建安全的伪随机数据)。

Checking a hash: 检查哈希:

In your PHP MembershipModel::verifypassword method you're using the received salt to craete a key with PBKDF2, then compare the new key with the received key. 在您的PHP MembershipModel::verifypassword方法中,您使用收到的盐来使用PBKDF2来获取密钥,然后将新密钥与收到的密钥进行比较。 A Node.js equivalent: 一个Node.js等价物:

function verifyPassword(hashedPassword, password) {
    var data = new Buffer(hashedPassword, 'base64');
    var salt = data.slice(0, 16);
    var key = data.slice(16);
    var hash = kdf(password, salt);
    return crypto.timingSafeEqual(key, hash);
}

I'm using crypto.timingSafeEqual to compare the keys; 我正在使用crypto.timingSafeEqual来比较密钥; it performs constant time comparison. 它执行恒定的时间比较。

Testing: 测试:

var password = "123456";
var hash = hashPassword(password);
console.log(hash);
//umzeh4aAeD1Ee6z4oN/BS9f2s2GQ7gswtbrguEr2C32c8XK99UjI8LkgYapbX8/N
console.log(verifyPassword(hash, password));
//true

hash = "JWvppSSfnzOQ+uMd+BORpT/8aQorC8y05Bjbo/8w/9b/eiG4WLzUFRQSSiKZqo3C";
console.log(verifyPassword(hash, password));
//true

We can see that the hashPassword function produces hashes that are compatible with your PHP code and verifyPassword can verify them successfully. 我们可以看到hashPassword函数生成与PHP代码兼容的哈希值, verifyPassword可以成功验证它们。


Some notes about your PHP code: 关于PHP代码的一些注意事项:

I assume that the MembershipModel::bytearraysequal method is supposed to use a constant time algorithm, but it returns false with the first occurrence of unequal characters. 我假设MembershipModel::bytearraysequal方法应该使用常量时间算法,但它返回false,第一次出现不相等的字符。 A better implementation, using bitwise operators: 使用按位运算符的更好实现:

function bytearraysEqual(string $hash1, string $hash2): bool {
    $result = 0;
    for ($i=0; $i<strlen($hash1) && $i<strlen($hash2); $i++) {
        $result |= ord($hash1[$i]) ^ ord($hash2[$i]);
    }
    return $result === 0 && strlen($hash1) === strlen($hash2);
}

This function checks all the characters, and the length of strings. 此函数检查所有字符和字符串的长度。 However, it's best to use the built-in hash_equals function (requires PHP 5.6 or higher). 但是,最好使用内置的hash_equals函数(需要PHP 5.6或更高版本)。 Similarly, you can use openssl_pbkdf2 to create the key (PHP 5.5 or higher). 同样,您可以使用openssl_pbkdf2来创建密钥(PHP 5.5或更高版本)。

We can improve your MembershipModel class using those functions, and type hinting (PHP 7) which doesn't require null checks, and produces cleaner code. 我们可以使用这些函数改进MembershipModel类,并输入不需要空检查的提示(PHP 7),并生成更清晰的代码。

class MembershipModel {

    const PBKDF2_ALGORITHM = "SHA1"; 
    const PBKDF2_ITERATIONS = 1000; 
    const KEY_LENGTH = 32;    
    const SALT_LENGTH = 16;

    function hashPassword(string $password): string {
        $salt = openssl_random_pseudo_bytes(16);
        $key = MembershipModel::kdf($password, $salt);
        return base64_encode($salt.$key);
    }

    function verifyPassword(string $hashedPassword, string $password): bool {
        $hashedPasswordBytes = base64_decode($hashedPassword);
        $salt = substr($hashedPasswordBytes, 0, MembershipModel::SALT_LENGTH);
        $key1 = substr($hashedPasswordBytes, MembershipModel::SALT_LENGTH);
        $key2 = MembershipModel::kdf($password, $salt);
        return hash_equals($key1, $key2);
    }

    private function kdf(string $password, string $salt): string {
        $key = hash_pbkdf2(
            MembershipModel::PBKDF2_ALGORITHM, $password, $salt, 
            MembershipModel::PBKDF2_ITERATIONS, MembershipModel::KEY_LENGTH,
            true
        );
        return $key;
    }
}

Your key derivation scheme seems secure enough: PBKDF2 with a random salt and long key. 您的密钥派生方案似乎足够安全:PBKDF2具有随机盐和长密钥。 You could increase the number of iterations for better security, but that would cost time and performance. 您可以增加迭代次数以获得更好的安全性,但这会花费时间和性能。

The implementation however may have bugs (like the one I found in MembershipModel::bytearraysequal ) that could reduce the security of your code. 但是,实现可能存在错误(如我在MembershipModel::bytearraysequal找到的错误),这可能会降低代码的安全性。 It's best to use built-in functions, if that is possible. 如果可能的话,最好使用内置函数。


Update 更新

After studying the source code of aspnet-identity-pw , I discovered that it uses crypto internally. 在研究了aspnet-identity-pw的源代码之后,我发现它在内部使用了crypto The key is created by crypto.pbkdf2 with 16 byte salt and 1000 iterations. 密钥由crypto.pbkdf2创建,具有16字节盐和1000次迭代。 The only difference is that it creates a 49 byte hash, with a zero byte at the front. 唯一的区别是它创建了一个49字节的散列,前面有一个零字节。

The hash format is 0 + salt[16] + key[32] , so could use this hash if we slice off the first byte. 哈希格式为0 + salt[16] + key[32] ,因此如果切掉第一个字节,可以使用此哈希。 For example: 例如:

const passwordHasher = require('aspnet-identity-pw');

function hashPassword(password) {
    var hash = passwordHasher.hashPassword(password);
    var bytes = Buffer(hash, 'base64');
    return bytes.slice(1).toString('base64');
}

function verifyPassword(hashedPassword, password) {
    var bytes = new Buffer(hashedPassword, 'base64');
    var hash = Buffer.concat([new Buffer([0x00]), bytes]).toString('base64');
    return passwordHasher.validatePassword(password, hash);
}

var hash = "JWvppSSfnzOQ+uMd+BORpT/8aQorC8y05Bjbo/8w/9b/eiG4WLzUFRQSSiKZqo3C";
console.log(verifyPassword(hash, '123456'))
//true

This code also produces results that are compatible with your PHP code. 此代码还生成与PHP代码兼容的结果。 Personally, I would rather use crypto directly because it is more flexible, and also aspnet-identity-pw doesn't use a constant time algorithm when comparing hashes. 就个人而言,我宁愿直接使用crypto ,因为它更灵活,并且aspnet-identity-pw在比较哈希时也不使用恒定时间算法 But I understand that aspnet-identity-pw may be easier to use, and it may be safer for less experienced users. 但我知道aspnet-identity-pw可能更容易使用,对于经验不足的用户来说可能更安全。

According to the documentation you should send in the password as the first parameter in validatePassword. 根据文档,您应该将密码作为validatePassword中的第一个参数发送。 So try this: 试试这个:

 var passwordHasher = require('aspnet-identity-pw'); var hashedPassword = passwordHasher.hashPassword('123456'); console.log(hashedPassword); var isValid = passwordHasher.validatePassword('123456', hashedPassword); console.log("Result:"+isValid); 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM