Пароль SALT и HASH в nodejs с криптографией

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

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

person lostintranslation    schedule 19.06.2013    source источник


Ответы (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);
}
person Matthew    schedule 19.06.2013
comment
ах, так что я должен хранить соль, созданную для каждого пользователя, а также хешированный пароль? - person lostintranslation; 20.06.2013
comment
Да, вы не можете реконструировать тот же хэш, используя тот же простой текст, если вы не знаете соль, которая использовалась для создания хэша. - person Matthew; 20.06.2013
comment
@Мэттью Привет. Можете ли вы привести пример кода, как проверить пароль? Меня смущает соль. Я вручную присоединяю строку соли и хэша и сохраняю ее. Я знаю, что соль — это, скажем, 10 символов. Я получаю первые 10 символов сохраненного salt+hash, чтобы получить соль. Я использую pbkdf2 для хеширования входящего, подлежащего проверке пароля с той же солью. Если original salt+hash = to-be-validated salt+hash то я пускаю пользователя? Это оно? - person slevin; 26.12.2015

На основе документации 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++ для своих вычислений и будет работать быстрее асинхронно. Питер Лайонс прав, сначала следует использовать асинхронную версию метода, а синхронную выбирать только при необходимости. Асинхронный метод может быть таким же медленным, как и синхронный, но синхронный будет всегда медленным.

person TwentyMiles    schedule 19.06.2013
comment
Я думаю, что публикация примеров синхронных вызовов для вопросов node.js вводит в заблуждение и сбивает с толку. Вы просто не можете использовать такие функции в сетевой службе, такой как веб-приложение на основе мангуста, над которым почти наверняка работает запрашивающий. Пожалуйста, публикуйте асинхронные примеры только в том случае, если вы уверены, что синхронный подход подходит, но это почти никогда не бывает. - person Peter Lyons; 20.06.2013
comment
Также похоже, что последняя рекомендация NIST использовать pbkdf2 вместо bcrypt. Хотя и не собирался начинать религиозную войну. - person lostintranslation; 20.06.2013
comment
Вы абсолютно правы в том, что NIST рекомендует pbkdf2. Однако по причинам, хорошо объясненным в этом вопросе, bcrypt может быть более безопасным в определенных ситуациях. - person TwentyMiles; 20.06.2013
comment
@TwentyMiles дело не в скорости, а в однопоточности. В многопользовательском сетевом сервисе, таком как веб-приложение, весь процесс узла остановится и не будет выполнять ничего, кроме криптографии с интенсивным использованием процессора, если вы используете синхронные API. Это приведет к зависанию сервера ВСЕХ других сетевых клиентов. Вы просто не можете использовать какие-либо синхронные вызовы на сетевом сервере. Это в корне нарушает базовую концепцию № 1 о том, почему узел вообще работает. См.: stackoverflow.com/questions/16827373/ - person Peter Lyons; 20.06.2013
comment
bcrypt — это не что иное, как куча ошибок. Никак не устанавливается. - person Green; 20.06.2016
comment
@PeterLyons, ваши аргументы относительно скорости bcrypt верны. В любом случае, они не имеют значения, поскольку весь смысл bcrypt заключается не только в том, что его нельзя взломать с помощью радужных таблиц, но и в том, что его сложнее взломать. Это медленно, и именно поэтому это так эффективно. По мере роста компьютеров и вычислительной мощности вы можете просто модифицировать соляные раунды. Полезно прочитать: codahale.com/how-to-safely-store-a -пароль - person Jonas Drotleff; 18.08.2016
comment
@JonasDrotleff, ты не понимаешь контекста. Я говорю об асинхронности и синхронизации, которая представляет собой разницу между ожиданием 1 пользователем медленного шифрования (асинхронного) и ожиданием КАЖДОГО клиента любого шифрования (синхронизации). Дело в том, что вы вообще не должны использовать синхронные вызовы на сетевом сервере node.js. Тот факт, что звонки в этом случае являются криптографическими, является лишь косвенным. - person Peter Lyons; 19.08.2016

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

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

person hobbs    schedule 19.06.2013

Это модифицированная версия ответа @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));
            }
        });
    });
}
person André Pena    schedule 12.08.2017

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

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

person florian    schedule 02.05.2014

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

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

person Saransh Mohapatra    schedule 21.06.2013
comment
Примечание. В настоящее время SHA1 недостаточно защищен от коллизий. - person Master James; 22.01.2019

В этом сценарии есть два основных шага

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

Здесь вам нужно будет сделать следующее.

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

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

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

  • Пользователь введет имя пользователя/электронную почту и пароль.

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

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

  • Хэшируйте комбинацию тем же алгоритмом хеширования.

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

В этом руководстве есть подробное объяснение того, как это сделать с криптографией nodejs. Именно то, что вы ищете. Пароли Salt Hash с использованием шифрования NodeJS

person rahil471    schedule 12.07.2016