FoxNotes Cryptographic Architecture
Software development security is not a state to be achieved and forgotten; it is a continuous process and a massive responsibility. When I began developing FoxNotes, my fundamental premise was absolute data sovereignty. This means that you, as the user, are the sole true custodian of your information.
To guarantee this promise, I decided that the application should not rely on the security of the operating system you use, nor on the security of the cloud environment where you choose to synchronize your files. Security must reside within the file itself and within your computer’s RAM during execution.
This document comprehensively details the algorithms, data structures, and architectural design decisions I have programmed into the FoxNotes cryptographic engine using C# (.NET 9 MAUI). We will analyze, step by step, everything from how I protect your password in volatile memory to the double (cascade) encryption I use to protect every single character of your plaintext.
1. The First Line of Defense: Protecting Your Password in Volatile Memory (RAM)
The weakest link in any cryptographic system is almost always how the application handles the master password typed by the user. In programming languages like C# (the .NET ecosystem), the basic data types we use for text, such as string, present a critical security risk: they are immutable and managed by a component called the Garbage Collector (GC).
Imagine RAM as a large whiteboard and the Garbage Collector as the janitor. When you type your password and it is stored in a normal string, it is like writing on the whiteboard with a permanent marker. You cannot erase it at will. If the value of the variable changes, .NET simply creates a new string on another part of the board and leaves the previous one lying there, orphaned, until the janitor (the GC) decides it is time to clean up.
This means that a memory dump, a cold-boot attack (where your RAM is frozen to extract data), or malware with access to the application’s memory space could extract your plaintext password long after you have stopped using FoxNotes.
To mitigate this severe attack vector, I have programmed a specialized class called SecurePasswordHolder.
The Implementation of SecurePasswordHolder
This class acts as a high-security container leveraging System.Security.SecureString. A SecureString does not write with a permanent marker; it encrypts its contents directly within RAM using the data protection tools of your own operating system (such as DPAPI on Windows). Furthermore, I force it to be allocated in “unmanaged memory”. In our analogy, this takes the password off the janitor’s whiteboard and places it inside a personal safe where the .NET Garbage Collector cannot move it or make accidental copies of it.
Let us look exactly at how I implemented the secure extraction in code:
C#
public byte[]? GetPasswordBytes()
{
if (_securePassword == null || _securePassword.Length == 0)
return null;
// 1. Allocation in unmanaged memory: Requesting the key to the safe
IntPtr ptr = Marshal.SecureStringToGlobalAllocUnicode(_securePassword);
try
{
int length = _securePassword.Length;
char[] chars = new char[length];
// 2. Temporary copy to a character array (only for an instant)
Marshal.Copy(ptr, chars, 0, length);
// 3. Conversion to UTF-8 bytes so the mathematical functions can interpret it
byte[] utf8Bytes = Encoding.UTF8.GetBytes(chars);
// 4. THE CRITICAL STEP: Immediate sanitization of the temporary array
Array.Clear(chars, 0, chars.Length);
return utf8Bytes;
}
finally
{
// 5. Cryptographic destruction of unmanaged memory
Marshal.ZeroFreeGlobalAllocUnicode(ptr);
}
}
Memory Flow Analysis
The GetPasswordBytes() method shown above represents my approach regarding memory paranoia. It operates as follows:
- It transforms the encrypted
SecureStringinto a pointer (IntPtr), which is nothing more than an address pointing to a block of unmanaged memory. - It copies the characters into a temporary managed
char[]array, but only for the exact duration required to convert it into a byte format (UTF-8). - The Critical Point: Immediately after obtaining the bytes, I use
Array.Clearto overwrite that temporary character array with zeros. - In the
finallyblock (a programming construct ensuring this code always executes, even if the computer encounters an error or a catastrophic exception), I callMarshal.ZeroFreeGlobalAllocUnicode(ptr). This is a deep native function that not only frees the unmanaged memory but explicitly overwrites it with zeros before returning the memory space to the operating system.
What is the result of all this? Your plaintext password—the one you entered via the keyboard—exists in RAM for a mere few milliseconds. Afterward, it is proactively destroyed, drastically narrowing the window of opportunity for any attacker attempting to peer into your computer’s memory.
2. Key Derivation Functions (KDF): My Dual Engine and Specialized Hardware Resistance
A human password (no matter how complex, even something like MyPassword123!) rarely possesses the entropy—that is, the mathematical randomness—required to be used directly as a 256-bit encryption key (which consists of 32 bytes of pure mathematical chaos). To transform your human password into invulnerable cryptographic keys, we utilize tools known as Key Derivation Functions (KDFs).
The vast majority of commercial note-taking applications rely on a single algorithm for this purpose (usually PBKDF2 or bcrypt). In FoxNotes, however, I chose to elevate the standard by implementing a dual derivation system. This system simultaneously combines two of the most robust algorithms available in modern cryptography: PBKDF2-SHA512 and Argon2id.
The Problem of Brute Force and Specialized Hardware (ASICs)
Modern attackers do not attempt to guess your password by sitting down and typing words. They utilize ultra-high-end video gaming graphics cards (GPUs) or even manufacture custom hardware chips (ASICs) designed exclusively to calculate combinations at ridiculous speeds of billions of attempts per second.
To combat these silicon monsters, a key derivation function (KDF) must be extremely “expensive” for the machine to compute. We can extract this cost from the attacker in two ways: in processor time (CPU) or by demanding a massive amount of RAM. I decided to charge them both.
- PBKDF2-SHA512 (Charging the cost in Processor / CPU): The first engine of FoxNotes utilizes Password-Based Key Derivation Function 2 (PBKDF2), relying on SHA-512 as its underlying mathematical hashing algorithm. In the FoxNotes options, I allow you to configure the number of iterations (rounds) you want to apply to your key, but I have enforced a strict minimum limit of 100,000 iterations (
MIN_ITERATIONS) within the source code. This means that for every single attempt to guess your password, an attacker’s processor must compute the complex SHA-512 algorithm at least one hundred thousand consecutive times sequentially on top of itself. This makes dictionary attacks (testing common words) or brute force attacks exponentially painful and slow. - Argon2id (Charging the cost in RAM): While charging for processor time via PBKDF2 is excellent, malicious corporations or heavily funded attackers can manufacture ASIC chips that calculate SHA-512 incredibly efficiently, bypassing my barrier. This is where the second trump card comes into play: Argon2id, the winner of the prestigious global Password Hashing Competition. Argon2id is a “memory-hard” algorithm. In the FoxNotes Cryptography Service (
CryptoService.cs), I have hardcoded it with the following audited parameters:ARGON2_MEMORY = 60416: Demands approximately 60 Megabytes of RAM per calculation.ARGON2_ITERATIONS = 4: The algorithm must traverse and mix that entire memory block 4 times.ARGON2_PARALLELISM = 1: Ensures a single thread of execution is used to avoid skipping steps.
This configuration is a hacker’s nightmare. It forces any attacker attempting to crack the password to have at least 60 Megabytes of RAM available for every simultaneous attempt. If a cluster of graphics cards attempts to test 10,000 passwords per second, it would require 600 Gigabytes of ultra-fast dedicated RAM exclusively for that purpose, completely destroying the economic and technical viability of the attack.
The Fusion: Creating Your Master Key via XOR
What makes the FoxNotes architecture exceptionally resilient is how these two algorithms are combined. Instead of forcing you to choose one, the application computes both and then fuses them together using a bitwise mathematical operation called XOR (^).
C#
byte[] hashA = PBKDF2_SHA512(passwordBytes, salt, iterationsSha512, HASH_SIZE);
byte[] hashB = Argon2idDerive(passwordBytes, salt, HASH_SIZE);
masterKey = new byte[HASH_SIZE]; // HASH_SIZE is 64 bytes
for (int i = 0; i < HASH_SIZE; i++)
masterKey[i] = (byte)(hashA[i] ^ hashB[i]); // The great fusion
Why use XOR? The XOR (exclusive OR) operation possesses a fascinating, almost magical mathematical property: if you fuse two byte strings together, and at least one of them is cryptographically perfect and secure, the final output will be completely secure regardless of how flawed or weak the other string was. This is future-proofing. If a catastrophic mathematical vulnerability is discovered in Argon2id tomorrow that allows it to be broken in seconds, the master key of your notes in FoxNotes will remain protected by the immense strength of PBKDF2, and vice versa. It is a “Defense-in-Depth” design, deliberately engineered to withstand the test of time and the inevitable cryptanalytic advancements of the coming decades.
From this massive 64-byte master key (masterKey), FoxNotes splits it into two exact halves:
key1(32 bytes): Dedicated to powering the internal encryption engine (ChaCha20).key2(32 bytes): Dedicated to powering the external encryption engine (AES-GCM).
3. Hybrid Cascade Encryption: ChaCha20 + AES-256-GCM
Once the system has securely derived key1 and key2 from your password, FoxNotes proceeds to encrypt the plaintext of the note you have just written. Instead of relying on a single lock (a single encryption algorithm), I utilize a technique known as Cascade Encryption.
Imagine placing your diary inside a safe, and then taking that entire safe and placing it inside a larger vault with a lock from a completely different brand. This means the data is encrypted first with one algorithm, and the resulting ciphertext is encrypted again with an entirely different algorithm.
The Inner Layer: ChaCha20 (The Stream Cipher)
The first layer of this armor is executed using ChaCha20. Developed by the renowned cryptologist Daniel J. Bernstein, ChaCha20 is what is known as a stream cipher.
Unlike traditional block ciphers (which take your text and divide it into fixed-size blocks), a stream cipher operates like a high-pressure hose. It generates an infinite pseudo-random stream of bits (mathematical noise) based on key1 and a nonce (a special 12-byte number used exactly once in history for that specific note). Then, this stream of noise is combined with your words.
C#
private static byte[] ChaCha20Encrypt(byte[] plain, byte[] key, byte[] nonce)
{
var cipher = new ChaCha7539Engine();
cipher.Init(true, new ParametersWithIV(new KeyParameter(key), nonce));
byte[] ciphertext = new byte[plain.Length];
cipher.ProcessBytes(plain, 0, plain.Length, ciphertext, 0);
return ciphertext;
}
I chose to use ChaCha20 here because its advantages are enormous:
- Immunity to Cache/Timing Attacks: Because ChaCha20 performs pure mathematical operations that do not rely on looking up data in memory (the infamous S-box tables), the time your processor takes to execute it is always exactly the same. An attacker cannot spy on how many milliseconds your CPU takes to encrypt in order to guess the key.
- Extreme Diffusion: It ensures that the original text you wrote turns into perfect cryptographic white noise before it is even passed to the second stage.
The Outer Layer: AES-256-GCM (Authenticated Encryption)
Once your data has been encrypted and converted into noise by ChaCha20, I take that resulting byte array (cipher1) and pass it to the second safe: the industry world standard, AES (Advanced Encryption Standard), using the maximum key length of 256 bits and operating in a special mode called GCM (Galois/Counter Mode).
The GCM mode is critical to my protection philosophy. You see, AES on its own hides data, but it does not guarantee that someone won’t secretly modify it while it is stored on your hard drive. If an attacker (or a corrupted sector on your hard drive) alters a single byte of the encrypted text, a normal AES algorithm would simply decrypt garbage without warning, and the application might crash unexpectedly, ruining your note.
GCM elevates AES into something superior: an AEAD system (Authenticated Encryption with Associated Data). While the AES vault encrypts the data, the GCM engine performs highly advanced mathematical calculations over a so-called “Galois Field”. The purpose of this is to generate a 16-byte Authentication Tag. It functions exactly like a wax seal on an ancient letter.
C#
private static byte[] AesGcmEncrypt(byte[] plain, byte[] key, byte[] nonce, byte[]? associatedData = null)
{
using var aes = new AesGcm(key, 16); // Prepare the 16-byte space for the Tag
byte[] ciphertext = new byte[plain.Length];
byte[] tag = new byte[16];
if (associatedData != null)
aes.Encrypt(nonce, plain, ciphertext, tag, associatedData); // Encryption with public rules
else
aes.Encrypt(nonce, plain, ciphertext, tag);
// Append the ciphertext and the Authentication Tag together for storage
byte[] result = new byte[ciphertext.Length + tag.Length];
Buffer.BlockCopy(ciphertext, 0, result, 0, ciphertext.Length);
Buffer.BlockCopy(tag, 0, result, ciphertext.Length, tag.Length);
return result;
}
During the reverse process (when you input your key to unlock the note), my AesGcmDecrypt method first halts and mathematically verifies this Tag. If a single bit of the .fox file has been altered—whether by file corruption or by a hacker’s hand—the Tag verification will fail, and the system will immediately throw an AuthenticationTagMismatchException. This stops access dead in its tracks before even attempting to process the corrupted data, guaranteeing total Integrity alongside Confidentiality.
By placing ChaCha20 inside AES-256-GCM, I ensure that an attacker would have to break not one, but two fundamentally distinct cryptographic algorithms with zero known flaws just to read a single word of what you wrote.
4. Additional Authenticated Data (AAD): The Mathematical Glue
There is an advanced attack against encrypted systems known as “Metadata Swapping”. Every encrypted file in the world needs to store a small amount of public information, in plaintext, right at the beginning of the file. This information includes vital details such as the Initialization Vector (the random number or Nonce), the Salt, and the number of iterations you selected in the options.
If I leave this public information unprotected, a clever attacker could cheat: they could take the header of a very old .fox file of yours (from back when you might have used fewer iterations) and paste it into your new file. This would force FoxNotes to use weak parameters to open your note, which could weaken the shield or cause the application to malfunction.
To prevent this elegantly, I designed the code to inject this metadata straight into the mathematical equation of the AES-GCM Tag using a feature called AAD (Additional Authenticated Data).
C#
// 1. Take your public rules and convert them into a readable format (Base64)
string saltBase64 = Convert.ToBase64String(salt);
string nonceBase64 = Convert.ToBase64String(nonce);
// KEY POINT: Inject those rules into an AAD variable
aad = Encoding.UTF8.GetBytes($"{TEXT_MAGIC}::{saltBase64}::{nonceBase64}::{iterationsSha512}");
// Encrypt the note and tell AES-GCM to generate the Tag BOUND to these rules
cipher2WithTag = AesGcmEncrypt(cipher1, key2, nonce, aad);
When AES-GCM calculates that famous 16-byte wax Tag, it includes the entire contents of the aad variable within its mathematical equation, even though that data itself is not encrypted (as it must remain readable).
What is the result of this maneuver? Your ciphertext becomes mathematically bound to its specific metadata. If a virus or a curious individual opens your note in a text editor and attempts to modify the iterationsSha512 field from 200,000 to a faster 100,000, the system will inject that altered rule into the mathematical formula when you attempt to open it in FoxNotes. Instantly, the Tag calculation will yield a result different from the original stored Tag, and the application will throw an integrity error, blocking the file. No rule in your file can be tampered with without invalidating the entire content completely.
5. Attachment Handling (Images): The Perfect Balance Between Security and Speed
One of the features I am most proud of (and one that took the most debugging) in FoxNotes is that .fox files are self-contained ecosystems. If you paste an image inside your note, I do not save that photo as a .png file in some hidden Windows folder. What FoxNotes does is grab that entire image, convert it into a massive block of text (Base64), encrypt it, and tuck it deep into the belly of your note file. If you copy your .fox file to a flash drive, you take your text and your photos with you, all fully protected.
However, I faced a major developer challenge here. Encrypting massive binary files (like high-resolution photos of several megabytes) by applying the full weight of the dual KDF (the 60MB of RAM from Argon2id plus the PBKDF2 CPU load) and then double-encrypting it with ChaCha20 and AES would cause saving your note to take several seconds. This would make using the application frustratingly slow, ruining the User Experience (UX).
To resolve this without compromising your privacy, I bifurcated the logic inside CryptoService, creating a fast-track but incredibly armored pipeline strictly for images:
C#
// Inside the specific process for image encryption
byte[] salt = RandomNumberGenerator.GetBytes(SALT_SIZE);
byte[] nonce = RandomNumberGenerator.GetBytes(NONCE_SIZE);
// 1. Optimized and fast key derivation
key = PBKDF2_SHA256(passwordBytes, salt, PBKDF2_IMG_ITERATIONS, KEY_SIZE);
// 2. Single encryption with AES-GCM (Ultra-fast due to CPU hardware integration)
cipherWithTag = AesGcmEncrypt(imageBytes, key, nonce);
The architectural differences for your photos include:
- Simplified KDF: For photos, I do not use Argon2id. I rely exclusively on PBKDF2-SHA256 and lock the iterations internally at 100,003 (I chose a prime number to prevent predictable patterns). It is fast, but it maintains the minimum baseline barrier of 100,000 rounds.
- Direct Single Encryption: I do not pass the photo through ChaCha20. I use strictly AES-256-GCM. Why? Because modern processors (such as Intel Core or AMD Ryzen) feature hardware-dedicated blocks designed exclusively for processing AES instructions (AES-NI). This allows my code to encrypt gigabytes of images in a fraction of a second.
- In-Memory Streams: When you open a note to view your photo, FoxNotes never creates a temporary file on your hard drive to display it. The photo is decrypted directly into an invisible memory stream (
MemoryStream).
C#
// Create a safe space in RAM
byte[] safeStreamBytes = new byte[imageBytes.Length];
Buffer.BlockCopy(imageBytes, 0, safeStreamBytes, 0, imageBytes.Length);
// Return the image directly to the UI from RAM
return new MemoryStream(safeStreamBytes, 0, safeStreamBytes.Length, true, true);
This guarantees that the decrypted photo exists solely within RAM and vanishes from existence the exact moment you close the note. No forensic police tool will ever be able to recover thumbnails of your photos by analyzing your powered-down hard drive.
6. Extreme Memory Hygiene: The Art of Leaving No Trace (ZeroMemory)
I can program the most impenetrable cryptography in the world, but it is all useless if the application leaves confidential “garbage” lying around in your computer’s memory. During the heavy mathematical processes of deriving your keys and encrypting your text, I generate a vast amount of temporary data: your password in byte format, mathematical salts, intermediate Argon2id outputs, split master keys, and the clean plaintext you just typed.
In a standard C# environment, all this data would be left floating around, at the mercy of the GC (the Garbage Collector). The real danger is that if your operating system runs out of physical memory (RAM) because you opened too many tabs in your browser, Windows might grab that “garbage” and write it to your hard drive (inside the infamous paging file, pagefile.sys). If someone steals your laptop while it is turned off, they could extract that pagefile and recover your master keys, completely bypassing all the safes we have discussed.
To block this deadly information leak, FoxNotes enforces what I call the Extreme Hygiene Doctrine, making obsessive use of a helper method I named Wipe. This method triggers a processor-level function called CryptographicOperations.ZeroMemory.
C#
private static void Wipe(byte[]? array)
{
if (array != null)
// Physically overwrite the memory cell with zeros
CryptographicOperations.ZeroMemory(array);
}
The finally Block Doctrine
Notice the control structure I use in the master method when you decrypt your note (DecryptTextAsync):
C#
try
{
// ... all the complex math to open your note happens here ...
}
catch (Exception)
{
// If there is an error, I hide it behind a generic message to avoid giving clues to a hacker
throw new CryptographicException("Decryption failed: Integrity error or invalid credentials.");
}
finally
{
// Mandatory cleanup, no matter what happens
Wipe(hashA);
Wipe(hashB);
Wipe(masterKey);
Wipe(key1);
Wipe(key2);
Wipe(cipher1);
Wipe(plainBytes);
Wipe(aad);
}
That finally block at the end of the code is your digital life insurance. It guarantees that regardless of whether you successfully opened your note, typed the wrong password, or the file was corrupted and the app exploded internally, every single critical vector of the operation is physically overwritten with zeros at the processor level.
It purges both halves of the key, the mathematical residuals of Argon2id, and your plaintext converted to bytes. I wipe the board clean before handing control back to the rest of the application. This paranoid memory hygiene technique is a standard requirement in cryptographic modules for intelligence agencies (such as the FIPS 140-2 standard), but it is something you will exceptionally rarely see implemented in a note-taking application designed for everyday users. I include it by default because my schizophrenia demands it.
7. Active Context: Fluid Performance Without Compromising Your Security
To wrap up, you might be asking yourself a logical question: If every time I hit a key, FoxNotes has to use 60 Megabytes of RAM and calculate one hundred thousand operations to encrypt… why doesn’t my computer crash or freeze when I type?
The application needs to save your changes live and render the preview of your markdown notes instantly. If it applied the full mathematical process to every single letter you type, your machine would grind to a complete halt.
The architectural engineering solution I implemented in my CryptoService.cs to solve this is called the Active Context (ActiveNoteContext).
C#
// Method that establishes the secure session in memory
public ActiveNoteContext CreateContext(byte[] passwordBytes, byte[] salt, int iterations)
When you click to unlock your notebook or note and provide the correct password, the system performs the incredibly heavy lifting (Argon2id and PBKDF2) exactly once. The final result (the master keys key1 and key2) is kept suspended inside a tiny vault within volatile memory via this encapsulated class (ActiveNoteContext). Immediately afterward, I destroy your original password.
As long as you keep the note open, FoxNotes will use the specialized methods EncryptTextWithContextAsync and DecryptTextWithContextAsync. These methods act like a VIP pass: they bypass the heavy CPU and RAM toll and go straight to the fast safes of ChaCha20 and AES-GCM using the suspended keys.
Thanks to this, FoxNotes can encrypt, decrypt, and auto-save entire megabytes of your text in microseconds, ensuring your writing experience is smooth, fast, and real-time, without ever downgrading the security of your key. The exact millisecond you decide to close the note or lock the application, the ZeroMemory method springs into action, the “Active Context” is killed and purged from RAM, and your bunker is completely sealed shut again, awaiting your next writing session.
Conclusion
As a developer, I believe that the cryptographic design of FoxNotes is not just about grabbing a few standard programming libraries and slapping them together with duct tape. I built this architecture from scratch, always assuming the worst-case scenario (one of my computer science professors used to say: “you must program as if the user were a serial killer who knows where you live”), engineering for a hostile threat model.
- I protected your intents by isolating your password from the Windows garbage collector.
- I defended you against server farms and massive hardware arrays by forcing resource exhaustion via the combination of Argon2id and PBKDF2 working in unison.
- I secured the absolute impenetrability of your words by combining the pure geometric confusion of ChaCha20 with the unyielding integrity validation of AES-256-GCM.
And most importantly: I understand and accept that a computer’s memory is a dangerous and dirty environment, and my code takes unshakeable responsibility for erasing its own mathematical footprints after every single operation.
With FoxNotes, you don’t have to trust me; you have to trust mathematics and the code. I have transformed a simple folder on your computer into an unassailable vault to deliver to you—and only you—the true meaning of data sovereignty. Just don’t lose your password, because neither I nor anyone else will ever be able to open your note.
