Friday, November 20, 2015

Google Authenticator One Time Passwords with PowerShell

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

2 comments:

  1. Hi,
    this doesn't work.
    the URL generate QR but the Google Authenticator claim is invalid

    ReplyDelete
  2. Do you have an update for 26 char secret?

    ReplyDelete