FoxPro Programming
Multi-Factor Authentication In VFP
Gravatar is a globally recognized avatar based on your email address. Multi-Factor Authentication In VFP
  J Ryan
  All
  Oct 26, 2022 @ 06:15pm

Interested in MFA (Multi-factor Authentication) aka 2FA for your Web Connection App?

Currently, the TOTP (Time-based One-Time Password) is regarded as one of the more secure 2FA options. Basically a TOTP is a code that updates regularly so that codes can't be reused and (hopefully) aren't available to a hacker. Some VFP developers have already deployed systems that send a TOTP via SMS or email... but both models are now regarded as less secure.

Here's a TOTP implementation that uses Google Authenticator and could take only a few minutes to deploy. You don't need to store email addresses or cell phone numbers or actually anything extra if you have a user table with one or more static fields.

Google Authenticator generates a new TOTP every 30 seconds without need for mobile or internet connectivity. The validation code follows RFC-6238 which is the industry-standard TOTP protocol.

The process is:

  • User installs Google Authenticator on their device.
  • To activate 2FA, the VFP App displays a QR code to the user.
  • The user scans the QR code using Google Authenticator. This creates an Application account in Google Authenticator, with a 6-digit validation code that updates every 30 seconds.
  • When logging in, the user is asked to enter their 6 digit code from Google Authenticator; they simple read the current validation code from their device.

Secret Code

Each user needs an 80-bit (10 character binary) secret code. This should be unique and treated like a password- so never stored, and ideally Hashed as required from static user fields. Ideally it would be hashed with more than just username, so it can't be easily guessed. It needs to use static fields because otherwise a data change will break the secret code and the user won't be able to authenticate.

A good example of a user secret code using vfpencryption.fll's Hash function would be something like

lcSecret=left(hash(username+ttoc(date_initiated+1276,1)+chrtran(rtrim(UPPER(username)),"AEIOU","@#$*&"),5),10)

(Yes this uses MD5 that is no longer considered cryptographically safe; but the resulting 80-bit/10 byte secret code doesn't care how impressively long the hash was that it came from)

Note that many 2FA systems that use Google authenticator use the Google Charts API to generate the QR code, followed by another url to validate what the user enters. This breaches cryptographic principles, since it passes the user's secret code across the internet to a third party. In addition, Google deprecated the Google Chart API in 2012 so while it works today, there's no guarantee it will tomorrow.

This VFP implementation produces the QR code locally and does not pass the user's secret code to any third party.

QR Code creation is easy using VFPX's very nice QR Code project: https://github.com/VFPX/FoxBarCodeQR

Note that in this code, Application, User and Issuer are only used for the account label in Google Authenticator, not for authentication. Only the secret code is used for authentication. So you can experiment with these other parameters to see how they affect the Google Authenticator label.

Also, your binary secret code needs to be converted to Base32 encoding for the barcode; this is handled in the attached code.

To display the QR code:

ShowQR("MyApp","Username","BinarySecr","MyApp")  &&10-byte binary secret code

function ShowQR(lcApp,lcUser,lcSecret,lcIssuer)

SET PROCEDURE TO FoxBarcodeQR.prg ADDITIVE
LOCAL lcString,loFbc,lcQRImage

loFbc = CREATEOBJECT("FoxBarcodeQR")

*---consider transforming user for security
lcuser=LEFT(m.lcuser,1)+"***"+RIGHT(RTRIM(m.lcuser),1)

lcString="otpauth://totp/"+m.lcApp+":"+m.lcUser+;
	"?secret="+Base32Encode(m.lcSecret)+"&issuer="+m.lcIssuer

lcQRImage = loFbc.FullQRCodeImage(lcString, , 333)

*-- Create form
LOCAL loForm AS FORM
loForm = CREATEOBJECT("Form")
WITH m.loForm
  .WIDTH = 600
  .HEIGHT = 600
  .AUTOCENTER = .T.
  .ADDOBJECT("Image1", "Image")
  WITH .Image1
    .WIDTH = 600
    .HEIGHT = 600
    .STRETCH = 0
    .PICTURE = m.lcQRImage
    .TOP = 20
    .LEFT = 20
    .VISIBLE = .T.
  ENDWITH
  .SHOW(1)
ENDWITH

Function Base32Encode(tcString as String) as String
   Local lnCode, lnEncoded, lcEncoded, lnBits, ln5Bits
   lcEncoded = ''
   lnEncoded = 0
   lnBits = 0
   Do While !Empty(tcString)
      lnCode = Asc(tcString)
      tcString = Substr(tcString,2)
      lnEncoded = BitLshift(lnEncoded,8)+lnCode
      lnBits = lnBits + 8
      Do While lnBits>=5
         * Take the highest 5 bits of the so far encoded value.
         ln5Bits = BitRshift(lnEncoded,lnBits-5)%32
         lcEncoded = lcEncoded + Chr(IIF(ln5Bits<26,65+ln5Bits,24+ln5Bits))
         lnEncoded = lnEncoded % 128
         lnBits = lnBits - 5
      EndDo
   Enddo
   Return lcEncoded
EndFunc   

This uses a VFP form, but for a Web Connection app you'd presumably use something like a Bootstrap Modal Dialog.

If you scan the displayed QR code in Google Authenticator, you'll see a new Application account with a 6-digit validation code that updates every 30 seconds.

Application Validation

Obviously your VFP app needs to let the user enter their validation code, then validate it.

Some implementations submit code and secret to a Google or 3rd party URL, but that again results in the secret code being shared. Instead, just a few lines of code and Craig Boyd's vfpencryption.fll allows your app to validate locally.

If you run the following code, it should match the validation code you see in Google Authenticator. Note that this uses VFPA's SYS(9028) function to generate UTC time; Web Connection comes with a VFP9 function to do the same if you're not using VFPA. The code also uses the HMAC() function in vfpencryption.fll.

? CurrentRFC6238("BinarySecr")

Function CurrentRFC6238(lcSecret)
  *Set Library to vfpencryption.fll
   Local lnMessage,lcMessage, lcHash, lcTruncatedHash, lnCode
   lnMessage = Floor((SYS(9028)-{^1970-01-01:00:00})/ 30) &&Unix time
   lcMessage = Replicate(Chr(0),4)+BinToC(m.lnMessage,'4S') 
   lcHash = HMAC(m.lcMessage,m.lcSecret, 1)
   lnOffset = Asc(Right(lcHash,1)) % 16
   lcTruncatedHash = SubStr(m.lcHash,m.lnOffset+1,4)
   lnCode = BitAND(CtoBIN(m.lcTruncatedHash,'4S'),0x7fffffff)
   return PadL(m.lnCode % 1000000,6,'0')
EndFunc

NOTE!!!!!!! As this is a time-sensitive code, the DateTime on the Google Authenticator device needs to be synced with the VFP app's PC DateTime. Time zone doesn't matter since machine time gets converted to UTC time- but UTC time needs to match or else the validation fails and the user won't be able to log in, causing urgent support requests. If validation fails, a common response is to check a few 30-second intervals before and after in case there's internet/network latency or slight time drift; this does make it easier to brute force/guess the validation code, but only if users are allowed multiple attempts.

Summary

The above few lines of code allow VFP developers to implement 2FA in their apps with relative ease. The implementation uses Google Authenticator and industry-standard RFC-6238, and does not share or transmit the user's secret code.

Is Google Authenticator Secure?

There are criticisms, since it uses the default RFC-6238 SHA-1 and has included SMS or emailed credentials in the past. Theoretically the QR code url can include additional parameters for cryptographic strength (e.g. SHA128/256) and validation code lengths longer than 6, but those parameters appear to be ignored by the Google authenticator application.

Gravatar is a globally recognized avatar based on your email address. re: Multi-Factor Authentication In VFP
  Rick Strahl
  J Ryan
  Oct 26, 2022 @ 08:46pm

Thanks for posting this John.

I haven't spent any time looking at this, so this was an interesting read. I was actually unaware that this can be done without a third party involved to provide the code to use in the Authenticator.

FWIW, I don't think you need to use Google Authenticator. You can use Authy or Microsoft Authenticator or any other Auth app you want to I believe for this to work. I use Authy personally for all of my 2F authentication.

Need to take a look at this more closely...

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: Multi-Factor Authentication In VFP
  J Ryan
  J Ryan
  Oct 27, 2022 @ 12:18pm

Hi Rick,

Yes, you can use any authenticator that uses the RFC-6238 TOTP standard- which is most of them.

Incidentally, if you disconnect your device from mobile and internet, you can still scan the QR code into Google Authenticator and get the validation code. It's not phoning home to Google. If you completely deny network access in settings, Google Authenticator still works fine AFAICS. It appears that it's a pretty simple local app that creates an account on the device when you scan the QR code, then regenerates the RFC-6238 every 30 seconds while the app is open.

Not what we're told to expect from Google- but it is a pretty old app, and/or maybe this isn't particularly useful data...

I think it is an issue that so many implementations still use the online Google Charts API to generate the QR code, which does share the app name, user and secret code with Google. These days there's a few dotNET assemblies you can use to generate the QR code locally, but actually it's pretty simple in a few lines of VFP code thanks to that XFRX QR Code project. Incidentally there's a padding bug in the Base32Encode function that people won't see if the secret code is exactly 80 bits as needed by Google Authenticator.

Regards, J

© 1996-2022