Google Authenticator in PowerShell

PS C:\> Import-Module c:\downloads\GoogleAuthenticator.psm1
PS C:\> Get-GoogleAuthenticatorPin -Secret XSFOC6D37UW6JOJ5 | Format-List

PIN Code          : 030 191
Seconds Remaining : 27

But where do you get that secret code?

Relevant links:

Google Authenticator is a 2-Factor Authentication (2FA) system, with an app that generates codes like this:

Google Authenticator iPhone App Screenshot

I wanted to generate that PIN code in PowerShell.

A basic website login has a username and password; anyone in the world who steals your password can get into your account. Google Authenticator 2FA adds another code from a smartphone app, and now anyone logging in needs to know your password and have your smartphone.

Behind the scenes, there is another secret stored against your user account and shared between the server and your smartphone. It gets there through a QR code, here’s an example of a basic logon and enabling 2FA:

Example of basic website logon form vs combined 2FA signup and logon form

If you have a signup for one of these Google 2FA sites, visit the online QR code reader linked above and see that the QR code contains text in the Key Uri format like this:

                                      this is the secret

The first thing this PowerShell module can do is generate one of these random secrets, wrap it in this otpauth:// style link, then embed that in a link to Google Charts to show the whole thing as a QR code.

PS C:\> Import-Module c:\temp\GoogleAuthenticator.psm1
PS C:\> $Secret = New-GoogleAuthenticatorSecret -Online    # online will open a browser window
PS C:\> $Secret | Format-List

Secret    : XSFOC6D37UW6JOJ5
KeyUri    : otpauth://totp/
QrCodeUri :

You can scan the QR code in the app to add your new token.

Once you have the secret code, either generating your own (for fun) or taken from a real website’s QR code for your account, you can generate the current PIN code for it:

PS C:\> $Secret | Get-GoogleAuthenticatorPin | Format-List

PIN Code          : 030 191
Seconds Remaining : 27

I do not know of a way to get the secret out of the Google Authenticator app, but it does have a bit of a weakness - if you are enabling 2FA you can scan the QR code with as many devices as you want and they will all generate the same PIN codes.

So disable/re-enable, or reset, 2FA on your account (careful not to lock yourself out), setup your normal login again with a new QR code and add that to the app - but also take a copy. Now you can get the secret out of the QR code URL, and generate the PIN from PowerShell.

NB. that this is a lot like having a password in plain text, and should be treated as carefully - store it somewhere safe, preferably an encrypted password vault, etc.

Playing with this code / the QrCodeUri and Google Charts would also let you add your serious website secret to the app, but with a more amusing name. Re-do your 2FA on an account to get a new QR code, and get the otpauth:// text out of it, and pic the secret code out of that, then generate a new QR code for it with your custom username and company name to show up in the app:

PS C:\> $authParams = @{
  UseThisSecretCode = 'HP44SIFI2GFDZHT6' 
  Issuer = "My bank 💎"
  Name   = ""
  Online = $true

PS C:\> New-GoogleAuthenticatorSecret @authParams | fl *

Secret    : HP44SIFI2GFDZHT6
KeyUri    : otpauth://totp/
QrCodeUri :[..]

web browser opens, and you can scan your bank code into the app, with new text around it.

On the PowerShell side of things, I started out with this StackExchange explanation of the algorithm, and trying to port this GoLang code, and after getting code which looked OK but generated the wrong output, I worked from this C# code

The algorithm itself is not very complex, but I did trip over a lot of minor problems:

  • Intel CPUs / dotnet on Windows is little-endian byte order, the algorithm needs big-endian byte order
  • Base32 encoding is involved and that means turning 8-bit-bytes into 5-bit-chunks, and I didn’t want to write a whole lot of bit manipulation.
  • BigInteger class helped, but it stores bytes little-endian inside and has occasional padding for handling two’s complement negative numbers.
  • After trying some combinations of BitConverter, and starting to port a page of C# code just for byte work, I switched to use regex and “binary” strings instead.
  • [BitConverter]::GetBytes($time) was trying to hit the overload for [char] until I added a [int64] type, and it was sometimes working.
  • I can’t type the alphabet correctly and put “VXWYZ”, so some codes which included X or W would cause the wrong output.
  • dotNet has a mess of methods for URI Encoding, 8 different ones for different framework versions, which are variously broken, incompatible with RFCs, or non-standard.
  • (I’m not sure I picked a good one)

NB. Google Authenticator app, and the TOTP protocol support more features than I have coded - other window sizes, other hash algorithms.

Written on March 7, 2019