00:00 / 00:00

Logo curso MongoDB: almacenando claves de forma segura

MongoDB: almacenando claves de forma segura

Fecha publicación: Jul 26, 2021

Recuperando credenciales

Ahora que ya sabemos como guardar las contraseñas de nuestros usuarios, necesitamos una forma de comprobar que cuando uno de ellos introduce sus credenciales, éstos son correctos, por ejemplo, en un formulario de login.

Manos a la obra

Lo primero, vamos a mover el código a una función para que quede separado lo que sería la insercción del usuario y por otro lado, el comprobar credenciales:

./src/index.js

...

+ const insertUsers = async (db) => {
+   const salt1 = await generateSalt();
+   const password1 = await hash('my-password', salt1);

+   const salt2 = await generateSalt();
+   const password2 = await hash('my-password', salt2);

+   const users = [
+     {
+       name: 'John Doe',
+       password: password1,
+       email: 'john.doe@email.com',
+     },
+     {
+       name: 'Jane Doe',
+       password: password2,
+       email: 'jane.doe@email.com',
+     },
+   ];

+   await db.collection('users').insertMany(users);
+   console.log(`Se han insertado ${users.length} usuarios`);
+ }

(async function () {
  const client = new MongoClient(connectionURI);
  await client.connect();
  const db = client.db();
  console.log('Conectado a la base de datos');

- const salt1 = await generateSalt();
- const password1 = await hash('my-password', salt1);

- const salt2 = await generateSalt();
- const password2 = await hash('my-password', salt2);

- const users = [
-   {
-     name: 'John Doe',
-     password: password1,
-     email: 'john.doe@email.com',
-   },
-   {
-     name: 'Jane Doe',
-     password: password2,
-     email: 'jane.doe@email.com',
-   },
- ];

- await db.collection('users').insertMany(users);
- console.log(`Se han insertado ${users.length} usuarios`);
+ await insertUsers(db);
})();

Cuando un usuario utiliza el formulario de login para identificarse, normalmente utiliza un valor único que lo identifique como un email y su contraseña, que nadie más conoce.

¿Cómo podemos comprobar ahora que esos credenciales son correctos si en base de datos no almacenamos la contraseña en texto claro, si no un valor _hash_ de la misma? La única opción para poder comprobar la contraseña proporcionada por el usuario, es volver a aplicar la funcion hash utilizando la misma salt y comparar ambas contraseñas _hasheadas_.

Asi que necesitamos guardar en base de datos la salt para recuperarla según el email proporcionado, y asi poder comparar ambos valores.

./src/index.js

...

const insertUsers = async (db) => {
  const salt1 = await generateSalt();
  const password1 = await hash('my-password', salt1);

  const salt2 = await generateSalt();
  const password2 = await hash('my-password', salt2);

  const users = [
    {
      name: 'John Doe',
      password: password1,
+     salt: salt1
      email: 'john.doe@email.com',
    },
    {
      name: 'Jane Doe',
      password: password2,
+     salt: salt2,
      email: 'jane.doe@email.com',
    },
  ];

  await db.collection('users').insertMany(users);
  console.log(`Se han insertado ${users.length} usuarios`);
};
...

Oye y esto de guardar la _salt_ en base de datos es seguro? No hay problema en guardarla ahí, ya que las Rainbow Tables se crean de antemano para ahorrar tiempo en los cálculos, y aunque el atacante obtenga el valor de la salt, generar Rainbow Tables sobre la marcha teniendo en cuenta estos valores de la salt lleva casi tanto tiempo como descifrar la contraseña, por lo que no merece la pena intentar guardar la salt en otro sitio o cifrar el valor de algún modo. Si quieres ampliar información sobre este tema te dejamos este enlace.

Vamos por tanto a implementar la parte de comprobar credenciales, lo primero sería recuperar el usuario por el email proporcionado:

...

+ const hasValidCredentials = async (db, email, password) => {
+   const user = await db.collection('users').findOne({ email });
+ };

(async function () {
  const client = new MongoClient(connectionURI);
  await client.connect();
  const db = client.db();
  console.log('Conectado a la base de datos');

  await insertUsers(db);
})();

Ahora, podemos aplicar la función hash utilizando el password en texto plano y la salt recuperada en base de datos:

...

const hasValidCredentials = async (db, email, password) => {
  const user = await db.collection('users').findOne({ email });
+ const hashedPassword = await hash(password, user.salt);
};
...

Si esta contraseña hasheada coincide con la recuperada de base de datos, entonces si que ha introducido los credenciales correctos:

...
const hasValidCredentials = async (db, email, password) => {
  const user = await db.collection('users').findOne({ email });
  const hashedPassword = await hash(password, user.salt);
+ return hashedPassword === user.password;
};
...

Vamos a comprobarlo introduciendo credenciales válidas y credenciales incorrectas:

...

const hasValidCredentials = async (db, email, password) => {
  const user = await db.collection('users').findOne({ email });
  const hashedPassword = await hash(password, user.salt);
  return hashedPassword === user.password;
};

(async function () {
  const client = new MongoClient(connectionURI);
  await client.connect();
  const db = client.db();
  console.log('Conectado a la base de datos');

  await insertUsers(db);

+ const invalidCredentials = await hasValidCredentials(
+   db,
+   'john.doe@email.com',
+   'invalid-password'
+ );
+ console.log(`¿Los credenciales son correctos? ${invalidCredentials}`);

+ const validCredentials = await hasValidCredentials(
+   db,
+   'john.doe@email.com',
+   'my-password'
+ );
+ console.log(`¿Los credenciales son correctos? ${validCredentials}`);
})();

Borramos la base de datos actual, para probar el nuevo código:

docker-compose down

Y volvemos a arrancarlo todo:

docker-compose up

Y en otro terminal:

npm start

Guardando la salt en base de datos

Resultado de comprobar credenciales

A partir de aquí, si queremos mejorar la seguridad, se puede ir ajustando los valores proporcionados para generar el hash o incluso utilizar otras funciones de derivación de clave como Scrypt, Argon2, etc que cubren otros casos más avanzados o incluso utilizar otro algoritmos de hash como la versión 3 de SHA, BLAKE, etc.

¿Con ganas de aprender Backend?

En Lemoncode impartimos un Bootcamp Backend Online, centrado en stack node y stack .net, en él encontrarás todos los recursos necesarios: clases de los mejores profesionales del sector, tutorías en cuanto las necesites y ejercicios para desarrollar lo aprendido en los distintos módulos. Si quieres saber más puedes pinchar aquí para más información sobre este Bootcamp Backend.