Jump to content
wraithdu

Google Authenticator Implementation

Recommended Posts

wraithdu

I've had this one rolling around my brain for a while now. And while I can't take credit for much of anything at this point since it's just implementation, here's a framework for Time-based One-Time Password authentication, ie Google Authenticator. I've added links in all the places where I've harvested code. I'm planning on building this into an actual authenticator app, so this is just step one.

As always, thanks to everyone whose code contributed.

_GAuth.au3

#include-once
#include <_HMAC.au3>
#include <Date.au3>

;; http://tools.ietf.org/html/rfc6238
Func _GenerateTOTP($key, $keyIsBase32 = True, $time = Default, $period = 30, $digits = 6)
    Local $DIGITS_POWER[9] = [1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000]
    ; time is some number of seconds
    If $time = Default Then $time = _GetUnixTimeUTC()
    $time = StringFormat("%016X", Floor($time / $period))
    If $keyIsBase32 Then
        $key = _Base32ToHex($key, True) ; return binary
    Else
        $key = StringToBinary($key)
    EndIf
    ; HMAC function expects binary arguments
    Local $hash = _HMAC_SHA1($key, Binary("0x" & $time))
    Local $offset = BitAND(BinaryMid($hash, BinaryLen($hash), 1), 0xf)
    Local $otp = BitOR(BitShift(BitAND(BinaryMid($hash, $offset + 1, 1), 0x7f), -24), _
            BitShift(BitAND(BinaryMid($hash, $offset + 2, 1), 0xff), -16), _
            BitShift(BitAND(BinaryMid($hash, $offset + 3, 1), 0xff), -8), _
            BitAND(BinaryMid($hash, $offset + 4, 1), 0xff) _
            )
    $otp = Mod($otp, $DIGITS_POWER[$digits])
    Return StringFormat("%0" & $digits & "i", $otp)
EndFunc

;; http://www.autoitscript.com/forum/topic/153617-seconds-since-epoch-aka-unix-timestamp/
Func _GetUnixTimeUTC()
    ; returns number of seconds since EPOCH in UTC
    Local $aSysTimeInfo = _Date_Time_GetTimeZoneInformation()
    Local $utcTime = ""
    Local $sDate = _NowCalc()
    If $aSysTimeInfo[0] = 2 Then
        $utcTime = _DateAdd('n', $aSysTimeInfo[1] + $aSysTimeInfo[7], $sDate)
    Else
        $utcTime = _DateAdd('n', $aSysTimeInfo[1], $sDate)
    EndIf
    Return _DateDiff('s', "1970/01/01 00:00:00", $utcTime)
EndFunc

;; http://tomeko.net/online_tools/base32.php?lang=en
Func _Base32ToHex($sInput, $returnBinary = False)
    $sInput = StringRegExpReplace(StringUpper($sInput), "[^A-Z2-7]", "")
    Local $key = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
    Local $buffer = 0, $bitsLeft = 0, $i = 0, $count = 0, $output = "", $val
    While $i < StringLen($sInput)
        $val = StringInStr($key, StringMid($sInput, $i + 1, 1)) - 1 ; StringInStr returns 1 as 1st position
        If $val >=0 And $val < 32 Then
            $buffer = BitOR(BitShift($buffer, -5), $val)
            $bitsLeft += 5
            If $bitsLeft >= 8 Then
                $output &= Chr(BitAND(BitShift($buffer, $bitsLeft - 8), 0xFF))
                $bitsLeft -= 8
            EndIf
        EndIf
        $i += 1
    WEnd
    If $bitsLeft > 0 Then
        $buffer = BitShift($buffer, -5)
        $output &= Chr(BitAND(BitShift($buffer, $bitsLeft - 3), 0xFF))
    EndIf
    If $returnBinary Then
        Return StringToBinary($output)
    Else
        Return $output
    EndIf
EndFunc

#cs
Alternate base32 to hex functions
Func _b32toh($input)
    $input = StringRegExpReplace(StringUpper($input), "[^A-Z2-7]", "")
    Local $ch = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
    Local $bits = "", $hex = "", $val, $i
    For $i = 0 To StringLen($input) - 1
        $val = StringInStr($ch, StringMid($input, $i + 1, 1)) - 1
        $bits &= StringFormat("%05s", _itob($val))
    Next
    $i = 0
    Local $chunk
    While ($i + 4) <= StringLen($bits)
        $chunk = StringMid($bits, $i + 1, 4)
        $hex &= StringFormat("%X", _btoi($chunk))
        $i += 4
    WEnd
    Return $hex
EndFunc

; int to binary (0's and 1's) string
Func _itob($int)
    Local $o = ""
    While $int
        $o = BitAND($int, 1) & $o
        $int = BitShift($int, 1)
    WEnd
    Return $o
EndFunc

; binary (0's and 1's) string to int
Func _btoi($b)
    Local $p = 0, $o = 0
    For $i = StringLen($b) To 1 Step -1
        $o += (2 ^ $p) * Number(StringMid($b, $i, 1))
        $p += 1
    Next
    Return $o
EndFunc
#ce

Func _TOTPTestVectors()
#cs
   Test vectors operate in HOTP mode.

   The test token shared secret uses the ASCII string value
   "12345678901234567890".  With Time Step X = 30, and the Unix epoch as
   the initial value to count time steps, where T0 = 0, the TOTP
   algorithm will display the following values for specified modes and
   timestamps.

  +-------------+--------------+------------------+----------+--------+
  |  Time (sec) |   UTC Time   | Value of T (hex) |   TOTP   |  Mode  |
  +-------------+--------------+------------------+----------+--------+
  |      59     |  1970-01-01  | 0000000000000001 | 94287082 |  SHA1  |
  |             |   00:00:59   |                  |          |        |
  |  1111111109 |  2005-03-18  | 00000000023523EC | 07081804 |  SHA1  |
  |             |   01:58:29   |                  |          |        |
  |  1111111111 |  2005-03-18  | 00000000023523ED | 14050471 |  SHA1  |
  |             |   01:58:31   |                  |          |        |
  |  1234567890 |  2009-02-13  | 000000000273EF07 | 89005924 |  SHA1  |
  |             |   23:31:30   |                  |          |        |
  |  2000000000 |  2033-05-18  | 0000000003F940AA | 69279037 |  SHA1  |
  |             |   03:33:20   |                  |          |        |
  | 20000000000 |  2603-10-11  | 0000000027BC86AA | 65353130 |  SHA1  |
  |             |   11:33:20   |                  |          |        |
  +-------------+--------------+------------------+----------+--------+
#ce
    Local $times[6] = [59, 1111111109, 1111111111, 1234567890, 2000000000, 20000000000]
    For $i = 0 To 5
        ConsoleWrite(StringFormat("%016X", Floor($times[$i] / 30)) & " : " & _
            _GenerateTOTP("12345678901234567890", False, $times[$i], 30, 8) & @CRLF)
    Next
EndFunc

_HMAC.au3

#include-once
#include <Crypt.au3>

;; http://www.autoitscript.com/forum/topic/145556-solved-hmac-sha1/?p=1028830
Func _HMAC_SHA1($key, $message)
    If Not IsBinary($key) Then $key = Binary($key)
    If Not IsBinary($message) Then $message = Binary($message)
    Local $blocksize = 64
    Local $a_opad[$blocksize], $a_ipad[$blocksize]
    Local Const $oconst = 0x5C, $iconst = 0x36
    Local $opad = Binary(''), $ipad = Binary('')
    If BinaryLen($key) > $blocksize Then $key = _Crypt_HashData($key, $CALG_SHA1)
    For $i = 1 To BinaryLen($key)
        $a_ipad[$i-1] = Number(BinaryMid($key, $i, 1))
        $a_opad[$i-1] = Number(BinaryMid($key, $i, 1))
    Next
    For $i = 0 To $blocksize - 1
        $a_opad[$i] = BitXOR($a_opad[$i], $oconst)
        $a_ipad[$i] = BitXOR($a_ipad[$i], $iconst)
    Next
    For $i = 0 To $blocksize - 1
        $ipad &= Binary('0x' & Hex($a_ipad[$i], 2))
        $opad &= Binary('0x' & Hex($a_opad[$i], 2))
    Next
    Return _Crypt_HashData($opad & _Crypt_HashData($ipad & $message, $CALG_SHA1), $CALG_SHA1)
EndFunc

Func _HMAC_MD5($key, $message)
    If Not IsBinary($key) Then $key = Binary($key)
    If Not IsBinary($message) Then $message = Binary($message)
    Local $blocksize = 64
    Local $a_opad[$blocksize], $a_ipad[$blocksize]
    Local Const $oconst = 0x5C, $iconst = 0x36
    Local $opad = Binary(''), $ipad = Binary('')
    If BinaryLen($key) > $blocksize Then $key = _Crypt_HashData($key, $CALG_MD5)
    For $i = 1 To BinaryLen($key)
        $a_ipad[$i-1] = Number(BinaryMid($key, $i, 1))
        $a_opad[$i-1] = Number(BinaryMid($key, $i, 1))
    Next
    For $i = 0 To $blocksize - 1
        $a_opad[$i] = BitXOR($a_opad[$i], $oconst)
        $a_ipad[$i] = BitXOR($a_ipad[$i], $iconst)
    Next
    For $i = 0 To $blocksize - 1
        $ipad &= Binary('0x' & Hex($a_ipad[$i], 2))
        $opad &= Binary('0x' & Hex($a_opad[$i], 2))
    Next
    Return _Crypt_HashData($opad & _Crypt_HashData($ipad & $message, $CALG_MD5), $CALG_MD5)
EndFunc

Example: Use the test vectors function to test HOTP mode, or visit http://gauth.apps.gbraad.nl/ for a live test site. The default account uses "JBSWY3DPEHPK3PXP" as the key ( https://code.google.com/p/google-authenticator/wiki/KeyUriFormat ).

ConsoleWrite(_GenerateTOTP("JBSWY3DPEHPK3PXP") & @CRLF)
Edited by wraithdu
  • Like 2

Share this post


Link to post
Share on other sites
sheck

Nice.

I was just going to write my own, but it's already done and posted.

Thanks. You saved me a bunch of time.

This worked perfectly.

Tested with Android Google Authenticator.

Edited by sheck

Live and Learn, 'cause Knowledge is Super Power.

Share this post


Link to post
Share on other sites
Lefiya

嗨〜

我將此UDF用於TOTP,但OTP不正確。

我找不到錯誤,你能幫幫我嗎?謝謝〜

(我在UTC + 8)

 

此代碼生成值<>  http://gauth.apps.gbraad.nl/    value

我也使用谷歌  身份驗證器  <>  http://gauth.apps.gbraad.nl/    值。

昏暗$ OTP = _GenerateTOTP (“JBSWY3DPEHPK3PXP” ,真,默認,306

Translation:

Hi~

I use this UDF for TOTP, but the OTP is incorrect. I can't find the error, can you help me? Thank you~ (I am at UTC + 8)

This code generates a value <> http://gauth.apps.gbraad.nl/ value

I also use the Google Authenticator <> http://gauth.apps.gbraad.nl/ value.

 

Dim $ OTP = _GenerateTOTP ("JBSWY3DPEHPK3PXP", true, default, 30, 6)

 

Edited by Melba23
Added translation

Share this post


Link to post
Share on other sites
argumentum
On 11/6/2018 at 9:24 AM, Lefiya said:

I use this UDF for TOTP, but the OTP is incorrect

I tested the code as shown and it works just fine with "JBSWY3DPEHPK3PXP" and "alice@google.com" on http://gauth.apps.gbraad.nl/

PS: http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/ may have what you need, as it works like the link above.

PS2: UTC is UTC. UTC+8 is YOUR time. Use UTC  =)

Edited by argumentum
more info

Share this post


Link to post
Share on other sites
Lefiya
On 2018/11/9 at 12:59 AM, argumentum said:

我測試了所顯示的代碼,它與http://gauth.apps.gbraad.nl/上的“JBSWY3DPEHPK3PXP”和“ alice@google.com ”  一樣正常工作

PS:http:  //blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/  可能有你需要的東西,因為它像上面的鏈接一樣工作。

PS2:UTC是UTC。UTC + 8是你的時間使用UTC =)

Thank you for your heip ^^

but i understand about how to use...

Can you heip me again? thx~

And   why use   >>  ConsoleWrite(_GenerateTOTP("JBSWY3DPEHPK3PXP") & @CRLF)   ==   8

What mean about 8?

 

The site OTP <> code OTP... 

Video: 

 

 

#include <ButtonConstants.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <Constants.au3>
#include <GuiListBox.au3>
#include <GuiComboBox.au3>
#include <GuiListView.au3>
#include <Array.au3>
#include <Misc.au3>
#include <Process.au3>
#include <GUIConstants.au3>
#include <EditConstants.au3>
#Include <GuiEdit.au3>
#include <APIConstants.au3>
#include <GDIPlus.au3>
#include <Memory.au3>
#include <WinAPIEx.au3>
#include <MsgBoxConstants.au3>
#include <Access.au3>
#include <Thread.au3>
#include <GuiIPAddress.au3>
#include <Crypt.au3>
#include <Excel.au3>
#include <GuiMenu.au3>
#include <_HMAC.au3>
#include <_GAuth.au3>
#include <math.au3>
#include <Clipboard.au3>


Dim $a, $n


Opt("GUIOnEventMode", 1)
$Form1 = GUICreate('GAuth', 500, 500, -1, -1)
GUISetOnEvent($GUI_EVENT_CLOSE, "GUI_EVENT_CLOSE")
$Edit1 = GUICtrlCreateEdit('',20,20,460,410)
GUICtrlCreateButton('Grt OTP',200,450,100,30,$BS_DEFPUSHBUTTON)
GUICtrlSetOnEvent(-1, "_RUN")

GUISetState(@SW_SHOW)
_SetWindowPos($Form1,-1,-1,-1,-1,$HWND_TOPMOST,$SWP_NOMOVE+$SWP_NOSIZE);;TOP


While 1
        Sleep(100)
WEnd


Func GUI_EVENT_CLOSE()
    Exit
EndFunc


Func _RUN()
        Dim $unixTime = _GetUnixTimeUTC()
        Dim $time =  @YEAR & "/" & @MON & "/" & @MDAY & " " & @HOUR & ":" & @MIN & ":" & @SEC   
        Dim $easy0 = ConsoleWrite(_GenerateTOTP("JBSWY3DPEHPK3PXP") & @CRLF)
        Dim $easy1 = _GenerateTOTP("JBSWY3DPEHPK3PXP", True, Default, 30, 6)
        Dim $counter = Int($unixTime) / 30
        Dim $T = Floor($counter)
        Dim $key = _HMAC_SHA1('JBSWY3DPEHPK3PXP',$T)
        Dim $Offset = StringRight($key,1)

        If StringRegExp($Offset,'[a-fA-F]',0) = 1 Then
                If $Offset = 'A' Then $Offset = '10'
                If $Offset = 'B' Then $Offset = '11'
                If $Offset = 'C' Then $Offset = '12'
                If $Offset = 'D' Then $Offset = '13'
                If $Offset = 'E' Then $Offset = '14'
                If $Offset = 'F' Then $Offset = '15'
        EndIf
        $m = StringSplit($key,'x')
        $Sbits = StringMid($m[2],$Offset,8) ;;---hex

        $Sbits2 = StringMid ($Sbits,5,2) & StringMid ($Sbits,3,6)

        ;;---hex to dec
        Dim $s = 7
        Dim $tenVal = Null, $num = Null, $num2 = Null
        For $i = 0 to 7
                $num = StringMid ($Sbits2,$i+1,1)
                If StringRegExp($num,'[a-fA-F]',0) = 1 Then
                        If $num = 'A' Then $num = '10'
                        If $num = 'B' Then $num = '11'
                        If $num = 'C' Then $num = '12'
                        If $num = 'D' Then $num = '13'
                        If $num = 'E' Then $num = '14'
                        If $num = 'F' Then $num = '15'
                EndIf
                $num2 = $num * 16 ^ $s
                $s -= 1
                $tenVal += $num2 ;;dec
        Next


        $Digit = 6 ;;6 digits key
        $OTP = Mod($tenVal,10^$Digit)-1

        ;;---Number of digits: 6
        If StringLen($OTP) < 6 Then
                $a = 6 - StringLen($OTP)
                If $a = 6 Then $OTP = '000000' & Mod($tenVal,10^6)
                If $a = 5 Then $OTP = '00000' & Mod($tenVal,10^6)
                If $a = 4 Then $OTP = '0000' & Mod($tenVal,10^6)
                If $a = 3 Then $OTP = '000' & Mod($tenVal,10^6)
                If $a = 2 Then $OTP = '00' & Mod($tenVal,10^6)
                If $a = 1 Then $OTP = '0' & Mod($tenVal,10^6)
        EndIf

        GUICtrlSetData($Edit1, 'Basic' & @CRLF & '===================' & @CRLF & '$unixTime: ' & $unixTime & @CRLF & 'unix to time: ' & _DateAdd('s',$unixTime,"1970/01/01 00:00:00") & @CRLF & '$time: ' & $time & @CRLF & @CRLF & 'wraithdu code' & @CRLF & '===================' & @CRLF & 'ConsoleWrite(_GenerateTOTP("JBSWY3DPEHPK3PXP") & @CRLF):' & $easy0 & @CRLF & '_GenerateTOTP:' & $easy1 & @CRLF & @CRLF & 'my code' & @CRLF & '===================' & @CRLF & '$counter: ' & $counter & @CRLF & '$T: ' & $T & @CRLF & '$key: ' & $key & @CRLF & '$Sbits: ' & $Sbits & @CRLF & '$tenVal: ' & $tenVal & @CRLF & '$OTP: ' & $OTP)

EndFunc


Func _SetWindowPos($hWnd,$x,$y,$cX,$cY,$hWndInsertAfter = -1,$wFlags = 1)
    DllCall("user32.dll","long","SetWindowPos","long",$hWnd,"long",$hWndInsertAfter,"long",$x,"long",$y,"long",$cX,"long",$cY,"long",$wFlags)
EndFunc;==>_SetWindowPos

 

Edited by Lefiya

Share this post


Link to post
Share on other sites
argumentum
5 hours ago, Lefiya said:

And   why use   >>  ConsoleWrite(_GenerateTOTP("JBSWY3DPEHPK3PXP") & @CRLF)   ==   8

The " == 8", I do not see anywhere AND would be useless. The StringLen($OfGenertatedTOTP) would be 6 anyway ( as declared in the example).
As long as the 2 devices ( the server side and client side ) have the same time ( UTC time ), the original code shows to perform as advertised.

PS: The " == 8" is because you added the "@CRLF" in your implementation and ConsoleWrite reports to have written 6 ( $OfGenertatedTOTP ) + 2 ( @CRLF ). 
The rest I can not understand, as I am not a programmer. Just a kind soul to copy and paste to see if it works, or not.    =/
I can no longer help. And since you are attempting a new implementation, kindly use the help and support forum, referencing this topic.

  • Thanks 1

Share this post


Link to post
Share on other sites
Lefiya
7 minutes ago, argumentum said:

“== 8”,我什麼都看不到,也沒用。無論如何,StringLen($ OfGenertatedTOTP)將為6(如示例中所聲明的)。
只要2個設備(服務器端和客戶端)具有相同的時間(UTC時間),原始代碼就會顯示為廣告執行。

PS:“== 8”是因為您在實現中添加了“@CRLF”,而ConsoleWrite報告中寫入了6($ OfGenertatedTOTP)+ 2(@CRLF)。 
其餘我無法理解,因為我不是程序員。只是一個善良的複制和粘貼靈魂,看看它是否有效。= /
我再也無法幫助了。由於您正在嘗試新的實施,請使用幫助和支持論壇,引用此主題。

thank you~ for everything^^

i think maybe demo site server time and i different... 

Can i  inquire you about Your location?(UTC+?)

i want to try it~

thank you very much~

Share this post


Link to post
Share on other sites
JLogan3o13

@Lefiya in the future please just hit reply rather than quoting everything everyone says. We know what we said, we were there when we said it :)


√-1 2^3 ∑ π, and it was delicious!

How to get your question answered on this forum!

Share this post


Link to post
Share on other sites
argumentum
34 minutes ago, Lefiya said:

Can i  inquire you about Your location?(UTC+?)

Mine is "-5" but read about UTC and you will see that is not important if I'm in "-5" and you are in "+8", as we are plus or minus of the reference. 

PS: 

ConsoleWrite('GetMyTimeUTC() = "' & GetMyTimeUTC() & '"' & @CRLF)
Func GetMyTimeUTC()
    Local $tTime = _Date_Time_EncodeFileTime(@MON, @MDAY, @YEAR, @HOUR, @MIN, @SEC)
    Local $tLocal = _Date_Time_LocalFileTimeToFileTime($tTime)
    Return _Date_Time_FileTimeToStr($tLocal, 1)
EndFunc   ;==>GetMyTimeUTC

PS2: https://www.autoitscript.com/forum/topic/182902-autoit-real-time-server-with-inet/?do=findComment&comment=1313452

Edited by argumentum
  • Thanks 1

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×