question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Cannot authenticate users imported with SHA512 passwords

See original GitHub issue

[READ] Step 1: Are you in the right place?

  • For issues related to the code in this repository file a Github issue.
  • If the issue pertains to Cloud Firestore, read the instructions in the “Firestore issue” template.
  • For general technical questions, post a question on StackOverflow with the firebase tag.
  • For general Firebase discussion, use the firebase-talk google group.
  • For help troubleshooting your application that does not fall under one of the above categories, reach out to the personalized Firebase support channel.

[REQUIRED] Step 2: Describe your environment

  • Operating System version: OS X 10.14.6
  • Firebase SDK version: firebase-admin 8.9.2
  • Firebase Product: auth
  • Node.js version: 12.6.0
  • NPM version: 6.9.0

[REQUIRED] Step 3: Describe the problem

Steps to reproduce:

I cannot authenticate with imported users using SHA512-encrypted passwords. The import is successful and the password hashes look correct using listUsers(), but authentication fails with auth/wrong-password.

What happened? How can we make the problem occur? This could be a description, log/console output, etc.

See code below for demo. I started from the [firebase docs instructions on importing users] (https://firebase.google.com/docs/auth/admin/import-users#import_users_with_md5_sha_and_pbkdf_hashed_passwords). Users imported using (known) SHA512 encrypted passwords are not able to authenticate.

I have tried this using both existing encrypted passwords using the AuthLogic ruby gem and by generating the encrypted passwords myself, as below. I have confirmed that my method produces identical password hashes to AuthLogic. Neither method is producing valid logins.

Relevant Code:

package.json dependencies:

{
    "firebase": "^7.9.1",
    "firebase-admin": "^8.9.2",
    "js-base64": "^2.5.2",
    "js-sha512": "^0.8.0"
  }

Node script to reproduce the issue:

// ---- SETUP ---- //
const Base64 = require('js-base64').Base64;
const firebase = require('firebase');
const firebaseAdmin = require('firebase-admin');
const fs = require('fs')
const path = require('path');
const { sha512 } = require('js-sha512')
const util = require('util');

const adminCredsPath = path.join(process.env.HOME, 'etc', 'firebase', 'personal-admin.json');
const clientConfig = path.join(process.env.HOME, 'etc', 'firebase', 'personal-client.json');


// ---- HELPER FUNCTIONS ---- //
function encryptPassword({password, rounds, salt}) {
  let c = password;
  if (salt) { c += salt; }
  for (let ii = 0; ii < rounds; ii++) { c = sha512(c) }
  return c;
}

async function importUser({uid, email, encryptedPassword, salt, shaRounds}) {
  const user = {
    uid: uid,
    email: email,
    emailVerified: true,
    passwordHash: Buffer.from(encryptedPassword),
  };
  if (salt) { user.passwordSalt = Buffer.from(salt) }
  const importOptions = {
    hash: {
      algorithm: 'SHA512',
      rounds: shaRounds
    }
  };

  // allow repeatedly running the script without needing to use new uids
  await firebaseAdmin.auth().deleteUser(uid).catch(err => console.log("Did not delete user:", err.message));

  return firebaseAdmin.auth().importUsers(
    [user], importOptions
  );
}

function sleep(time) {
  return new Promise((res, rejj) => {setTimeout(res, time)})
}

async function setupApp() {
  firebaseAdmin.initializeApp({
    credential: firebaseAdmin.credential.cert(adminCredsPath)
  });

  const clientCreds = await util.promisify(fs.readFile)(clientConfig, 'utf-8').then(JSON.parse);
  await firebase.initializeApp(clientCreds);

  firebase.auth().onAuthStateChanged(user => {
    console.log("Auth change:", user)
  })
}

// ----- MAIN FUNCTIONS ---- //
async function main() {
  await setupApp();

  const email = 'test@foo.com';
  const password = 'test123';
  const rounds = 20;
  // const salt = 'abc';
  // const encryptedPassword = encryptPassword({password, rounds, salt});
  const encryptedPassword = encryptPassword({password, rounds});
  console.log({password, encryptedPassword, base64: Base64.encode(encryptedPassword)});

  const userDetails = {uid: '123', email, encryptedPassword, shaRounds: rounds};
  console.log("Importing", email);
  const result = await importUser(userDetails);
  console.log(result);
  console.log("Created user for", email);

  const {users} = await firebaseAdmin.auth().listUsers();
  console.log("USERS", users.map(({uid, email, passwordHash, passwordSalt}) => ({uid, email, passwordHash, passwordSalt})));

  await sleep(5000);
  console.log("Attempting to sign in")
  await firebase.auth().signInWithEmailAndPassword(email, password).catch(console.error)
}

main().then(() => { process.exit() })

OUTPUT:

{
  password: 'test123',
  encryptedPassword: 'ac888afb942d693a154323d180d708bc2b4c6d4902d3b90dfddc3e2e88d975a4c641addd492c67af24656cf4252c18fce8a45337d8c72e9a4f9b4e3782d90589',
  base64: 'YWM4ODhhZmI5NDJkNjkzYTE1NDMyM2QxODBkNzA4YmMyYjRjNmQ0OTAyZDNiOTBkZmRkYzNlMmU4OGQ5NzVhNGM2NDFhZGRkNDkyYzY3YWYyNDY1NmNmNDI1MmMxOGZjZThhNDUzMzdkOGM3MmU5YTRmOWI0ZTM3ODJkOTA1ODk='
}
Importing test@foo.com
Auth change: null
{ successCount: 1, failureCount: 0, errors: [] }
Created user for test@foo.com
USERS [
  {
    uid: '123',
    email: 'test@foo.com',
    passwordHash: 'YWM4ODhhZmI5NDJkNjkzYTE1NDMyM2QxODBkNzA4YmMyYjRjNmQ0OTAyZDNiOTBkZmRkYzNlMmU4OGQ5NzVhNGM2NDFhZGRkNDkyYzY3YWYyNDY1NmNmNDI1MmMxOGZjZThhNDUzMzdkOGM3MmU5YTRmOWI0ZTM3ODJkOTA1ODk=',
    passwordSalt: ''
  }
]
Attempting to sign in
[M [Error]: The password is invalid or the user does not have a password.] {
  code: 'auth/wrong-password',
  message: 'The password is invalid or the user does not have a password.'
}

To prove that password hashes match the third-party output:

My method in Node:

> encryptPassword({password: "test123", rounds: 20})
'ac888afb942d693a154323d180d708bc2b4c6d4902d3b90dfddc3e2e88d975a4c641addd492c67af24656cf4252c18fce8a45337d8c72e9a4f9b4e3782d90589'
> encryptPassword({password: "test123", rounds: 20, salt: "abc"})
'1e878fcdb010cb6d4013a2bd4aa24e5b28eb0f7749b6c2429aa30c8bdf53af24d9f0055e8277196b25dff06845f85e2b99c44cf705d4556436c2abd023bef6ed'

Using AuthLogic in ruby:

> Authlogic::CryptoProviders::Sha512.encrypt("test123")
 => "ac888afb942d693a154323d180d708bc2b4c6d4902d3b90dfddc3e2e88d975a4c641addd492c67af24656cf4252c18fce8a45337d8c72e9a4f9b4e3782d90589"
> Authlogic::CryptoProviders::Sha512.encrypt("test123", "abc")
 => "1e878fcdb010cb6d4013a2bd4aa24e5b28eb0f7749b6c2429aa30c8bdf53af24d9f0055e8277196b25dff06845f85e2b99c44cf705d4556436c2abd023bef6ed"

I have tried the above script with salted and unsalted passwords. Same result.

Any help is greatly appreciated!

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:8 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
hiranya911commented, Feb 22, 2020

I managed to get a successful sign in by modifying your function as follows:

function encryptPassword({password, rounds, salt}) {
  let c = password;
  if (salt) { c = salt + c; } // Salt-first
  for (let ii = 0; ii < rounds; ii++) {
      const hex = sha512(c)
      c = new Buffer(hex, 'hex');
  }
  return c;
}

In addition to using salt-first hashing, you should also account for the fact that the js-sha512 API you’re using returns output as a hex string.

0reactions
hiranya911commented, Oct 7, 2020

I might be wrong, but it seems you’re setting the hash algorithm type to SHA512 while actually using a PBKDF2 hash. I think the hashing logic for SHA512 should look something like this:

crypto.createHash('sha512').update(rawSalt + rawPassword).digest();

That might explain why the login is not working as expected. As for why the password hash is not included in the listUsers() results, that may be due to your service account lacking some required IAM permission (I believe it needs to have the “Firebase Authentication Admin” role for those fields to be returned – See #660).

Read more comments on GitHub >

github_iconTop Results From Across the Web

Users are unable to login using imported hashed password
Users that are created using Imported Hashed Password can not login for the first time to the Okta tenant. Applies To. Create User...
Read more >
Unable to login after importing user into Firebase Authentication
We are migrating users into Firebase Auth from legacy db who have their passwords hashed with SHA512. We are able to successfully import...
Read more >
Authenticating Users
In this tutorial I will explain how to properly authenticate a user against a name and password managed by our own system, which...
Read more >
Can't log in using imported pbkdf2 hashed passwords
I'm trying to use a user imported into Auth0, using passwords with hash pbkdf2, the job runs successfully, but I can't log in...
Read more >
Importing hashed password into Keycloak - Google Groups
I think you need to add a password policy so that your hashing iterations match, as the default is 27500, not 180000. Go...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found