published on
tags: powershell aspnet

How to hash passwords in PowerShell with ASP.NET Core Identity PasswordHasher

Calling .NET types from PowerShell is not always as straightforward as it seems. Here’s how I was able to use the PasswordHasher to generate a hash compatible with ASP.NET Core Identity’s AspNetUsers table.

First, gather the NuGet package for Microsoft.Extensions.Identity.Core and all of its dependencies.

Pick a runtime and gather the DLLs into a folder called “References” under the folder with your PowerShell script. I chose net461 but it depends on your platform.

Here’s the function to get a password hash:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<#
    .SYNOPSIS
    Gets the hashed password.

    .DESCRIPTION
    Hashes the password with the same class used by ASP.NET Core Identity.

    .PARAMETER Password
    The new password.

    .OUTPUTS
    string. The hashed password.
#>
function Get-PasswordHash {
    param(
        [Parameter(Mandatory = $true)]
        [string] $Password
    )

    [string]$assemblyBasePath = "$PSScriptRoot\References"

    Add-Type -Path "$assemblyBasePath\System.Memory.dll"
    Add-Type -Path "$assemblyBasePath\Microsoft.Extensions.Primitives.dll"
    Add-Type -Path "$assemblyBasePath\Microsoft.Extensions.DependencyInjection.Abstractions.dll"
    Add-Type -Path "$assemblyBasePath\Microsoft.Extensions.Logging.Abstractions.dll"
    Add-Type -Path "$assemblyBasePath\Microsoft.Extensions.Options.dll"
    Add-Type -Path "$assemblyBasePath\Microsoft.Extensions.Identity.Core.dll"

    $hasherOptions = New-Object Microsoft.AspNetCore.Identity.PasswordHasherOptions
    $createMethod = [Microsoft.Extensions.Options.Options].GetMethod("Create").MakeGenericMethod([Microsoft.AspNetCore.Identity.PasswordHasherOptions])
    $hasherIOptions = $createMethod.Invoke($null, ($hasherOptions)) 
    $hasher = New-Object -TypeName 'Microsoft.AspNetCore.Identity.PasswordHasher[System.String]' -ArgumentList ($hasherIOptions)
    return $hasher.HashPassword($null, $Password)
}

The constructor for PasswordHasher defaults the options to null. However, PowerShell kept complaining it couldn’t find a constructor. So the code creates a PasswordHasherOptions instance and calls Options.Create. Also note that the generic for PasswordHasher is string. This is the type of the user. A brief look at the code shows that the default implementation doesn’t use the user type/object for anything.

When it comes to adding the types, they have to be added in the right order. PowerShell was not able to automatically pull in other assemblies in the same path based on dependencies.

If you’re using Visual Studio Code, it may suggest using SecureString for the Password parameter. The .NET SecureString is being retired though.