Build a File Encryption Tool in C using XOR cipher with key-based encryption, file processing, command-line interface, and multiple cipher modes explained.
This project implements a file encryption/decryption tool using the XOR cipher. While XOR isn't suitable for production security, it demonstrates cryptographic concepts, file I/O, bit manipulation, and building command-line tools in C. The same key encrypts and decrypts – making XOR a symmetric cipher.
How XOR Encryption Works
The XOR operation has a key property: applying it twice with the same key returns the original value.
| Original | 01001000 (H = 72) |
| Key | 10101010 (key = 170) |
| Encrypted | 11100010 (XOR result = 226) |
| Encrypted | 11100010 (226) |
| Key | 10101010 (170) |
| Decrypted | 01001000 (H = 72) ← Back to original! |
The mathematical property: A ^ K ^ K = A
Basic XOR Encryption
#include <stdio.h>
#include <string.h>
void xorEncrypt(char *data, int dataLen, const char *key, int keyLen) {
for (int i = 0; i < dataLen; i++) {
data[i] ^= key[i % keyLen];
}
}
int main() {
char message[] = "Hello, World! This is secret.";
char key[] = "MySecretKey";
int msgLen = strlen(message);
int keyLen = strlen(key);
printf("Original: %s\n", message);
// Encrypt
xorEncrypt(message, msgLen, key, keyLen);
printf("Encrypted: ");
for (int i = 0; i < msgLen; i++)
printf("%02X ", (unsigned char)message[i]);
printf("\n");
// Decrypt (same operation!)
xorEncrypt(message, msgLen, key, keyLen);
printf("Decrypted: %s\n", message);
return 0;
}
Original: Hello, World! This is secret.
Encrypted: 05 0A 03 09 02 69 57 12 06 07 00 6D 52 1D 01 00 1C 44 08 1C 48 1C 0C 18 1A 0E 01 6D 52
Decrypted: Hello, World! This is secret.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_SIZE 4096
#define MAX_KEY 256
typedef enum { MODE_ENCRYPT, MODE_DECRYPT } Mode;
// XOR encrypt/decrypt a buffer
void xorBuffer(unsigned char *buffer, size_t bufLen,
const char *key, size_t keyLen, size_t *keyOffset) {
for (size_t i = 0; i < bufLen; i++) {
buffer[i] ^= key[*keyOffset % keyLen];
(*keyOffset)++;
}
}
// Process file: encrypt or decrypt
int processFile(const char *inputPath, const char *outputPath,
const char *key, Mode mode) {
FILE *input = fopen(inputPath, "rb");
if (!input) {
fprintf(stderr, "Error: Cannot open input file '%s'\n", inputPath);
return -1;
}
FILE *output = fopen(outputPath, "wb");
if (!output) {
fprintf(stderr, "Error: Cannot create output file '%s'\n", outputPath);
fclose(input);
return -1;
}
unsigned char buffer[BUFFER_SIZE];
size_t bytesRead;
size_t keyOffset = 0;
size_t totalBytes = 0;
size_t keyLen = strlen(key);
while ((bytesRead = fread(buffer, 1, BUFFER_SIZE, input)) > 0) {
xorBuffer(buffer, bytesRead, key, keyLen, &keyOffset);
fwrite(buffer, 1, bytesRead, output);
totalBytes += bytesRead;
}
fclose(input);
fclose(output);
printf("%s successful!\n", mode == MODE_ENCRYPT ? "Encryption" : "Decryption");
printf("Processed %zu bytes\n", totalBytes);
printf("Input: %s\n", inputPath);
printf("Output: %s\n", outputPath);
return 0;
}
// Enhanced XOR with key stretching
void stretchKey(const char *password, unsigned char *key, size_t keyLen) {
// Simple key derivation - hash the password multiple times
memset(key, 0, keyLen);
size_t passLen = strlen(password);
for (size_t i = 0; i < keyLen; i++) {
key[i] = password[i % passLen];
key[i] ^= (unsigned char)(i * 31 + 17);
key[i] = (key[i] << 3) | (key[i] >> 5); // rotate
}
}
void printUsage(const char *progName) {
printf("File Encryption Tool - XOR Cipher\n");
printf("==================================\n\n");
printf("Usage:\n");
printf(" %s -e <input> <output> <key> Encrypt file\n", progName);
printf(" %s -d <input> <output> <key> Decrypt file\n", progName);
printf(" %s -t <text> <key> Encrypt text\n\n", progName);
printf("Examples:\n");
printf(" %s -e secret.txt secret.enc mypassword\n", progName);
printf(" %s -d secret.enc decoded.txt mypassword\n", progName);
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printUsage(argv[0]);
return 1;
}
if (strcmp(argv[1], "-e") == 0 || strcmp(argv[1], "-d") == 0) {
if (argc != 5) {
printUsage(argv[0]);
return 1;
}
Mode mode = (argv[1][1] == 'e') ? MODE_ENCRYPT : MODE_DECRYPT;
return processFile(argv[2], argv[3], argv[4], mode);
} else if (strcmp(argv[1], "-t") == 0) {
if (argc != 4) {
printUsage(argv[0]);
return 1;
}
char *text = strdup(argv[2]);
size_t textLen = strlen(text);
size_t keyLen = strlen(argv[3]);
size_t offset = 0;
printf("Original: %s\n", text);
xorBuffer((unsigned char*)text, textLen, argv[3], keyLen, &offset);
printf("Encrypted (hex): ");
for (size_t i = 0; i < textLen; i++)
printf("%02X", (unsigned char)text[i]);
printf("\n");
offset = 0;
xorBuffer((unsigned char*)text, textLen, argv[3], keyLen, &offset);
printf("Decrypted: %s\n", text);
free(text);
} else {
printUsage(argv[0]);
return 1;
}
return 0;
}
Usage Examples
$ ./encrypt -e document.pdf document.enc "MyStr0ngP@ss!"
Encryption successful!
Processed 245760 bytes
Input: document.pdf
Output: document.enc
$ ./encrypt -d document.enc restored.pdf "MyStr0ngP@ss!"
Decryption successful!
Processed 245760 bytes
Input: document.enc
Output: restored.pdf
$ ./encrypt -t "Hello World" "secret"
Original: Hello World
Encrypted (hex): 3B0A080015574A1204071F
Decrypted: Hello World
Verifying Encryption
#include <stdio.h>
#include <string.h>
// Verify that encrypting then decrypting gives original
int verify(const char *original, const char *key) {
size_t len = strlen(original);
char *copy = (char*)malloc(len + 1);
strcpy(copy, original);
size_t keyLen = strlen(key);
size_t offset = 0;
// Encrypt
for (size_t i = 0; i < len; i++)
copy[i] ^= key[i % keyLen];
// Verify it changed
if (memcmp(copy, original, len) == 0 && len > 0) {
printf("FAIL: Encryption didn't change data!\n");
free(copy);
return 0;
}
// Decrypt
for (size_t i = 0; i < len; i++)
copy[i] ^= key[i % keyLen];
// Verify restoration
int success = (memcmp(copy, original, len) == 0);
printf("Verify '%s': %s\n", original, success ? "PASS" : "FAIL");
free(copy);
return success;
}
int main() {
verify("Hello, World!", "key123");
verify("Binary data: \x00\x01\x02\x03", "test");
verify("Long message that exceeds key length for wrapping test", "short");
return 0;
}
Verify 'Hello, World!': PASS
Verify 'Binary data: ': PASS
Verify 'Long message that exceeds key length for wrapping test': PASS
Security Considerations
| Aspect | XOR Cipher | Production Cipher (AES) |
|---|
| Security level | Very weak | Strong (256-bit) |
| Key reuse | Devastating | Manageable with modes |
| Known plaintext | Key revealed immediately | Secure |
| Speed | Extremely fast | Fast (hardware support) |
| Use case | Education, obfuscation | Real security |
Why XOR alone is insecure:
- Repeating key creates patterns
- Known plaintext reveals the key:
ciphertext ^ plaintext = key - Statistical analysis can break it
Possible Enhancements
- Add file header – Store metadata (original filename, checksum)
- Key derivation – Use PBKDF2 or bcrypt for password → key
- Initialization vector – Randomize to prevent pattern attacks
- Integrity check – Add checksum/hash to detect tampering
- Multiple passes – Apply different transformations
- Compression – Compress before encrypting (reduces patterns)
Interview Questions
Q: Why is XOR used as the basis for many ciphers? XOR is its own inverse (A^K^K=A), preserves randomness (XOR with random key produces random output), and is extremely fast as a single CPU instruction.
Q: What is a one-time pad and why is it unbreakable? A one-time pad uses a truly random key as long as the message, used only once. It's provably unbreakable because every possible plaintext is equally likely given the ciphertext.
Q: How would you make this more secure? Use a proper cipher like AES from a cryptographic library (OpenSSL). Add authenticated encryption (GCM mode), proper key derivation, and random IVs.
Summary
This File Encryption Tool demonstrates XOR cipher operations, file I/O with binary data, command-line argument parsing, and buffer-based processing. While XOR alone is not suitable for real security, understanding it builds intuition for how symmetric encryption works at the bit level. The project showcases practical C skills including bit manipulation, file handling, and building CLI tools.