Monday, May 9, 2011

Using PowerShell to hash files

I found a way to use the built-in crypto functions to hash files on Windows systems using PowerShell. Paste the following lines into the PowerShell prompt, and specify one or more files when you run md5sum or sha1sum.

Function HashPrint ([byte[]]$hash)
{
  $result = ""
  foreach ($byte in $hash) {
    $result += "{0:x2}" -f $byte
    }
  Write-Host $result -nonewline
}

Function md5sum
{
  $csp = [System.Security.Cryptography.MD5CryptoServiceProvider];
  $hasher = new-object $csp
  for ($count = 0; $count -lt $args.length; $count++ ) {
    [byte[]]$is = Get-Content -encoding byte $args[$count];
    if ( $is.length -gt 0 ) {
      HashPrint($hasher.ComputeHash($is));
    } else {
      HashPrint($hasher.ComputeHash([char[]]"", 0, 0));
    }
    Write-Host " " $args[$count]
  }
}

Function sha1sum
{
  $csp = [System.Security.Cryptography.SHA1CryptoServiceProvider];
  $hasher = new-object $csp
  for ($count = 0; $count -lt $args.length; $count++ ) {
    [byte[]]$is = Get-Content -encoding byte $args[$count];
    if ( $is.length -gt 0 ) {
      HashPrint($hasher.ComputeHash($is));
    } else {
      HashPrint($hasher.ComputeHash([char[]]"", 0, 0));
    }
    Write-Host " " $args[$count]
  }
}


The zero length kludge is the only way I found to work around trying to pass a zero-length string to the ComputeHash function. ComputeHash complains about having only one empty argument, and .length of a zero-length string returns null rather than 0. (!!!) Since that hash is always fixed, I could just let it error out, or fake the result with the expected hash.

Note: this may crash on sufficiently large files, since it stores the file in a variable. I'm sure there's a way to use streams that doesn't have this limitation, anyone care to help out?