This is an implementation of the SHA1 cryptographic hash algorithm in Powershell. It was a little harder than it seems, since Powershell naturally assumes all numbers are signed. Lets take for example: 0xFFFFFFFF (the maximum 32 bit unsigned int), which one would assume is equal to 4294967295. Well.. Check out this simple line of code.
#This wont throw an exception right? [uint32]0xFFFFFFFF
Well, in PowerShell this just throws an exception. Why!? Because under the hood, PowerShell thinks: “0xFFFFFFFF”, that’s 32 bits. Let me stick it in a 32 bit signed integer. But wait… “0xFFFFFFFF” is just 32 1s. So in 2s complement, that’s -1. So in PowerShell 0xFFFFFFFF == -1. So now its pretty clear why the above code throws an exception, there is no unsigned value to represent -1. For an example, just go type “0xFFFFFFFF” into PowerShell. So.. because of that, everything here has to be done in a 64 bit integer.
# # Implemenation of SHA1 # function SHA1 { [CmdLetBinding()] param ( [Parameter(Mandatory = $false, Position=1)][string]$Str ) process { # # Truncate a value to UInt32 # function TUI { param($val) process { [uint64]($val -band (((-bnot [uint64]0)) -shr 32)) } } # # Get a 32 bit value from a byte array # function GetBigEndianUInt { param ( [byte[]]$bytes, [int]$index ) process { return ([uint64]$bytes[$index] -shl 24) -bor ([uint64]$bytes[$index+1] -shl 16) -bor ([uint64]$bytes[$index+2] -shl 8) -bor ([uint64]$bytes[$index+3]) } } # # Left rotate # function LeftRotate { param ( [uint64]$val, [int]$amount ) process { $res = TUI ([uint64]$val -shr (32 - $amount)) $res = $res -bor ($val -shl $amount) return TUI $res } } # Initialize constants $h0 = TUI 0x67452301 $h1 = TUI 0xEFCDAB89 $h2 = TUI 0x98BADCFE $h3 = TUI 0x10325476 $h4 = TUI 0xC3D2E1F0 # Get the message bytes $message = [System.Text.ASCIIEncoding]::ASCII.GetBytes($Str) # Get the length in bytes which we need. Message length + 0x80 + (64bit message len) $len = ($message.Length + 9) # Get the padded length of our our byte array if($len % 64 -ne 0){ $len += (64 - ($len % 64)) } # Copy the bytes in the message to our byte array $bytes = ([byte[]]0 * $len) for($i = 0; $i -lt $message.Length; $i++){ $bytes[$i] = [byte]$message[$i] } # Pad the message with 1000 0000 $bytes[$i] = 128 # The message length in bits $bitLen = $message.Length * 8 # Set the last [uint64] as the messsage length. (We only do 32 bits) $bytes[$len-1] = [byte]($bitLen -band 0xFF) $bytes[$len-2] = [byte](($bitLen -shr 8) -band 0xFF) $bytes[$len-3] = [byte](($bitLen -shr 16) -band 0xFF) $bytes[$len-4] = [byte](($bitLen -shr 24) -band 0xFF) # Divide the message into 512 bit chunks for($chunk = 0; $chunk -lt $bytes.Length; $chunk += 64) { $w = ([uint64[]]0 * 80) # Copy the chunk into our working array as uints for($i = 0; $i -lt 16; $i++){ $w[$i] = GetBigEndianUInt -bytes $bytes -index ($i*4 + $chunk) } for($i = 16; $i -lt 80; $i++){ $w[$i] = LeftRotate -val (TUI ($w[$i-3] -bxor $w[$i-8] -bxor $w[$i-14] -bxor $w[$i-16])) -amount 1 } $a = TUI $h0 $b = TUI $h1 $c = TUI $h2 $d = TUI $h3 $e = TUI $h4 # A bunch of crazy stuff for($i = 0; $i -lt 80; $i++){ $k=0 if($i -lt 20){ $f = TUI (($b -band $c) -bor ((-bnot $b) -band $d)) $k = TUI 0x5A827999 } elseif($i -lt 40){ $f = TUI ($b -bxor $c -bxor $d) $k = TUI 0x6ED9EBA1 } elseif($i -lt 60){ $f = TUI (($b -band $c) -bor ($b -band $d) -bor ($c -band $d)) $k = TUI 0x8F1BBCDC } else{ $f = TUI ($b -bxor $c -bxor $d) $k = TUI 0xCA62C1D6 } $temp = TUI ((LeftRotate -val $a -amount 5) + $f + $e + $k + $w[$i]) $e = $d $d = $c $c = LeftRotate -val $b -amount 30 $b = $a $a = $temp } $h0 = TUI ($h0 + $a) $h1 = TUI ($h1 + $b) $h2 = TUI ($h2 + $c) $h3 = TUI ($h3 + $d) $h4 = TUI ($h4 + $e) } '{0:X8}{1:X8}{2:X8}{3:X8}{4:X8}' -f $h0, $h1, $h2, $h3, $h4 } }
There it is. I may have overused my TUI(To uint) function a little bit, since values larger than 32 bits could actually cause problems. I got most of the implementation details from the Wikipedia article on SHA1.