Has your client or boss ever asked you if you could send and receive encrypted information? It's easier than you think! Alex Feldstein had the need for such a tool, and with the power of Visual FoxPro and the Windows Crypto API, he found that it was fun and easy to build.
Building the tool
In my organization I had the need to be able to communicate with clients, sending data files with confidential information. The requirement was for a simple-to-use tool that we could automate (that is, generate the encrypted files programmatically) and then send them as e-mail attachments.
We looked at different commercial alternatives, like Entrust, F-Secure, and PGP. Although each one had its pros and cons, we decided against them, because the licensing costs for installation for clients would have been too expensive. Our clients are overseas, and we didn't want to deal with the key management issues of public keys. I won't go into details about public vs. private key cryptography, as that would be outside the scope of this article, but I will point you to some references at the end.
Our requirements were for a simple, standalone tool that allowed us and our clients to encrypt and decrypt files using strong cryptography. I proposed a system with private keys using the Triple-DES algorithm. This was deemed sufficient for our needs.
A system with private keys using the Triple-DES algorithm deemed sufficient for our needs
What I needed was a way to encrypt data and send the encrypted file as an attachment, with the recipient being able to decipher this information. I also needed the ability to bypass the tool entirely and programmatically create and send the encrypted data files with the program that does a nightly process of reams of data.
Since version 7.0 Visual FoxPro includes a class library as part of the FoxPro Foundation Classes (FFC) and makes it easy to access the wealth of cryptography routines built into Windows. This library resides in _CRYPT.VCX and is easily accessible through FoxPro's Component Gallery. It comes with a ready-to-run sample form that allows for different types of encryption and decryption routines. You should open your copy and examine it. It may be intimidating at first, but you don't have to have a thorough understanding of the algorithms involved, or be a cryptographer or mathematician to be able to put these to use.
This simple-to-use program allows a user to select a file to encrypt. Any type of file, from text to pictures, will do. Using the Windows Crypto API and a password or pass phrase from 6 to 60 characters for added security, the user can then save the file in an encrypted format, which can then be sent by e-mail securely. The password used would have to be sent by a separate channel, of course.
Since version 7.0 Visual FoxPro includes a class library that makes it easy to access the wealth of cryptography routines built into Windows
I'll show you, in detail, the tool I built to do what our organization needed.
First, open Visual FoxPro (I used VFP 8 SP1, but any version from VFP 7.0 on will suffice). Create a project called "Encryptor". In it we'll build one form and one small main program. We'll also include the _CRYPT.VCX class library supplied with VFP.
*====================================================
* Program: Main.prg (Encryptor)
* Purpose: Simple wrapper around
* Visual FoxPro's FFC\_crypto.vcx
* Author: Alex Feldstein
* Copyright: Public Domain
* Released: 09/24/2003
* Modified: 12/02/2003
* Parameters: depends on method called
* (generally string_to_encrypt, passphrase)
* Returns: encrypted/decrypted string
* or an error message as char
* Notes: passphrase is case-sensitive
*====================================================
If Version(2) > 0
* in Development environment
Application.Visible = .T.
Else
* in Production: hide VFP screen
Application.Visible = .F.
EndIf
Set Safety off
Set Talk Off
Set Deleted On
DO form Encryptor
Read Events
If Version(2) > 0
* in Development environment
Application.Visible = .T.
Set Resource on
Else
* in Production
Release All
Quit
EndIf
Create a new form, based on the VFP base form class or any other plain form you normally use as your base. Let's call it "Encryptor".

Add four text boxes and labels as shown above. I also added command buttons for Encrypt, Decrypt, and Exit, and two small ones in the top right area for the user to pick the file to process.
As an option, I included a timer to do a little trick – or an "Easter egg," if you will – showing a "Built with VFP" logo. More on that later.
Below is the code for the form.
#define CRLF CHR(13)+CHR(10)
* set default values to custom form properties
This.nTimeOut = 20000
This.nMinimumPasswordLength = 6
This.cEncryptTextFile = "
This.nAttempts = 0
This.oCrypto = NewObject("_cryptapi", Home(1)+"ffc\_crypt.vcx")
If Vartype(this.oCrypto) != "O"
MessageBox("Can't load Windows "+ "encryption routines!"+;
CRLF + CRLF + ;
"make sure you have Windows 2000 or " +;
"Windows XP installed.",16,;
"Error", ThisForm.nTimeOut)
Return .F.
EndIf
With ThisForm
* enable/disable starting controls
.timer1.Enabled = .F.
.BorderStyle = 2 && Windows Fixed Dialog
.cmdSelectFile.Default = .T.
.cmdSelectFile.SetFocus()
.chkZIP.Visible = .F.
.txtPass2.Visible = .F.
.txtPass2.Enabled = .F.
.lblPass2.Visible = .F.
.lblPass2.Visible = .F.
.lblNote1.Visible = .F.
.cmdSelectOutput.Visible = .F.
EndWith
If not File("Encryptor.INI")
* INI file is not there.
* Assume this is the first time the app is run
* create default INI file. Will be used
* to persist last folder used
StrToFile("[Data]" + CRLF + "datafolder=." + CRLF,"Encryptor.INI")
EndIf
frmEncryptor.Refresh()
#define CRLF CHR(13)+CHR(10)
With ThisForm
.cmdSelectOutput.Visible = .F.
DO Case
Case Empty(.txtFile.Value) or Empty(.txtOutputFile.Value)
.cmdEncrypt.Enabled = .F.
.cmdDecrypt.Enabled = .F.
Case Upper(JustExt(Alltrim(.txtFile.Value))) = "ENC"
* encrypted file as input
.chkZIP.Visible = .F.
.txtPass2.Visible = .F.
.txtPass2.Enabled = .F.
.lblPass2.Visible = .F.
.lblNote1.Visible = .T.
.lblOutput.caption = "Output Folder"
.cmdEncrypt.Default = .F.
.cmdEncrypt.Visible = .F.
.cmdDecrypt.Enabled = .T.
.cmdDecrypt.Default = .T.
.cmdDecrypt.Visible = .T.
.cmdSelectOutput.Visible = .T.
Case Upper(JustExt(Alltrim(.txtOutputFile.Value))) = "ENC"
* encrypted file as output
.chkZIP.Visible = .T.
.txtPass2.Visible = .T.
.txtPass2.Enabled = .T.
.lblPass2.Visible = .T.
.lblNote1.Visible = .F.
.lblOutput.caption = .lblOutput.Tag
.cmdDecrypt.Default = .F.
.cmdDecrypt.Enabled = .F.
.cmdDecrypt.Visible = .F.
.cmdEncrypt.Visible = .T.
.cmdEncrypt.Default = .T.
.cmdEncrypt.Enabled = .T.
.cmdSelectOutput.Visible = .F.
EndCase
EndWith
Listing 1: frmEncryptor.Init()
* encrypt():
* Encrypt should check password entered
* two times to see they match,
* as the passwords are masked on screen.
* This is not needed for decryption,
* as it already fails if wrong password
* entered. For Encryption (file creation),
* user need to be sure that what they
* typed is what they wanted.
Local lcInFile, lcOutFile, llZIPed
lcInFile = "
lcOutFile = "
ThisForm.cZIPFile = "
With ThisForm
If Empty(.txtFile.Value)
MessageBox("Nothing to do!", 16,"No file",.nTimeOut)
Return .F.
EndIf
If Empty(.txtOutputFile.Value)
MessageBox("No output file defined!", 16,"No file",.nTimeOut)
Return .F.
EndIf
* make sure both encryption passwords match
If .txtPassPhrase.Value == .txtPass2.Value
* it's a match!
Else
MessageBox("Passphrases do not match!", 64,"Wrong password")
Return .F.
EndIf
If not .CheckPwd()
Return .F.
EndIf
lcInFile = ["]+Alltrim(.txtFile.Value)+["]
lcOutFile = ["]+Alltrim(.txtOutputFile.Value)+["]
* if user chooses checkbox to compress,
* we ZIP, then encrypt.
* usage: ThisForm.DoZIP(,
* )
* Sys(2023) returns temp folder name
If ThisForm.chkZIP.value = 1
ThisForm.cZIPFile = ["]+Addbs(Sys(2023))+;
JustStem(lcInFile)+".zip"+["]
llZIPed = ThisForm.DoZIP(lcInFile, ThisForm.cZIPFile)
If llZIPed
* if ZIP succeeded then point to
* ZIPped temp file
* (to be deleted later)
lcInFile = ThisForm.cZIPFile
EndIf
Else
llZIPed = .F.
EndIf
.oCrypto.ldisplayhighlevelapierrors = .F.
If not .oCrypto.EncryptSessionStreamFile(lcInFile, ;
Alltrim(.txtPassPhrase.Value), lcOutFile)
* delete temp ZIP file (if any)
If File(ThisForm.cZIPFile)
Erase (lcInThisForm.cZIPFileFile)
EndIf
Return .F.
EndIf
.CleanUpForm()
If llZIPed
* delete temp ZIP file (if any)
If File(lcInFile)
Erase (lcInFile)
EndIf
MessageBox("File encrypted and compressed", 64,"Encryptor")
Else
MessageBox("File encrypted", 64,"Encryptor")
EndIf
EndWith
Listing 2. The encrypt method
* decrypt():
Local lcInFile, lcOutFile, lnHandle
Local lcZIPFile, lcBytes, llZIPped, llOK
If This.nAttempts > 3
* if more than 3 failed password
* attempts to decrypt,
* inform user and quit in 10 seconds.
* This is mainly to delay
* a potential automated attacker.
MessageBox("Password failure!",16, "Password error",10000)
Quit
Else
With ThisForm
If not .CheckPwd()
This.nAttempts = This.nAttempts + 1
Return .F.
EndIf
If Empty(.txtOutputFile.Value)
MessageBox("No output file defined!", 16, "No file", nTimeOut)
Return .F.
EndIf
lcInFile = Alltrim(.txtFile.Value)
* if they picked a folder only (no filename),
* add random
lcOutFile = Alltrim(.txtOutputFile.Value)
If Empty(JustFname(lcOutFile))
lcOutFile = Addbs(lcOutFile) + Sys(3) + ".TXT"
EndIf
.oCrypto.ldisplayhighlevelapierrors = .F.
If not .oCrypto.DecryptSessionStreamFile(lcInFile, ;
Alltrim(.txtPassPhrase.Value), lcOutFile)
.CleanUpForm()
MessageBox("Error decrypting file!",16,"Encryptor")
Return .F.
EndIf
.CleanUpForm()
* we have decrypted, now check the first few bytes
* to see if it is a ZIP, in which case we unzip to
* original file.
* open Low Level and check first bytes
lnHandle = Fopen(lcOutFile,0)
If lnHandle = -1
* error. should never happen.
ThisForm.CleanUpForm()
MessageBox("Error uncompressing file!", ;
MB_ICONSTOP, "Error", ThisForm.nTimeOut)
Return .F.
EndIf
* read first 4 bytes
lcBytes = Fread(lnHandle, 4)
* check to see if it is a ZIP file
llZIPped = (lcBytes = "PK" + Chr(3) + Chr(4))
* close file
Fclose(lnHandle)
If llZIPped
* it is a ZIP, unzip it
* Rename file from what they wanted,
* to a ZIP
lcZIPFile = ForceExt(lcOutFile, "ZIP")
If File(lcOutFile)
Try
If File(lcZIPFile)
* erase a file of same
* name from a previous run
Erase (lcZIPFile)
EndIf
Rename (lcOutFile) to (lcZIPFile)
Catch
*
EndTry
EndIf && File(lcOutFile)
If ThisForm.DoUnZIP(lcZIPFile, lcOutFile) and File(lcZIPFile)
Erase (lcZIPFile)
Else
ThisForm.CleanUpForm()
MessageBox("Error uncompressing file!",16, "Error",;
ThisForm.nTimeOut)
Return .F.
EndIf
MessageBox("File decrypted and "+"decompressed to" + Chr(13) +;
JustPath(lcOutFile) + " folder.",64,"Encryptor")
Else
MessageBox("File decrypted to"+Chr(13)+;
lcOutFile,64,"Encryptor")
EndIf
EndWith
EndIf
Listing 3. The decrypt method.
The command buttons just call the form's methods Encrypt() and Decrypt() to do the work. The Refresh() methods of the form and buttons check to see whether the minimum password length has been entered to enable or disable the trigger buttons.
I mentioned a little "Easter egg" in the app. When you right-click on the Exit button, the picture changes for a few seconds to a "Built with VFP" logo, and the button's Click() gets changed to displaying the program's version by using AGetFileVersion() as follows:
* cmdExit.RightClick()
* show VFP logo and start timer to get rid of
* it after a few seconds
This.Caption = "
This.Picture = "FOXPOWERXSMALL.GIF"
ThisForm.timer1.Interval = 10000
ThisForm.timer1.Enabled = .T.
* cmdExit. Click()
Local laVersion[1], lcVersion
ThisForm.timer1.Enabled = .F.
If Upper(This.Picture) = "FOXPOWERXSMALL.GIF"
* show version while we are displaying fox logo
AGetFileVersion(laVersion,"encryptor.exe")
lcVersion = Alltrim(Transform(laVersion[4]))
MessageBox("Encryptor Version:" + lcVersion,64,"Encryptor",8000)
This.Picture = "exit.bmp"
Else
ThisForm.Release()
EndIf
Optional feature: ZIP compression
An optional feature that could be added is the ability to ZIP/UNZIP text files before encrypting. You can use a set of ActiveX controls or DLL libraries that do ZIP and UNZIP. I used DynaZIP in my production copy, but any one of the several available tools would do. It's important to compress (ZIP) the file before encrypting.
If you do it after, the encryption scrambles and randomizes the file in such a way as to render the compression algorithm useless. You could add a checkbox for the user to decide whether she wants to compress, or you could do it transparently every time. The trick is that when the recipient receives the file and has to apply it to a copy of this program, the program has no knowledge of the received file structure; therefore, it will have to check to see whether the file is encrypted or not.
After decrypting normally, with the pass phrase supplied, it will have to look at a few bytes in the file. Studying the ZIP format, I found that the first few characters are always the same. You'd open it with low-level functions and take a peek, like this:
* open Low Level and check first bytes
lnHandle = Fopen(lcOutFile,0)
If lnHandle = -1
* error. should never happen.
MessageBox("Error uncompressing file!", 16,"Error",ThisForm.nTimeOut)
Return .F.
EndIf
* read first 4 bytes
lcBytes = Fread(lnHandle, 4)
* check to see if it is a ZIP file
llZIPped = (lcBytes = "PK"+Chr(3)+Chr(4))
* close file
Fclose(lnHandle)
The characters to look for include "PK", which stands for Phil Katz, the late creator of the ZIP standard, and they're used by his own program, PKZIP, and such common Windows utilities as WinZIP.
The complete code included as a download contains the ZIP() and UNZIP() methods I used for DynaZIP. If you have DynaZIP, just drop the "dzocx11" and "duzocx11" controls anywhere on the form. I can't include the DynaZIP DLLs that are part of this commercial program. If you don't have or need DynaZIP, but want to use another similar product, just replace the code in these two methods.