I have the following problem that I've been dealing for a couple of hours by now and it's driving nuts.
Context
I have a legacy database that stored passwords using the following algorithm. The legacy code used a Python library.
- PBKDF2 with SHA256
- 1000 iterations
- Salt has a length of 8
- Password is stored like this $salt$hashedPassword
I'm switching login flow for the new system and I need to migrate that old algorithm to a new one. New system uses .netcore
Question
What I'm trying to do is even possible?. How can I achieve it?
What my logic dictates is that I can take the salt and recreate the hashing algorithm using .netcore Crypto library but its not working and the function returns always false.
Legacy Code
from werkzeug.security import generate_password_hash, check_password_hash
def setPassword(self, password):
self.password = generate_password_hash(password, method='pbkdf2:sha256')
Where generate_password_hash comes from the library, this is the code
SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
def generate_password_hash(password, method="pbkdf2:sha256", salt_length=8):
"""Hash a password with the given method and salt with a string of
the given length. The format of the string returned includes the method
that was used so that :func:`check_password_hash` can check the hash.
The format for the hashed string looks like this::
method$salt$hash
This method can **not** generate unsalted passwords but it is possible
to set param method='plain' in order to enforce plaintext passwords.
If a salt is used, hmac is used internally to salt the password.
If PBKDF2 is wanted it can be enabled by setting the method to
``pbkdf2:method:iterations`` where iterations is optional::
pbkdf2:sha256:80000$salt$hash
pbkdf2:sha256$salt$hash
:param password: the password to hash.
:param method: the hash method to use (one that hashlib supports). Can
optionally be in the format ``pbkdf2:<method>[:iterations]``
to enable PBKDF2.
:param salt_length: the length of the salt in letters.
"""
salt = gen_salt(salt_length) if method != "plain" else ""
h, actual_method = _hash_internal(method, salt, password)
return "%s$%s$%s" % (actual_method, salt, h)
def gen_salt(length):
"""Generate a random string of SALT_CHARS with specified ``length``."""
if length <= 0:
raise ValueError("Salt length must be positive")
return "".join(_sys_rng.choice(SALT_CHARS) for _ in range_type(length))
Code
using System;
using System.Security.Cryptography;
using System.Text;
namespace test_pwd
{
class Program
{
static void Main(string[] args)
{
var res = SameHash("Qwerty12", "84e8c8a5dbdafaf23523ffa5dfecf29d53522a35ca4c76fa877c5fcf9eb4b654", "laSgSC6R");
Console.WriteLine(res);
}
public static bool SameHash(string userpwd, string storedHash, string storedSalt)
{
var saltByte = Encoding.UTF8.GetBytes(storedSalt);
var rfc = new Rfc2898DeriveBytes(userpwd, saltByte, 1000);
var baseString = Convert.ToBase64String(rfc.GetBytes(64));
return baseString == storedHash;
}
}
}
Base string is converted into
k6vhCweBNz8ymMeEdhi+1czrea+oTTYLrW1OuwdinA78AFyEXKitpKUGLCt1ZdyS1Vka8Cptzd5u5Uzdbi4MbA==
Which is not the same as the stored password hash I'm sending. What I'm doing wrong or this idea is even feasible?.