I like two-factor authentication. Hopefully you do, too. Google Authenticator's OTP is a very popular app for using 2FA. The code below implements one-time passwords and is entirely interoperable with Google Authenticator. Details for use are in the function headers, and I should point out here that the bulk of the smarts in this code are simply translated from a javascript implementation I found online (and credit in the code). Why would you want this in PowerShell? No idea! But isn't in fun to know/see how this stuff works?
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | <# .SYNOPSIS Implementation of the Time-based One-time Password Algorithm used by Google Authenticator. .DESCRIPTION As described in http://tools.ietf.org/id/draft-mraihi-totp-timebased-06.html, the script generates a one-time password based on a shared secret key and time value. This script generates output identical to that of the Google Authenticator application, but is NOT INTENDED FOR PRODUCTION USE as no effort has been made to code securely or protect the key. For demonstration-use only. Script code is essentially a transation of a javascript implementation found at http://jsfiddle.net/russau/uRCTk/ Output is a PSObject that includes the generated OTP, the values of intermediate calculations, and a URL leading to a QR code that can be used to generate a corresponding OTP in Google Authenticator applications. The generated QR code contains a URL that takes the format "otpauth://totp/<email_address_here>?secret=<secret_here>", for example: otpauth://totp/tester@test.com?secret=JBSWY3DPEHPK3PXP The generated OTP is (obviously) time-based, so this script outptu will only match Google Authenticator output if the clocks on both systems are (nearly) in sync. The acceptable alphabet of a base32 string is ABCDEFGHIJKLMNOPQRSTUVWXYZ234567. Virtually no parm checking is done in this script. Caveat Emptor. .PARAMETER sharedSecretKey A random, base32 string shared by both the challenge and reponse side of the autheticating pair. This script mandates a string length of 16. .EXAMPLE .\Get-OTP.ps1 -sharedSecret "JBSWY3DPEHPK3PXP" | Select SharedSecret, Key, Time, HMAC, URL, OTP .NOTES FileName: Get-OTP.ps1 Author: Jim Nelson nelsondev1 #> [CmdletBinding()] param ( [Parameter(Mandatory=$true,ValueFromPipeline=$true)] [ValidateLength(16,16)] [string]$sharedSecret ) #------------------------------------------------------------------------------ #------------------------------------------------------------------------------ # Converts the supplied Int64 value to hexadecimal. #------------------------------------------------------------------------------ function Convert-DecimalToHex($in) { return ([String]("{0:x}" -f [Int64]$in)).ToUpper() } #------------------------------------------------------------------------------ #------------------------------------------------------------------------------ # Converts the supplied hexadecimal value Int64. #------------------------------------------------------------------------------ function Convert-HexToDecimal($in) { return [Convert]::ToInt64($in,16) } #------------------------------------------------------------------------------ #------------------------------------------------------------------------------ # Converts the supplied hexadecimal string to a byte array. #------------------------------------------------------------------------------ function Convert-HexStringToByteArray($String) { return $String -split '([A-F0-9]{2})' | foreach-object { if ($_) {[System.Convert]::ToByte($_,16)}} } #------------------------------------------------------------------------------ #------------------------------------------------------------------------------ # Converts the supplied base32 string to a hexadecimal string #------------------------------------------------------------------------------ function Convert-Base32ToHex([String]$base32) { $base32 = $base32.ToUpper() $base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" $bits = "" $hex = "" # convert char-by-char of input into 5-bit chunks of binary foreach ($char in $base32.ToCharArray()) { $tmp = $base32chars.IndexOf($char) $bits = $bits + (([Convert]::ToString($tmp,2))).PadLeft(5,"0") } # leftpad bits with 0 until length is a multiple of 4 while ($bits.Length % 4 -ne 0) { $bits = "0" + $bits } # convert binary chunks of 4 into hex for (($tmp = $bits.Length -4); $tmp -ge 0; $tmp = $tmp - 4) { $chunk = $bits.Substring($tmp, 4); $dec = [Convert]::ToInt32($chunk,2) $h = Convert-DecimalToHex $dec $hex = $h + $hex } return $hex } #------------------------------------------------------------------------------ #------------------------------------------------------------------------------ # Get the currentUnix epoch (div 30) in hex, left-padded with 0 to 16 chars #------------------------------------------------------------------------------ function Get-EpochHex() { # this line from http://shafiqissani.wordpress.com/2010/09/30/how-to-get-the-current-epoch-time-unix-timestamp/ $unixEpoch = ([DateTime]::Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000 $h = Convert-DecimalToHex ([Math]::Floor($unixEpoch / 30)) return $h.PadLeft(16,"0") } #------------------------------------------------------------------------------ #------------------------------------------------------------------------------ # Get the HMAC signature for the supplied key and time values. #------------------------------------------------------------------------------ function Get-HMAC($key, $time) { $hashAlgorithm = New-Object System.Security.Cryptography.HMACSHA1 $hashAlgorithm.key = Convert-HexStringToByteArray $key $signature = $hashAlgorithm.ComputeHash((Convert-HexStringToByteArray $time)) $result = [string]::join("", ($signature | % {([int]$_).toString('x2')})) $result = $result.ToUpper() return $result } #------------------------------------------------------------------------------ #------------------------------------------------------------------------------ # Get the OTP based on the supplied HMAC #------------------------------------------------------------------------------ function Get-OTPFromHMAC($hmac) { $offset = Convert-HexToDecimal($hmac.Substring($hmac.Length -1)) $p1 = Convert-HexToDecimal($hmac.Substring($offset*2,8)) $p2 = Convert-HexToDecimal("7fffffff") [string]$otp = $p1 -band $p2 $otp = $otp.Substring($otp.Length - 6, 6) return $otp } # ------------------------------------------------------------------------------------------------------- # ------------------------------------------------------------------------------------------------------- # ------------------------------------------------------------------------------------------------------- # MAIN PROGRAM # ------------------------------------------------------------------------------------------------------- # ------------------------------------------------------------------------------------------------------- $reportObject = New-Object PSObject –Property @{'SharedSecret' = ""; 'Key' = ""; 'Time' = ""; 'HMAC' = ""; 'OTP' = ""; 'URL' = ""; } #google can generate a QR code of the secret for their authenticator app at this url... $email = "look@that.com" $url = "https://chart.googleapis.com/chart?chs=200x200&cht=qr&chl=200x200&chld=M|0&cht=qr&chl=otpauth://totp/" + $email + "%3Fsecret%3D" + $sharedSecret $key = Convert-Base32ToHex $sharedSecret $time = Get-EpochHex $hmac = Get-HMAC $key $time $otp = Get-OTPFromHMAC $hmac $reportObject.SharedSecret = $sharedSecret $reportObject.Key = $key $reportObject.Time = $time $reportObject.HMAC = $hmac $reportObject.OTP = $otp $reportObject.URL = $url $reportObject |
Hi,
ReplyDeletethis doesn't work.
the URL generate QR but the Google Authenticator claim is invalid
Do you have an update for 26 char secret?
ReplyDelete