PowerShell checking file hash

PowerShell has a function Get-FileHash to compute the hash value for a file by using a specified hash algorithm. For example, to calculate SHA1 hash code for a file:

Get-FileHash -Path "D:\Software\test.ovf" -Algorithm SHA1

You can also check other algorithms like MD5, SHA256 etc.

PS D:\Software> Get-Command Get-FileHash

CommandType     Name                  Version    Source
-----------     ----                  -------    ------
Function        Get-FileHash          3.1.0.0    Microsoft.PowerShell.Utility

To see the definition of the function:

PS D:\Software> (Get-Command Get-FileHash).definition

    [CmdletBinding(DefaultParameterSetName = "Path", HelpURI = "https://go.microsoft.com/fwlink/?LinkId=517145")]
    param(
        [Parameter(Mandatory, ParameterSetName="Path", Position = 0)]
        [System.String[]]
        $Path,

        [Parameter(Mandatory, ParameterSetName="LiteralPath", ValueFromPipelineByPropertyName = $true)]
        [Alias("PSPath")]
        [System.String[]]
        $LiteralPath,

        [Parameter(Mandatory, ParameterSetName="Stream")]
        [System.IO.Stream]
        $InputStream,

        [ValidateSet("SHA1", "SHA256", "SHA384", "SHA512", "MACTripleDES", "MD5", "RIPEMD160")]
        [System.String]
        $Algorithm="SHA256"
    )

    begin
    {
        # Construct the strongly-typed crypto object

        # First see if it has a FIPS algorithm
        $hasherType = "System.Security.Cryptography.${Algorithm}CryptoServiceProvider" -as [Type]
        if ($hasherType)
        {
            $hasher = $hasherType::New()
        }
        else
        {
            # Check if the type is supported in the current system
            $algorithmType = "System.Security.Cryptography.${Algorithm}" -as [Type]
            if ($algorithmType)
            {
                if ($Algorithm -eq "MACTripleDES")
                {
                    $hasher = $algorithmType::New()
                }
                else
                {
                    $hasher = $algorithmType::Create()
                }
            }
            else
            {
                $errorId = "AlgorithmTypeNotSupported"
                $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument
                $errorMessage = [Microsoft.PowerShell.Commands.UtilityResources]::AlgorithmTypeNotSupported -f $Algorithm
                $exception = [System.InvalidOperationException]::New($errorMessage)
                $errorRecord = [System.Management.Automation.ErrorRecord]::New($exception, $errorId, $errorCategory, $null)
                $PSCmdlet.ThrowTerminatingError($errorRecord)
            }
        }

        function GetStreamHash
        {
            param(
                [System.IO.Stream]
                $InputStream,

                [System.String]
                $RelatedPath,

                [System.Security.Cryptography.HashAlgorithm]
                $Hasher)

            # Compute file-hash using the crypto object
            [Byte[]] $computedHash = $Hasher.ComputeHash($InputStream)
            [string] $hash = [BitConverter]::ToString($computedHash) -replace '-',''

            if ($RelatedPath -eq $null)
            {
                $retVal = [PSCustomObject] @{
                    Algorithm = $Algorithm.ToUpperInvariant()
                    Hash = $hash
                }
            }
            else
            {
                $retVal = [PSCustomObject] @{
                    Algorithm = $Algorithm.ToUpperInvariant()
                    Hash = $hash
                    Path = $RelatedPath
                }
            }
            $retVal.psobject.TypeNames.Insert(0, "Microsoft.Powershell.Utility.FileHash")
            $retVal
        }
    }

    process
    {
        if($PSCmdlet.ParameterSetName -eq "Stream")
        {
            GetStreamHash -InputStream $InputStream -RelatedPath $null -Hasher $hasher
        }
        else
        {
            $pathsToProcess = @()
            if($PSCmdlet.ParameterSetName  -eq "LiteralPath")
            {
                $pathsToProcess += Resolve-Path -LiteralPath $LiteralPath | Foreach-Object ProviderPath
            }
            if($PSCmdlet.ParameterSetName -eq "Path")
            {
                $pathsToProcess += Resolve-Path $Path | Foreach-Object ProviderPath
            }

            foreach($filePath in $pathsToProcess)
            {
                if(Test-Path -LiteralPath $filePath -PathType Container)
                {
                    continue
                }

                try
                {
                    # Read the file specified in $FilePath as a Byte array
                    [system.io.stream]$stream = [system.io.file]::OpenRead($filePath)
                    GetStreamHash -InputStream $stream  -RelatedPath $filePath -Hasher $hasher
                }
                catch [Exception]
                {
                    $errorMessage = [Microsoft.PowerShell.Commands.UtilityResources]::FileReadError -f $FilePath, $_
                    Write-Error -Message $errorMessage -Category ReadError -ErrorId "FileReadError" -TargetObject $FilePath
                    return
                }
                finally
                {
                    if($stream)
                    {
                        $stream.Dispose()
                    }
                }
            }
        }
    }

To check a series of files under certain directory:

Get-ChildItem -File |where {$_.Name -notlike '*dbg'}|ForEach-Object {Get-FileHash -Path $_ -Algorithm MD5}

However you may see an error like the following when calculating MD5:

Exception calling ".ctor" with "0" argument(s): "This implementation is not part of the Windows Platform FIPS validated cryptographic algorithms."
At
C:\Windows\system32\WindowsPowerShell\v1.0\Modules\Microsoft.PowerShell.Utility\Microsoft.PowerShell.Utility.psm1:31
char:13

That’s because the computer where you run the PowerShell has FIPS enabled. You can confirm it’s enabled with:

PS D:\> Get-ItemProperty -Path Registry::HKLM\System\CurrentControlSet\Control\Lsa\FIPSAlgorithmPolicy


Enabled      : 1
PSPath       : Microsoft.PowerShell.Core\Registry::HKLM\System\CurrentControlSet\Control\Lsa\FIPSAlgorithmPolicy
PSParentPath : Microsoft.PowerShell.Core\Registry::HKLM\System\CurrentControlSet\Control\Lsa
PSChildName  : FIPSAlgorithmPolicy
PSProvider   : Microsoft.PowerShell.Core\Registry

Or if you have a PowerShell drive namedHKLM mapped to registry: HKEY_LOCAL_MACHINE:

PS D:\> Get-PSDrive

Name           Used (GB)     Free (GB) Provider      Root                                               CurrentLocation
----           ---------     --------- --------      ----                                               ---------------
Alias                                  Alias
C                  51.09         48.37 FileSystem    C:\                                               Windows\system32
Cert                                   Certificate   \
D                 104.23        919.77 FileSystem    D:\
Env                                    Environment
Function                               Function
HKCU                                   Registry      HKEY_CURRENT_USER
HKLM                                   Registry      HKEY_LOCAL_MACHINE
Variable                               Variable
WSMan                                  WSMan
Z                                      FileSystem    Z:\

PS D:\> Get-ItemProperty -Path HKLM:\System\CurrentControlSet\Control\Lsa\FIPSAlgorithmPolicy


Enabled      : 1
PSPath       : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\FIPSAlgorithmPolicy
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa
PSChildName  : FIPSAlgorithmPolicy
PSDrive      : HKLM
PSProvider   : Microsoft.PowerShell.Core\Registry

.NET has the property AllowOnlyFipsAlgorithms enabled:

PS D:\> [System.Security.Cryptography.Cryptoconfig]::AllowOnlyFipsAlgorithms
True

As a comparison, you can create an SHA256 object, but it will fail when creating an MD5 object:

PS D:\> $md5    = [System.Security.Cryptography.MD5]::Create()
Exception calling "Create" with "0" argument(s): "This implementation is not part of the Windows Platform FIPS validated
cryptographic algorithms."
At line:1 char:1
+ $md5    = [System.Security.Cryptography.MD5]::Create()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : InvalidOperationException

PS D:\> $sha256 = [System.Security.Cryptography.SHA256]::Create()
PS D:\> $sha256.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    SHA256Cng                                System.Security.Cryptography.SHA256

If you really need to compute MD5 checksum, you might use certutil.exe as a workaround without disabling FIPS

PS D:\Software> Get-Command certutil.exe

CommandType     Name                  Version    Source
-----------     ----                  -------    ------
Application     certutil.exe          10.0.17... C:\Windows\system32\certutil.exe
PS D:\Software> certutil.exe -hashfile .\test.ovf md5
MD5 hash of .\test.ovf: 1cab1536b160b8a6827de086852a4598
CertUtil: -hashfile command completed successfully.

certutil.exe is a very powerful tool which can do a lot of things around certificates. For example, if you have a pfx file and you would like to take a look it first before you install it, you can use the tool to dump the certificates within the pfx file:

certutil -v -p <password_of_pfx> -dump <pfx_file> > cert.out

You can use the tool to encode a file to Base64 or decode a Base64 file. To see the help of the tool:

certutil -?
certutil -v -uSAGE

References:

  1. https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig.allowonlyfipsalgorithms?view=net-6.0
  2. https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography?view=net-6.0

One thought on “PowerShell checking file hash

  1. Thanks! Here’s my solution to live with FIPS:

    function CertUtilMD5
    {
    [cmdletbinding()]
    param([string]$filepath)

    $cmdpar = @(‘-hashfile’, $filepath, ‘md5’);

    $out = (&certutil.exe $cmdpar) -split “`n”;
    $MD5 = $null;
    [bool]$ret = ( 0 -eq $LASTEXITCODE);
    $message = switch ($LASTEXITCODE)
    {
    0 {
    Write-Verbose $out[0].Trim();
    $MD5 = $out[1].Trim();
    Write-Verbose $out[2].Trim();
    }
    default {
    Write-Warning $out[0].Trim();
    Write-Error $out[1].Trim();
    }
    }
    return $MD5;
    }

    Like

Leave a comment