Level Extreme platform
Subscription
Corporate profile
Products & Services
Support
Legal
Français
Two-Factor Authentication for VFP
Message
From
26/10/2022 20:31:02
John Ryan
Captain-Cooker Appreciation Society
Taumata Whakatangi ..., New Zealand
 
 
To
All
General information
Forum:
Visual FoxPro
Category:
VFPX/Sedna
Title:
Two-Factor Authentication for VFP
Miscellaneous
Thread ID:
01685145
Message ID:
01685145
Views:
98
Likes (2)
Interested in MFA (Multi-factor Authentication) aka 2FA for your VFP 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 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.

This implementation uses Google Authenticator that 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, only ever Hashed. 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   
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 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; Rick Strahl's Web Connection and other resources have VFP9 functions 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. If validation fails, a common response is to check a few 30-second intervals before and after in case there's 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.
"... They ne'er cared for us
yet: suffer us to famish, and their store-houses
crammed with grain; make edicts for usury, to
support usurers; repeal daily any wholesome act
established against the rich, and provide more
piercing statutes daily, to chain up and restrain
the poor. If the wars eat us not up, they will; and
there's all the love they bear us.
"
-- Shakespeare: Coriolanus, Act 1, scene 1
Next
Reply
Map
View

Click here to load this message in the networking platform