Skip to content

Instantly share code, notes, and snippets.

@royashbrook
Last active July 1, 2020 01:52
Show Gist options
  • Save royashbrook/e708a7969084345069df4a43b3229567 to your computer and use it in GitHub Desktop.
Save royashbrook/e708a7969084345069df4a43b3229567 to your computer and use it in GitHub Desktop.
cli for password hashing using dotnetcore identity with no user
# this is actually just output from running in powershell,
# but I'll just add my comments in here along with the output
## First of all, I am going to set an alias to the compiled exe
└[C:\git\h]> set-alias h "C:\git\h\bin\Release\netcoreapp3.1\win-x64\h.exe"
## Generate a users file, I'm using a tab here because if you
## select this from a database query window, it'll be tab seperated
## `t is a tab character in powershell
## This first line puts a header row in there
└[C:\git\h]> "Username`tPassword" > users.txt
## Add a value for users 0-5
└[C:\git\h]> 0..5 | %{"User$_`tPassword$_">>users.txt}
## show the file
└[C:\git\h]> type users.txt
Username Password
User0 Password0
User1 Password1
User2 Password2
User3 Password3
User4 Password4
User5 Password5
## Import the file, noting the delimiter is a `t, by default it's ,
└[C:\git\h]> $users = ipcsv users.txt -Delimiter "`t"
## Now generate hashes for each password, note the call to the 'h.exe' here
└[C:\git\h]> $hashes = $users | select Username, @{n="Hash";e={h $_.Password}}
## Show them, note these will be different each time due to how the hasher
## works, but they will still match, also due to how the hasher works
└[C:\git\h]> $hashes
Username Hash
-------- ----
User0 AQAAAAEAACcQAAAAEMEKg+0ZBbN5yrU61QwLjLmGtfqz9rxeI/JE+yh50b8MUSQ5ZP9jTnBo03Rgqjxinw==
User1 AQAAAAEAACcQAAAAEOITU5VLDv09PxDS0Srlx3fBfN/ESW8kcCDLvZWMA7MoiI+FaJxAEjTgowP0v8bkHw==
User2 AQAAAAEAACcQAAAAEDtt5KDzMHns6HAxu+BRVBC9fx4p+sbp7C865vBQ55FkEzwis4r0KZEVXF1tf68vkA==
User3 AQAAAAEAACcQAAAAEGl8rdnq7w32QMxprJvyhQm5c+uPStmvz+xWqC1h0KsufB9I2tfwjQB66BnoRmWm2A==
User4 AQAAAAEAACcQAAAAEICrchmblaQio2cGyDtuNKVoosNZBDYOpSlu/otk5ZlTICVGeIpGdY0hMxRZgGcnbA==
User5 AQAAAAEAACcQAAAAELZbMaqVlXex8t40fXlpyXpaW4PClcVrxqjTuIMP4rp7v6OCJy6CC0wnNQlmB4tGgw==
## Now we will generate some sql that holds the users and hashes.
## This will be wrapped with the actual statements needed to insert
## You'll see the output in the following lines, but just to provide
## some info on what is happening, I'm taking the hashes and then
## creating a new object with a property called sql that has a
## select statement for each value, then i use the join command
## for each of those sql values to put a new line and a union all
## sql command between them. What this basically will do is make
## a big select statement that will merge all of these values
└[C:\git\h]> $UsersAndHashes = ($hashes | select @{n="sql";e={"select '{0}','{1}'" -f $_.UserName,$_.Hash}} ).sql -join "`nunion all "
## This will actually generate the sql to run. You can see that the
## sql generated above is just placed into the middle of the value below
## Note: just in case you are not a powershell person, note that the >> at
## the front of the line is part of the way the shell shows commands that
## span a line, if you were running this at the command line, you would not
## have the >> characters at the front. This is what it looks like
## typed or pasted into the command line.
└[C:\git\h]> $SqlToRun = @"
>> ;with a(UserName,Hash) as (
>> $UsersAndHashes
>> )
>> insert into [dbo].[AspNetUsers] ([Id],[SecurityStamp],UserName,[NormalizedUserName],[EmailConfirmed],[PasswordHash],[TwoFactorEnabled],[LockoutEnabled],AccessFailedCount,PhoneNumberConfirmed)
>> select newid(),newid(),UserName, UPPER(UserName),1,[Hash],0,0,0,0 from a
>> "@
## And here is the Sql you'll actually paste into a sql window with the
## aspnet identity tables in it. when i was working on this, i wrapped it
## in a tran so i could check it before and after. you should probably
## do the same just to be safe =).
## In case this sql is not clear, what is happening is we are using a CTE
## to basically serve as a temp table for the values we generated for
## users and passwordhashes, that is then getting used along with some
## default values with an insert statement to put the users into the
## required table. You have to add a value for the security stamp and
## the id, but these are just guids so newid works fine. you also have
## to add the 0 and 1 values as those are non null fields. Obviously,
## if you are wanting to set some different values, do so. This is just
## what I used for testing. I normalized to uppercase as that seemed
## to be what was in there when I created some users the normal way,
## although the 'normal' way had email addresses in there. This worked
## just fine with usernames though.
└[C:\git\h]> $SqlToRun
;with a(UserName,Hash) as (
select 'User0','AQAAAAEAACcQAAAAEMEKg+0ZBbN5yrU61QwLjLmGtfqz9rxeI/JE+yh50b8MUSQ5ZP9jTnBo03Rgqjxinw=='
union all select 'User1','AQAAAAEAACcQAAAAEOITU5VLDv09PxDS0Srlx3fBfN/ESW8kcCDLvZWMA7MoiI+FaJxAEjTgowP0v8bkHw=='
union all select 'User2','AQAAAAEAACcQAAAAEDtt5KDzMHns6HAxu+BRVBC9fx4p+sbp7C865vBQ55FkEzwis4r0KZEVXF1tf68vkA=='
union all select 'User3','AQAAAAEAACcQAAAAEGl8rdnq7w32QMxprJvyhQm5c+uPStmvz+xWqC1h0KsufB9I2tfwjQB66BnoRmWm2A=='
union all select 'User4','AQAAAAEAACcQAAAAEICrchmblaQio2cGyDtuNKVoosNZBDYOpSlu/otk5ZlTICVGeIpGdY0hMxRZgGcnbA=='
union all select 'User5','AQAAAAEAACcQAAAAELZbMaqVlXex8t40fXlpyXpaW4PClcVrxqjTuIMP4rp7v6OCJy6CC0wnNQlmB4tGgw=='
)
insert into [dbo].[AspNetUsers] ([Id],[SecurityStamp],UserName,[NormalizedUserName],[EmailConfirmed],[PasswordHash],[TwoFactorEnabled],[LockoutEnabled],AccessFailedCount,PhoneNumberConfirmed)
select newid(),newid(),UserName, UPPER(UserName),1,[Hash],0,0,0,0 from a
# make the base project
dotnet new console -o h
# switch to project dir
cd h
# add a couple of packages we'll need
dotnet add package Microsoft.AspNetCore.Identity
dotnet add package Microsoft.AspNetCore.Cryptography.KeyDerivation
# get the source file we'll tweak from github
$uri = "https://raw.githubusercontent.com/aspnet/Identity/master/src/Core/"
"PasswordHasher.cs" | %{iwr $uri$_ -o .\$_}
[C:\git\h]> dotnet build
Microsoft (R) Build Engine version 16.6.0+5ff7b0c9e for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.
Determining projects to restore...
All projects are up-to-date for restore.
h -> C:\git\h\bin\Debug\netcoreapp3.1\h.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:01.51
└[C:\git\h]> $passwordHash = dotnet run password
└[C:\git\h]> $password1Hash = dotnet run password1
└[C:\git\h]> $passwordHash
AQAAAAEAACcQAAAAEKBC6Tb5klZXeE51B8GwBssJWHVyqAy4O3BWPEVkaNzYx6XttTBXNuUYlI6Bu4sGFQ==
└[C:\git\h]> $password1Hash
AQAAAAEAACcQAAAAEEO6oT/nnLhprMbx2TwFhaM3WLdylwAS5bNVwAeWI9t0jD1BBLgL0+gcYmFS5y+7og==
└[C:\git\h]> dotnet run password $passwordHash
True
└[C:\git\h]> dotnet run password $password1Hash
False
└[C:\git\h]> dotnet run password1 $password1Hash
True
└[C:\git\h]> dotnet run password1 $passwordHash
False
//change this:
public class PasswordHasher<TUser> : IPasswordHasher<TUser> where TUser : class
//to this, or just remove everything after the comment below
public class PasswordHasher//<TUser> : IPasswordHasher<TUser> where TUser : class
//next, simply add some default values for these private vars
//these...
private readonly PasswordHasherCompatibilityMode _compatibilityMode;
private readonly int _iterCount;
private readonly RandomNumberGenerator _rng;
//become these...
//Note these are the default values from the PasswordHasherOptions.cs file
// you can see that here if you want:
//https://github.com/aspnet/Identity/blob/master/src/Core/PasswordHasherOptions.cs
private readonly PasswordHasherCompatibilityMode _compatibilityMode = PasswordHasherCompatibilityMode.IdentityV3;
private readonly int _iterCount = 10000;
private readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create();
//next we are going to, basically, wipe out the constructor
// as we don't need to pass in any information
//replace this...
public PasswordHasher(IOptions<PasswordHasherOptions> optionsAccessor = null)
{
var options = optionsAccessor?.Value ?? new PasswordHasherOptions();
_compatibilityMode = options.CompatibilityMode;
switch (_compatibilityMode)
{
case PasswordHasherCompatibilityMode.IdentityV2:
// nothing else to do
break;
case PasswordHasherCompatibilityMode.IdentityV3:
_iterCount = options.IterationCount;
if (_iterCount < 1)
{
throw new InvalidOperationException(Resources.InvalidPasswordHasherIterationCount);
}
break;
default:
throw new InvalidOperationException(Resources.InvalidPasswordHasherCompatibilityMode);
}
_rng = options.Rng;
}
// with just this one line:
public PasswordHasher(){}
// and finally, we need to remove the user var from these two function calls
// so these lines...
public virtual string HashPassword(TUser user, string password)
public virtual PasswordVerificationResult VerifyHashedPassword(TUser user, string hashedPassword, string providedPassword)
//become these lines...
public virtual string HashPassword(string password)
public virtual PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
using System;
namespace h
{
class Program
{
static void Main(string[] args)
{
string result = "Syntax: hash <password> or hash <password> <hash>";
if (args.Length==1 || args.Length==2){
var passwordHasher = new Microsoft.AspNetCore.Identity.PasswordHasher();
if(args.Length == 1)
result = passwordHasher.HashPassword(args[0]);
if(args.Length == 2)
result = (passwordHasher.VerifyHashedPassword(args[1],args[0]).ToString() == "Success").ToString();
}
Console.WriteLine(result);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment