Соль и хэш-пароль в nodejs w / crypto

Я пытаюсь выяснить, как солить и хэшировать пароль в nodejs с помощью модуля crypto. Я могу создать хэш-пароль, делая это:

UserSchema.pre('save', function(next) {
  var user = this;

  var salt = crypto.randomBytes(128).toString('base64');
  crypto.pbkdf2(user.password, salt, 10000, 512, function(err, derivedKey) {
    user.password = derivedKey;
    next();
  });
});

однако я смущен тем, как позже проверить пароль.

UserSchema.methods.validPassword = function(password) {    
  // need to salt and hash this password I think to compare
  // how to I get the salt?
}

7 ответов


в любом механизме сохранения (базе данных), который вы используете, вы бы сохранили полученный хэш вместе с солью и количеством итераций, оба из которых были бы открытым текстом. Если каждый пароль имеет другую соль (что вы должны сделать), вы должны сохранить эту информацию.

затем вы сравните новый пароль обычного текста, хэш, который использует ту же соль (и итерации), а затем сравните последовательность байтов с сохраненной.

создать пароль (псевдо)

function hashPassword(password) {
    var salt = crypto.randomBytes(128).toString('base64');
    var iterations = 10000;
    var hash = pbkdf2(password, salt, iterations);

    return {
        salt: salt,
        hash: hash,
        iterations: iterations
    };
}

для проверки пароля (псевдо)

function isPasswordCorrect(savedHash, savedSalt, savedIterations, passwordAttempt) {
    return savedHash == pbkdf2(passwordAttempt, savedSalt, savedIterations);
}

на основе документации nodejs (http://nodejs.org/api/crypto.html), не похоже, что существует определенный метод, который будет проверять пароль для вас. Чтобы проверить его вручную, вам нужно будет вычислить хэш текущего предоставленного пароля и сравнить его с сохраненным для равенства. В принципе, вы будете делать то же самое с паролем вызова, что и с оригиналом, но использовать соль, хранящуюся в базе данных, вместо создания нового, а затем сравните два хеша.

Если вы не слишком привержены использованию встроенной крипто-библиотеки, я мог бы рекомендовать использовать bcrypt. Эти два примерно равны на фронте безопасности, но я думаю, что bcrypt имеет более удобный интерфейс. Пример того, как его использовать (взятый непосредственно из документов bcrypt на странице, связанной выше), будет следующим:

создать хэш:

var bcrypt = require('bcrypt');
var salt = bcrypt.genSaltSync(10);
var hash = bcrypt.hashSync("B4c0/\/", salt);
// Store hash in your password DB.

чтобы проверить пароль:

// Load hash from your password DB.
bcrypt.compareSync("B4c0/\/", hash); // true
bcrypt.compareSync("not_bacon", hash); // false

редактировать добавить:

еще одним преимуществом bcrypt является то, что вывод функции genSalt содержит как хэш, так и соль в одной строке. Это означает, что вы можете хранить только один элемент в своей базе данных вместо двух. Существует также метод, который будет генерировать соль одновременно с хэшированием, поэтому вам не нужно беспокоиться об управлении солью вообще.

редактировать для уточнения:

в ответ на комментарий от Петр Лайонс: ты на 100% правильно. Я предполагал, что модуль bcrypt, который я рекомендовал, был реализацией javascript, и поэтому использование его асинхронно не ускорит работу на однопоточной модели узла. Оказывается, это не так; модуль bcrypt использует собственный код C++ для своих вычислений и будет работать быстрее асинхронно. Питер Лайонс прав, сначала вы должны использовать асинхронную версию метода и только при необходимости выбирать синхронную. Этот асинхронный метод может быть как синхронной, но синхронно один всегда быть медленным.


либо хранить пароль и соль в отдельных столбцах в базе данных, либо (мой предпочтительный метод) хранить пароли в базе данных в формате, совместимом с RFC 2307 5.3. Примером может служить {X-PBKDF2}base64salt:base64digest. Вы также можете сохранить количество итераций там, что позволяет увеличить количество итераций в будущем для новых учетных записей и учетных записей, которые обновляют ваши пароли, не нарушая логины для всех остальных.

пример хэш от моего собственного модуль PBKDF2 для Perl выглядит так:
{X-PBKDF2}HMACSHA1:AAAD6A:8ODUPA==:1HSdSVVwlWSZhbPGO7GIZ4iUbrk= который включает в себя конкретный используемый алгоритм хэша, а также количество итераций, соль и полученный ключ.


Я думаю, что этот учебник будет наиболее подходящим для вас. Просто пройдите через него, это лучшее, что я нашел. учебник по паспорту с узлом.js и Crypto

надеюсь, вы найдете это полезным.


столкнувшись с тем же вопросом, я собрал все вместе в один модуль:https://www.npmjs.org/package/password-hash-and-salt

Он использует pbkdf2 и хранит хэш, соль, алгоритм и итерации в одном поле. Надеюсь, это поможет.


в этом сценарии участвуют два основных шага

1) Создание и хранение паролей

здесь вам придется сделать следующее.

  • возьмите пароль пользователя
  • создать строку случайных символов (соль)
  • объедините соль с введенным пользователем паролем
  • хэш комбинированной строки.
  • храните хэш и соль в база данных.

2) проверка пароля пользователя

этот шаг потребуется для аутентификации пользователя.

  • пользователь вводит имя пользователя / адрес электронной почты и пароль.

  • получить хэш и соль на основе введенного имени пользователя

  • объедините соль с паролем пользователя

  • хэш-комбинация с тем же хэширования алгоритм.

  • сравнить результат.

в этом уроке есть подробное объяснение того, как это сделать с NodeJS crypto. Именно то, что вы ищете. хэш-пароли соли с использованием NodeJS crypto


Это измененная версия ответа @Matthews, используя TypeScript

import * as crypto from 'crypto';

const PASSWORD_LENGTH = 256;
const SALT_LENGTH = 64;
const ITERATIONS = 10000;
const DIGEST = 'sha256';
const BYTE_TO_STRING_ENCODING = 'hex'; // this could be base64, for instance

/**
 * The information about the password that is stored in the database
 */
interface PersistedPassword {
    salt: string;
    hash: string;
    iterations: number;
}

/**
 * Generates a PersistedPassword given the password provided by the user. This should be called when creating a user
 * or redefining the password
 */
export async function generateHashPassword(password: string): Promise<PersistedPassword> {
    return new Promise<PersistedPassword>((accept, reject) => {
        const salt = crypto.randomBytes(SALT_LENGTH).toString(BYTE_TO_STRING_ENCODING);
        crypto.pbkdf2(password, salt, ITERATIONS, PASSWORD_LENGTH, DIGEST, (error, hash) => {
            if (error) {
                reject(error);
            } else {
                accept({
                    salt,
                    hash: hash.toString(BYTE_TO_STRING_ENCODING),
                    iterations: ITERATIONS,
                });
            }
        });
    });
}

/**
 * Verifies the attempted password against the password information saved in the database. This should be called when
 * the user tries to log in.
 */
export async function verifyPassword(persistedPassword: PersistedPassword, passwordAttempt: string): Promise<boolean> {
    return new Promise<boolean>((accept, reject) => {
        crypto.pbkdf2(passwordAttempt, persistedPassword.salt, persistedPassword.iterations, PASSWORD_LENGTH, DIGEST, (error, hash) => {
            if (error) {
                reject(error);
            } else {
                accept(persistedPassword.hash === hash.toString(BYTE_TO_STRING_ENCODING));
            }
        });
    });
}