How To: Cross Platform File Encryption with Delphi
Overview
A real-world issue is making sure our data, whether it is settings or user files, that are encrypted, not readable, and if edited become invalid. For achieving this we are going to dive into this hands-on tutorial on encryption using Delphi and an open source library.
Design
This tutorial is going to design a file saving and loading for a simple Todo application. The UI design will contain a Memo field (a large scrollable text field) that we will use for logging and application output. The application will also have two buttons, one for saving the encrypted file and one for loading that file. The model will contain one interface ITodo and three classes: TTodoError class, a TTodoItems class, and a TTodoList class. Each class will inherit from the ITodo interface. Also, each of these classes will be marshable and use the Delphi built-in Json parsing and encoding libraries.
NOTE: We are NOT going to do a full application or MVVM design, the main focus will be on the encryption and decryption of JSON files. This is more of a piece meal approach to adding a new tool on that belt.
Dependencies
We are going to use two libraries, and will describe each one and why were are using them.
Delphi Encryption Compendium
We are going to leverage Delphi Encryption Compendium (DEC), a library for Delphi, that contains cryptographic algorithms that we are going to use for our data encryption/decryption. This library has many other uses and I encourage the reader to download it and experiment with it.
https://github.com/MHumm/DelphiEncryptionCompendium
To install the library download the repo, then open up the SetDEPaths.dproj on your Delphi IDE, and then run the project in Release mode.
HINT: Make sure you open the project in the install folders, then execute it in both Debug and Release modes in that order.
Skia4Delphi
For the UI, we will also use Skia4Delphi, which is a great library that supports SVGs, and better Labels, and improves the performance across your UI. You can find it here, and it is straightforward to install through the installer.
https://github.com/skia4delphi/skia4delphi
TIP: This library is a new must when developing apps, look at the Lottie file support to add some juice to your applications for both VCL and FMX
Now that we have a design and the libraries are installed, we should be ready to go!
Implementation
The meat of the article happens here, we will take our design and turn it into a real application. We should have a project folder, in my case I named the folder "appwriter-todo". The folder will contain a Project, Resources, and Src subfolders.
HINT: it is best to have our folder structure laid out before working on any code as it helps keep our project well organized.
Project Setup
We will create a Blank Multi-platform project by going to the Project > Add New Project... menu item, then selecting Blank Application under the Delphi > Multi-Device section of the left panel as shown in the image below.
This will give us an empty Form and a starting project. The Form will be used as our base Form to inherit from in our application. The Base Form will have a TRectangle for the content and will contain a TRectangle, with a title and a back button, and a TScrollBox where all the content will be added. The Base Form should have the following structure:
The Base Form will be modified and saved as Base.pas under the Src > Base folder, and it will look as follows:
Our project will be saved in the Todo > Project folder as AppWriterTodo.dproj.
We are using a lot of the Skia components on this project, mainly the TSkSvg, TSkLabel as it gives us a greater ability to customize the look and feel of our application.
NOTE: Make sure you right clicked on your project and selected "Enable Skia" on the popup menu.
We will now add two Unit1.pas, one for our settings and another one for our data model. We will save our settings class as Todo.Utils.Settings.pas in the Src > Utils, and our data model as Todo.Model.Todo.pas in the Src > Model folder. The last class we will have be the main application Form, we will go to File > New > Other... and select the Delphi > Inheritable Items and select the FrmBase as our "template" Form. We will save this Form as Todo.Form.Main.pas and save it in our Src folder. Finally, our folder structure for this project should be reflected on the Projects panel as shown below.
Now we are going to modify our Main Form and add a TMemo field, a TRectangle to use as a Save button, a TRectangle to use as a Load button, and we are going to change the visibility of the BtnBack on the RctHeader to False. Arranging the components will give you the following UI:
HINT: to achieve the corner on the TRectangle, make sure to change the CornerType to "Round", then the XRadius and YRadius to some value (I used 8).
containing the following Structure:
Selecting a TSpeedButton, such as the BtnLoad, and under the Object Inspector select the Events tab and double-click on the OnClick event. This will bring up a section on the code that will execute every time the user clicks on the button. We will do the same for the BtnSave, and finally, we will similarly select the FrmMain and do an OnCreate event. Our skeleton code should look like the one below:
Running the UI you will have a simple application with no visible actions:
NOTE: this might not look like much, but getting here is a big effort push as it has a working piece of code that builds your UI.
Most of the UI is completed at this point, so now we need to work on the data model class, and the encryption/decryption class, and then wrap it up by updating the UI code stubs.
Data Model
We are going to edit the Todo.Model.Todo.pas file in the Src > Model folder. We will create an interface object called ITodo, that will be inherited from our objects. Our data model objects will inherit from the TInterfaceObject and our custom ITodo interface.
HINT: the TInferfaceObject allows our class to use reference counting and memory management. It helps for the lifetime management of our objects, and when the reference count goes to zero our object is destroyed.
Our design will contain three classes: TErrorTodo, TItemTodo, and TListTodo.
TErrorTodo - contains an error message, and provides a ToString method.
TItemTodo - contains a string for the Task, and a Boolean variable to check if IsCompleted**.
TListTodo - contains a string for the Name of the list of items todo, as well as a TList of TItemTodo objects.
All our objects will contain a method for GetSelf, which returns a TObject, helping us convert between interface references and the actual object itself. All the variables are annotated to provide JSONMarshalling and their respective JSONName.
Our defined class types are as follows:
Below we show how we implement our TErrorTodo class type, we leave the rest of the classes for the user as an exercise.
TIP: remember to create and destroy the TList object in the constructor and destructor.
NOTE: The full project code source to the repository with the full implementation will be at the end of the article targeting Delphi 11 (Alexandria).
Encryption / Decryption
This section will focus on adding a few helper class methods (class methods are static to the class and do not require and instance) for file I/O across different platforms, loading the TListTodo, saving the TListTodo, and encrypting/decrypting stringified JSON.
NOTE: we are encrypting strings, so you can use this with ANY string. In this sample project, we are focusing on encrypting a JSON string.
For file I/O we are going to have the following methods:
GetSettingsFolder - to obtain the folder where we want to store our files. This method will create a folder for our project if it does not already exists. This method will contain platform-specific code because on MacOS and iOS we need the Library path to store our files, but in Windows and Android we need the Home path instead.
GetDefaultSettingsFilename - to obtain the file name with the full path.
SaveTodos - takes a TListTodo object instance, converts the object into a JSON string, then calls the OnCipherEncodeAndSave providing the full path and JSON stringified object.
LoadTodo - calls the OnCipherDecodeAndLoad providing the path to the file to load and decrypt, then received a stringified JSON, and converts it to a TListTodo object to return.
OnCipherDecodeAndLoad - creates a TCipher_TwoFish object, reads all the raw bytes from the specified file, initializes the cipher key, sets the cipher mode, decodes the loaded file, and finally returns the string containing the stringified JSON.
OnCipherEncodeAndSave - creates a TCipher_TwoFish object, saves the stringified JSON to a .json file if in DEBUG mode, initializes the cipher key, sets the cipher mode, converts the stringified JSON to bytes, encodes the bytes, saves the bytes to the provided file path, verifies the saved file, and finally returns the length of characters of the stringified JSON.
The implementation of these methods is provided below and as an exercise creating the definition of the class.
NOTE: the TTodoProgSettings.pas file becomes a useful utility class to help with other projects for file I/O.
Wrap up
This section will tie in the data model, encryption, and decryption of a sample object in the UI. We are going to fill in the stubbed methods as follows:
FormCreate - will create a sample TListTodo object, populate it with two TItemTodo objects, and provide feedback by adding to the memo field the string version of the TListTodo, using the ToString method we implemented.
BtnSaveClick - will inform the user that we are "Saving..." through the memo field, print the settings folder path, print the full path to the settings filename, change the name of the TListTodo object to "test me", and finally save the object (using the SaveTodos class method in the TTodoProgSettings class).
BtnLoadClick - will inform the user that the application is "Loading..." through the memo field, then add the path to the settings folder, load the TListTodo object (through the LoadTodos class method of our TTodoProgSettings class), assign it to our class variable Todos, and print the name of the loaded object to the memo field.
These are all basic functions needed for any Delphi application that needs to load and save data, but it is critical to do so the correct way so that your application can create, load, and save data across different platforms.
Conclusion
If you have followed all the steps or downloaded the working Git repo, you will have an application that takes a data model, encodes it to JSON, encrypts the data, and saves it to disk in a safe manner. This code works cross-platform, and you can have this on the following platforms: Windows (32 and 64 bits), Android (32 and 64 bits), MacOS, and iOS. The application should look like the Gif below, and in this case, it is the Windows version on 64 bits.
You can find the full source of the project in this Github repo https://github.com/an01f01/delphi_todo_deencrypt/tree/main
Happy Coding!
References
https://blogs.embarcadero.com/this-is-how-to-store-cross-platform-app-settings-in-json/
https://docwiki.embarcadero.com/RADStudio/Alexandria/en/TInterfacedObject
Subscribe to my newsletter
Read articles from Alessandro directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Alessandro
Alessandro
I am a developer from the Greater Boston area, I have a M.S. in Computer Science and Engineering from the University of Connecticut, focused on Bayesian Networks and Bayesian Knowledge Bases. I have experience working on 3D laser scanners, motion platforms, DARPA projects, 3D simulations using the Unity game engine for robotics in synthetic environments (game generated and scanned), mobile development in both Android and iOS. My strengths are in rapid prototyping and taking an idea into a release candidate. The process is a winding road that takes a lot of time