Migrating users from ASP.NET Identity Core (2.0) to

I’m having trouble migrating a user, without losing the original password, from an existing database that was created using ASP.NET Identity Core (version 2.0).

My goal is to migrate a user over and be able to use the existing password.

I’m using Auth0’s Bulk Import API tools to do this, following the directions in this blog post very closely.

I am able to create users via the API without issue. My problem has only to do with retaining the user’s original password.

Here is what my JSON payload looks like for a single user (with some info redacted):

[
	{
		"user_id": "1",
		"email": "<my_email>",
		"custom_password_hash": {
			"algorithm": "sha1",
			"hash": {
                "value": "<base64_passwordhash>",
                "encoding": "base64"
			},
            "salt": {
                "value": "<base64_salt>",
                "encoding": "base64"
            }
		},
		"given_name": "Tom",
		"blocked": false,
		"email_verified": true
	}
]

I believe my issue to be with the way I am obtaining hash.value and salt.value.

To get these values, I am using a custom C# script I wrote in order to break down the PasswordHash that was originally produced by AspNetIdentity’s HashPassword method.

It is to my understanding that AspNetIdentity’s HashPassword() produces a base64 string that contains both an encoded salt and an encoded password value.

My C# script looks like this:

using System;
using System.Collections.Generic;
using System.Linq;
					
public class Program
{
	public static void Main()
	{
		var passwordhash_b64 = Convert.FromBase64String("<base64 PasswordHash produced by Asp.Net.Identity.Core v2.0>");
				
		List<byte> salt_bytes = new List<byte>();
		List<byte> pw_bytes = new List<byte>();
		
		// Break down the "PasswordHash" value into 
		// the bytes representing salt and pw
        // The first byte represents which version of Identity is being used - can ignore for this
        // bytes 2-17 represent the salt, randomly generated by Identity
        // bytes 18-49 represent the password hash
		for (var i = 0; i < passwordhash_b64.Length; i++)
		{		
			if (i > 0 && i < 17)
				salt_bytes.Add(passwordhash_b64[i]);
			else if (i > 0)
				pw_bytes.Add(passwordhash_b64[i]);
		}
		
		// Convert to byte array and print to console
		var salt_bytearray = salt_bytes.ToArray();
		
		// Convert to byte array and print to console
		var pw_bytearray = pw_bytes.ToArray();
				
		// Convert both back to base 64
		var salt_b64 = Convert.ToBase64String(salt_bytearray);
		var pw_b64 = Convert.ToBase64String(pw_bytearray);
		
		// print base64 to console, and use in json payload for user migration
		Console.WriteLine(salt_b64);
		Console.WriteLine(pw_b64);				
	}
}

Are my presuppositions about the breaking-down of the Identity PasswordHash incorrect? Am I specifying the wrong hashing algorithm (sha1)? Is my JSON payload incorrect?

Has anyone had success migrating users from Asp.Net.Identity.Core version 2.0 to Auth0 using Bulk Import? Any and all advice is appreciated.

Thanks in advance!

1 Like

Hi @mt-tom

Welcome to the Auth0 Community!

It looks like you may be using the wrong algorithm. Check out this post:

Hey @dan.woda, thanks for getting back to me.

I actually just came across that post this morning. I tried following the user’s instruction in the post update, so far without success.

I suspect that the way I am trying to produce a salt and password hash (via the C# script in my original post) is failing me.

Can you please post an example of the new payload you are trying?

False alarm - my PasswordHash data was incorrect from the start. All is good now.

This post has it correct:

“value”: “$pbkdf2-sha1$i=1000,l=32${salt}${hash}”

For anyone else in the future in a similar situation - here’s a quick C# script I wrote that you can run to produce the string you’ll need to put in hash.value in your user import JSON payload:

	public static void Main(string[] args)
	{
		var passwordhash_string_fromdb = "YOUR_PASSWORDHASH_BASE64_STRING";
		var passwordhash_bytes = Convert.FromBase64String(passwordhash_string_fromdb);

		var salt_bytes = new List<byte>();
		var pw_bytes = new List<byte>();
		
		// Break down passwordhash_string_fromdb into bytes representing salt and pw
		// The first byte represents the version of Identity being used (0 == v2.0; 1 == v3.0)
		// This script will not work if your password was not hashed using v2
		// For v2, bytes 1-17 represent the salt and bytes 18-49 represent the password
		// reference: https://github.com/aspnet/AspNetIdentity/blob/main/src/Microsoft.AspNet.Identity.Core/Crypto.cs
		if (passwordhash_bytes[0] != 0)
		{
			Console.WriteLine("This script will not work for you because your password was hashed using something other than Identity v2");
			return;
		}
		for (var i = 0; i < passwordhash_bytes.Length; i++)
		{		
			if (i > 0 && i < 17)
				salt_bytes.Add(passwordhash_bytes[i]);
			else if (i >= 17 && i <= 49)
				pw_bytes.Add(passwordhash_bytes[i]);
		}
		
		// Convert to byte array
		var salt_bytearray = salt_bytes.ToArray();			
		
		// Convert to byte array
		var pw_bytearray = pw_bytes.ToArray();
				
		// Convert both back to base64 and Remove base 64 padding aka '='
		var salt_b64_nopadding = Convert.ToBase64String(salt_bytearray).Replace('=',' ').Trim(' ');
		var pw_b64_nopadding = Convert.ToBase64String(pw_bytearray).Replace('=',' ').Trim(' ');	

		// PHC string format
		var pbkdf2string = ($"$pbkdf2-sha1$i=1000,l=32${salt_b64_nopadding}${pw_b64_nopadding}");

 		// pbkdf2string: copy this to hash.value in your json payload
		Console.WriteLine(pbkdf2string);
	}
2 Likes

Thanks for sharing that script!

1 Like