代码之家  ›  专栏  ›  技术社区  ›  Juned Ansari

使用nodejs生成ASP.NET网页\u成员身份密码

  •  3
  • Juned Ansari  · 技术社区  · 7 年前

    “网页\u成员资格”

    C#生成的随机密码: “JWvppSSfnzOQ+uMd+BORpT/8aQorC8y05Bjbo/8w/9b/eiG4WLzUFRQSSiKZqo3C”是“123456”字符串的哈希密码。

    所以现在有一些其他的模块现在将在nodejs中,但是其余的东西将只在c中。所以现在登录我必须通过节点登录。

    https://www.npmjs.com/package/aspnet-identity-pw

    但它返回False。

    c#为“123456”生成的密码=>JWvppSSfnzOQ+uMd+BORpT/8aQorC8y05Bjbo/8w/9b/EIG4WLZUFRQSIKZQO3C“

    请帮助我在nodejs中实现同样的目标。

    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
    

    参考工作PHP代码:

    <?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);
    
    2 回复  |  直到 7 年前
        1
  •  3
  •   t.m.adam    7 年前

    您可以使用内置的 crypto 模块。

    在PHP中 MembershipModel::encript crypto.pbkdf2Sync .

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

    现在我们可以编写一个函数 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');
    }
    

    为了我用过的盐 crypto.randomBytes 它是一个CSPRNG函数(创建安全的伪随机数据)。

    检查哈希:

    MembershipModel::verifypassword 方法使用收到的salt用PBKDF2创建一个密钥,然后将新密钥与收到的密钥进行比较。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);
    }
    

    我在用 crypto.timingSafeEqual 比较钥匙;它执行恒定时间比较。

    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
    

    我们可以看到 hashPassword verifyPassword 可以成功验证它们。


    关于PHP代码的一些注意事项:

    MembershipModel::bytearraysequal 方法应该使用一个常数时间算法,但是它返回false,并且第一次出现不相等的字符。更好的实现,使用位运算符:

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

    hash_equals 函数(需要PHP5.6或更高版本)。同样,您可以使用 openssl_pbkdf2 创建密钥(PHP5.5或更高版本)。

    我们可以改善你的生活 MembershipModel 使用这些函数初始化,并使用不需要空检查的类型提示(PHP7),生成更干净的代码。

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

    但是实现可能有bug(比如我在 成员身份模型::bytearraysequal


    在研究了 aspnet-identity-pw ,我发现它使用 内部。密钥由创建 crypto.pbkdf2 16字节salt和1000次迭代。唯一的区别是它创建了一个49字节的散列,前面是一个零字节。

    哈希格式为 0 + salt[16] + key[32] ,所以如果我们切掉第一个字节,就可以使用这个散列。例如:

    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
    

    加密 直接因为它更灵活,而且 aspnet标识pw doesn't use a constant time algorithm aspnet标识pw 可能更容易使用,而且对于经验不足的用户来说可能更安全。

        2
  •  0
  •   Ogge    7 年前

    根据文档,您应该将密码作为validatePassword中的第一个参数发送进来。所以试试这个:

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