Contraseña de SAL y HASH en nodejs con crypto


Estoy tratando de averiguar cómo salar y hash una contraseña en nodejs utilizando el módulo crypto. Puedo crear la contraseña con hash haciendo esto:

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

Sin embargo, estoy confundido acerca de cómo validar más tarde la contraseña.

UserSchema.methods.validPassword = function(password) {    
  // need to salt and hash this password I think to compare
  // how to I get the salt?
}
Author: gevorg, 2013-06-20

7 answers

En cualquier mecanismo de persistencia (base de datos) que esté utilizando, almacenaría el hash resultante junto con la sal y el número de iteraciones, los cuales serían texto plano. Si cada contraseña tiene una sal diferente (que debe hacer), debe guardar esa información.

Luego compararía la nueva contraseña de texto plano, hash que usa la misma sal (e iteraciones), luego compararía la secuencia de bytes con la almacenada.

Para generar la contraseña (pseudo)

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

Para validar la contraseña (pseudo)

function isPasswordCorrect(savedHash, savedSalt, savedIterations, passwordAttempt) {
    return savedHash == pbkdf2(passwordAttempt, savedSalt, savedIterations);
}
 33
Author: Matthew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-06 15:38:04

Basado en la documentación de nodejs ( http://nodejs.org/api/crypto.html ), no parece que haya un método específico que validará una contraseña para usted. Para validar manualmente, deberá calcular el hash de la contraseña proporcionada y compararla con la almacenada uno para la igualdad. Básicamente, harás lo mismo con la contraseña de desafío que hiciste con la original, pero usa la sal almacenada en la base de datos en lugar de generar una nueva, y luego compara los dos hashes.

Si no está demasiado comprometido con el uso de la biblioteca de cifrado incorporada, podría recomendar el uso de bcrypt en su lugar. Los dos son casi iguales en el frente de seguridad, pero creo que bcrypt tiene una interfaz más fácil de usar. Un ejemplo de cómo usarlo (tomado directamente de los documentos de bcrypt en la página enlazada arriba) sería este:

Crea un hash:

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

Para comprobar una contraseña:

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

Editar para añadir:

Otra ventaja de bcrypt es que la salida de la función genSalt contiene tanto el hash como la sal en una cadena. Esto significa que puede almacenar solo un elemento en su base de datos, en lugar de dos. También hay un método proporcionado que generará una sal al mismo tiempo que se produce el hachís, por lo que no tiene que preocuparse de administrar la sal en absoluto.

Editar para actualizar:

En respuesta al comentario de Peter Lyons: estás 100% en lo correcto. Había asumido que el módulo bcrypt que tenía se recomendó una implementación de javascript, y por lo tanto usarlo de forma asíncrona realmente no aceleraría las cosas en el modelo de subproceso único de node. Resulta que este no es el caso; el módulo bcrypt utiliza código nativo de c++ para sus cálculos y se ejecutará más rápido de forma asíncrona. Peter Lyons tiene razón, primero debe usar la versión asíncrona del método y solo elegir la síncrona cuando sea necesario. El método asíncrono podría ser tan lento como el síncrono, pero el síncrono siempre será lento.

 23
Author: TwentyMiles,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:33:24

Almacene la contraseña y la sal en columnas separadas en su base de datos, o (mi método preferido), almacene sus contraseñas en su base de datos en un formato que sea compatible con RFC 2307 sección 5.3. Un ejemplo sería {X-PBKDF2}base64salt:base64digest. También puede almacenar su recuento de iteraciones allí, lo que le permite aumentar el recuento de iteraciones en el futuro para cuentas nuevas y cuentas que actualizan sus contraseñas, sin romper los inicios de sesión para todos los demás.

Un hash de ejemplo de mi propio El módulo PBKDF2 para Perl parece
{X-PBKDF2}HMACSHA1:AAAD6A:8ODUPA==:1HSdSVVwlWSZhbPGO7GIZ4iUbrk= que incluye el algoritmo hash específico utilizado, así como el número de iteraciones, la sal y la clave resultante.

 7
Author: hobbs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-19 21:25:18

Creo que este tutorial sería el más adecuado para usted. Sólo tienes que ir a través de él, es el mejor que he encontrado hasta ahora. Tutorial de pasaporte con Nodo.js y Crypto

Espero que le resulte útil.

 5
Author: Saransh Mohapatra,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-21 18:55:05

Ante la misma pregunta reuní todo en un módulo: https://www.npmjs.org/package/password-hash-and-salt

Utiliza pbkdf2 y almacena hash, salt, algoritmo e iteraciones en un solo campo. Espero que ayude.

 5
Author: florian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-02 20:42:04

Hay dos pasos principales involucrados en este escenario

1) Crear y Almacenar la contraseña

Aquí tendrá que hacer lo siguiente.

  • Tome la contraseña de usuario
  • Generar una cadena de caracteres aleatorios (sal)
  • Combine la sal con la contraseña introducida por el usuario
  • Hash la cadena combinada.
  • Almacena el hash y la sal en la base de datos.

2) Validación de la contraseña de usuario

Este paso sería necesario para autenticar al usuario.

  • El usuario ingresará el nombre de usuario / correo electrónico y la contraseña.

  • Obtener el hash y la sal en función del nombre de usuario introducido

  • Combine la sal con la contraseña de usuario

  • Hash la combinación con el mismo algoritmo de hash.

  • Compara el resultado.

Este tutorial tiene una explicación detallada sobre cómo hacerlo con nodejs crypto. Exactamente lo que eres buscando. Salt Hash contraseñas usando NodeJS crypto

 1
Author: rahil471,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-01 05:06:37

Esta es una versión modificada de @Matthews answer, usando 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));
            }
        });
    });
}
 1
Author: André Pena,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-12 17:02:25