Files
otp/src/crypto.c

1331 lines
43 KiB
C

#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include "main.h"
#define PROGRESS_UPDATE_INTERVAL (64 * 1024 * 1024) // 64MB intervals
// Custom base64 character set
static const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const int base64_decode_table[256] = {
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,
52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,
-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
};
// Universal XOR operation - handles both encryption and decryption
// Since XOR is symmetric, this single function replaces all 6 duplicate XOR loops
int universal_xor_operation(const unsigned char* data, size_t data_len,
const unsigned char* pad_data, unsigned char* result) {
if (!data || !pad_data || !result) {
return 1; // Error: null pointer
}
for (size_t i = 0; i < data_len; i++) {
result[i] = data[i] ^ pad_data[i];
}
return 0; // Success
}
// Custom base64 encode function
char* custom_base64_encode(const unsigned char* input, int length) {
int output_length = 4 * ((length + 2) / 3);
char* encoded = malloc(output_length + 1);
if (!encoded) return NULL;
int i, j;
for (i = 0, j = 0; i < length;) {
uint32_t octet_a = i < length ? input[i++] : 0;
uint32_t octet_b = i < length ? input[i++] : 0;
uint32_t octet_c = i < length ? input[i++] : 0;
uint32_t triple = (octet_a << 16) + (octet_b << 8) + octet_c;
encoded[j++] = base64_chars[(triple >> 18) & 63];
encoded[j++] = base64_chars[(triple >> 12) & 63];
encoded[j++] = base64_chars[(triple >> 6) & 63];
encoded[j++] = base64_chars[triple & 63];
}
// Add padding
for (int pad = 0; pad < (3 - length % 3) % 3; pad++) {
encoded[output_length - 1 - pad] = '=';
}
encoded[output_length] = '\0';
return encoded;
}
// Custom base64 decode function
unsigned char* custom_base64_decode(const char* input, int* output_length) {
int input_length = strlen(input);
if (input_length % 4 != 0) return NULL;
*output_length = input_length / 4 * 3;
if (input[input_length - 1] == '=') (*output_length)--;
if (input[input_length - 2] == '=') (*output_length)--;
unsigned char* decoded = malloc(*output_length);
if (!decoded) return NULL;
int i, j;
for (i = 0, j = 0; i < input_length;) {
int sextet_a = input[i] == '=' ? 0 & i++ : base64_decode_table[(unsigned char)input[i++]];
int sextet_b = input[i] == '=' ? 0 & i++ : base64_decode_table[(unsigned char)input[i++]];
int sextet_c = input[i] == '=' ? 0 & i++ : base64_decode_table[(unsigned char)input[i++]];
int sextet_d = input[i] == '=' ? 0 & i++ : base64_decode_table[(unsigned char)input[i++]];
if (sextet_a == -1 || sextet_b == -1 || sextet_c == -1 || sextet_d == -1) {
free(decoded);
return NULL;
}
uint32_t triple = (sextet_a << 18) + (sextet_b << 12) + (sextet_c << 6) + sextet_d;
if (j < *output_length) decoded[j++] = (triple >> 16) & 255;
if (j < *output_length) decoded[j++] = (triple >> 8) & 255;
if (j < *output_length) decoded[j++] = triple & 255;
}
return decoded;
}
// Extracts checksum, offset, and base64 data from ASCII armored messages
int parse_ascii_message(const char* message, char* chksum, uint64_t* offset, char* base64_data) {
if (!message || !chksum || !offset || !base64_data) {
return 1; // Error: null pointer
}
size_t msg_len = strlen(message);
char *message_copy = malloc(msg_len + 1);
if (!message_copy) {
return 1; // Memory allocation failed
}
strcpy(message_copy, message);
char *line_ptr = strtok(message_copy, "\n");
int found_begin = 0;
int in_data_section = 0;
int found_chksum = 0, found_offset = 0;
// Initialize output
chksum[0] = '\0';
*offset = 0;
base64_data[0] = '\0';
while (line_ptr != NULL) {
if (strcmp(line_ptr, "-----BEGIN OTP MESSAGE-----") == 0) {
found_begin = 1;
}
else if (strcmp(line_ptr, "-----END OTP MESSAGE-----") == 0) {
break;
}
else if (found_begin) {
if (strncmp(line_ptr, "Pad-ChkSum: ", 12) == 0) {
strncpy(chksum, line_ptr + 12, 64);
chksum[64] = '\0';
found_chksum = 1;
}
else if (strncmp(line_ptr, "Pad-Offset: ", 12) == 0) {
*offset = strtoull(line_ptr + 12, NULL, 10);
found_offset = 1;
}
else if (strlen(line_ptr) == 0) {
in_data_section = 1;
}
else if (in_data_section) {
strncat(base64_data, line_ptr, MAX_INPUT_SIZE * 2 - strlen(base64_data) - 1);
}
else if (strncmp(line_ptr, "Version:", 8) != 0 && strncmp(line_ptr, "Pad-", 4) != 0) {
// This might be base64 data without a blank line separator
strncat(base64_data, line_ptr, MAX_INPUT_SIZE * 2 - strlen(base64_data) - 1);
}
}
line_ptr = strtok(NULL, "\n");
}
free(message_copy);
if (!found_begin || !found_chksum || !found_offset) {
return 2; // Error: incomplete message format
}
return 0; // Success
}
// Creates ASCII armored output format used by both text and file encryption
int generate_ascii_armor(const char* chksum, uint64_t offset, const unsigned char* encrypted_data,
size_t data_length, char** ascii_output) {
if (!chksum || !encrypted_data || !ascii_output) {
return 1; // Error: null pointer
}
// Encode data as base64
char* base64_data = custom_base64_encode(encrypted_data, data_length);
if (!base64_data) {
return 2; // Error: base64 encoding failed
}
// Calculate required buffer size
size_t base64_len = strlen(base64_data);
size_t header_size = 200; // Approximate size for headers
size_t total_size = header_size + base64_len + (base64_len / 64) + 100; // +newlines +footer
*ascii_output = malloc(total_size);
if (!*ascii_output) {
free(base64_data);
return 3; // Error: memory allocation failed
}
// Build ASCII armor
strcpy(*ascii_output, "-----BEGIN OTP MESSAGE-----\n");
char temp_line[256];
snprintf(temp_line, sizeof(temp_line), "Version: %s\n", OTP_VERSION);
strcat(*ascii_output, temp_line);
snprintf(temp_line, sizeof(temp_line), "Pad-ChkSum: %s\n", chksum);
strcat(*ascii_output, temp_line);
snprintf(temp_line, sizeof(temp_line), "Pad-Offset: %lu\n", offset);
strcat(*ascii_output, temp_line);
strcat(*ascii_output, "\n");
// Add base64 data in 64-character lines
int b64_len = strlen(base64_data);
for (int i = 0; i < b64_len; i += 64) {
char line[70];
snprintf(line, sizeof(line), "%.64s\n", base64_data + i);
strcat(*ascii_output, line);
}
strcat(*ascii_output, "-----END OTP MESSAGE-----\n");
free(base64_data);
return 0; // Success
}
/*
// Progress display function for long operations - MOVED TO src/util.c
void show_progress(uint64_t current, uint64_t total, time_t start_time) {
double percentage = (double)current / total * 100.0;
time_t current_time = time(NULL);
double elapsed = difftime(current_time, start_time);
double rate = current / elapsed;
double eta = (total - current) / rate;
printf("\rProgress: %.2f%% (%lu/%lu bytes) - %.2f MB/s - ETA: %.0fs",
percentage, current, total, rate / (1024*1024), eta);
fflush(stdout);
}
*/
// Calculate XOR checksum of pad file
int calculate_checksum(const char* filename, char* checksum_hex) {
FILE* file = fopen(filename, "rb");
if (!file) {
return 1;
}
unsigned char checksum[32];
unsigned char buffer[64 * 1024]; // 64KB buffer for large files
size_t bytes_read;
// Initialize checksum
memset(checksum, 0, 32);
size_t total_bytes = 0;
// Calculate XOR checksum of entire file
while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) {
// Process this chunk with XOR checksum
for (size_t i = 0; i < bytes_read; i++) {
unsigned char bucket = (total_bytes + i) % 32;
checksum[bucket] ^= buffer[i] ^ (((total_bytes + i) >> 8) & 0xFF) ^
(((total_bytes + i) >> 16) & 0xFF) ^ (((total_bytes + i) >> 24) & 0xFF);
}
total_bytes += bytes_read;
}
fclose(file);
// Now encrypt the checksum with the first 32 bytes of the pad
fseek(file = fopen(filename, "rb"), 0, SEEK_SET);
unsigned char pad_key[32];
if (fread(pad_key, 1, 32, file) != 32) {
fclose(file);
return 1;
}
fclose(file);
// XOR encrypt the checksum with pad data to create unique identifier
unsigned char encrypted_checksum[32];
for (int i = 0; i < 32; i++) {
encrypted_checksum[i] = checksum[i] ^ pad_key[i];
}
// Convert to hex string (64 characters)
for (int i = 0; i < 32; i++) {
sprintf(checksum_hex + (i * 2), "%02x", encrypted_checksum[i]);
}
checksum_hex[64] = '\0';
return 0;
}
int encrypt_text(const char* pad_identifier, const char* input_text) {
char* pad_chksum = find_pad_by_prefix(pad_identifier);
if (!pad_chksum) {
return 1;
}
char text_buffer[MAX_INPUT_SIZE];
char chksum_hex[MAX_HASH_LENGTH];
uint64_t current_offset;
char pad_path[MAX_HASH_LENGTH + 20];
char state_path[MAX_HASH_LENGTH + 20];
get_pad_path(pad_chksum, pad_path, state_path);
// Check if pad file exists
if (access(pad_path, R_OK) != 0) {
printf("Error: Pad file %s not found\n", pad_path);
free(pad_chksum);
return 1;
}
// Read current offset
if (read_state_offset(pad_chksum, &current_offset) != 0) {
printf("Error: Cannot read state file\n");
free(pad_chksum);
return 1;
}
// Ensure we never encrypt before offset 32 (reserved for checksum encryption)
if (current_offset < 32) {
printf("Warning: State offset below reserved area, adjusting to 32\n");
current_offset = 32;
if (write_state_offset(pad_chksum, current_offset) != 0) {
printf("Warning: Failed to update state file\n");
}
}
// Calculate XOR checksum of pad file
if (calculate_checksum(pad_path, chksum_hex) != 0) {
printf("Error: Cannot calculate pad checksum\n");
free(pad_chksum);
return 1;
}
// Get input text - either from parameter or user input
if (input_text != NULL) {
// Use provided text
strncpy(text_buffer, input_text, sizeof(text_buffer) - 1);
text_buffer[sizeof(text_buffer) - 1] = '\0';
} else {
// Get input text from user (interactive mode)
if (get_interactive_mode()) {
printf("\nText input options:\n");
printf(" 1. Type text directly\n");
printf(" 2. Use text editor\n");
printf("Enter choice (1-2): ");
}
char input_choice[10] = "1"; // Default to direct input in non-interactive mode
if (get_interactive_mode()) {
if (!fgets(input_choice, sizeof(input_choice), stdin)) {
printf("Error: Failed to read input\n");
free(pad_chksum);
return 1;
}
}
if (get_interactive_mode() && atoi(input_choice) == 2) {
// Use text editor
if (launch_text_editor(NULL, text_buffer, sizeof(text_buffer)) != 0) {
if (get_interactive_mode()) {
printf("Falling back to direct text input.\n");
printf("Enter text to encrypt: ");
}
fflush(stdout);
if (fgets(text_buffer, sizeof(text_buffer), stdin) == NULL) {
printf("Error: Failed to read input\n");
free(pad_chksum);
return 1;
}
// Remove newline if present
size_t len = strlen(text_buffer);
if (len > 0 && text_buffer[len - 1] == '\n') {
text_buffer[len - 1] = '\0';
}
}
} else {
// Direct text input
if (get_interactive_mode()) {
printf("Enter text to encrypt: ");
fflush(stdout);
}
if (fgets(text_buffer, sizeof(text_buffer), stdin) == NULL) {
printf("Error: Failed to read input\n");
free(pad_chksum);
return 1;
}
// Remove newline if present
size_t len = strlen(text_buffer);
if (len > 0 && text_buffer[len - 1] == '\n') {
text_buffer[len - 1] = '\0';
}
}
}
size_t input_len = strlen(text_buffer);
if (input_len == 0) {
printf("Error: No input provided\n");
free(pad_chksum);
return 1;
}
// Check if we have enough pad space
struct stat pad_stat;
if (stat(pad_path, &pad_stat) != 0) {
printf("Error: Cannot get pad file size\n");
free(pad_chksum);
return 1;
}
if (current_offset + input_len > (uint64_t)pad_stat.st_size) {
printf("Error: Not enough pad space remaining\n");
printf("Need: %lu bytes, Available: %lu bytes\n",
input_len, (uint64_t)pad_stat.st_size - current_offset);
free(pad_chksum);
return 1;
}
// Read pad data at current offset
FILE* pad_file = fopen(pad_path, "rb");
if (!pad_file) {
printf("Error: Cannot open pad file\n");
free(pad_chksum);
return 1;
}
if (fseek(pad_file, current_offset, SEEK_SET) != 0) {
printf("Error: Cannot seek to offset in pad file\n");
fclose(pad_file);
free(pad_chksum);
return 1;
}
unsigned char* pad_data = malloc(input_len);
if (fread(pad_data, 1, input_len, pad_file) != input_len) {
printf("Error: Cannot read pad data\n");
free(pad_data);
fclose(pad_file);
free(pad_chksum);
return 1;
}
fclose(pad_file);
// Use universal XOR operation for encryption
unsigned char* ciphertext = malloc(input_len);
if (universal_xor_operation((const unsigned char*)text_buffer, input_len, pad_data, ciphertext) != 0) {
printf("Error: Encryption operation failed\n");
free(pad_data);
free(ciphertext);
free(pad_chksum);
return 1;
}
// Update state offset
if (write_state_offset(pad_chksum, current_offset + input_len) != 0) {
printf("Warning: Failed to update state file\n");
}
// Use universal ASCII armor generator
char* ascii_output;
if (generate_ascii_armor(chksum_hex, current_offset, ciphertext, input_len, &ascii_output) != 0) {
printf("Error: Failed to generate ASCII armor\n");
free(pad_data);
free(ciphertext);
free(pad_chksum);
return 1;
}
// Output with appropriate formatting - clean format for piping, spaced format for interactive
int is_interactive = (input_text == NULL);
if (is_interactive) {
printf("\n\n\n%s\n\n", ascii_output);
} else {
printf("%s\n", ascii_output); // Add newline for proper piping with tee
}
// Cleanup
free(pad_data);
free(ciphertext);
free(ascii_output);
free(pad_chksum);
return 0;
}
// Universal decrypt function - consolidates all decrypt operations
// input_data: encrypted message text or file path
// output_target: output file path (NULL for stdout/interactive)
// mode: determines behavior and output format
int universal_decrypt(const char* input_data, const char* output_target, decrypt_mode_t mode) {
char stored_chksum[MAX_HASH_LENGTH];
uint64_t pad_offset;
char base64_data[MAX_INPUT_SIZE * 8] = {0};
unsigned char* ciphertext = NULL;
int ciphertext_len;
// Handle input based on mode
if (mode == DECRYPT_MODE_FILE_TO_TEXT || mode == DECRYPT_MODE_FILE_TO_FILE) {
// File input - read the entire file
FILE* input_fp = fopen(input_data, "r");
if (!input_fp) {
printf("Error: Cannot open input file %s\n", input_data);
return 1;
}
fseek(input_fp, 0, SEEK_END);
long file_size = ftell(input_fp);
fseek(input_fp, 0, SEEK_SET);
char* file_content = malloc(file_size + 1);
if (!file_content) {
printf("Error: Memory allocation failed\n");
fclose(input_fp);
return 1;
}
size_t bytes_read = fread(file_content, 1, file_size, input_fp);
file_content[bytes_read] = '\0';
fclose(input_fp);
// Parse ASCII message from file content
if (parse_ascii_message(file_content, stored_chksum, &pad_offset, base64_data) != 0) {
printf("Error: Invalid ASCII armored format in file\n");
free(file_content);
return 1;
}
free(file_content);
if (mode == DECRYPT_MODE_FILE_TO_TEXT) {
printf("Decrypting ASCII armored file...\n");
}
// Note: DECRYPT_MODE_FILE_TO_FILE should be completely silent for piping
} else {
// Text input (interactive or piped)
const char* message_text;
char full_message[MAX_INPUT_SIZE * 4] = {0};
if (input_data != NULL) {
message_text = input_data;
} else {
// Interactive mode - read from stdin
if (mode == DECRYPT_MODE_INTERACTIVE) {
printf("Enter encrypted message (paste the full ASCII armor block):\n");
}
char line[MAX_LINE_LENGTH];
while (fgets(line, sizeof(line), stdin)) {
strncat(full_message, line, sizeof(full_message) - strlen(full_message) - 1);
if (strstr(line, "-----END OTP MESSAGE-----")) {
break;
}
}
message_text = full_message;
}
// Parse ASCII message from text
if (parse_ascii_message(message_text, stored_chksum, &pad_offset, base64_data) != 0) {
if (mode == DECRYPT_MODE_SILENT) {
fprintf(stderr, "Error: Invalid message format - missing BEGIN header\n");
} else {
printf("Error: Invalid message format - missing BEGIN header\n");
}
return 1;
}
}
// Get pad path and check existence
char pad_path[MAX_HASH_LENGTH + 20];
char state_path[MAX_HASH_LENGTH + 20];
get_pad_path(stored_chksum, pad_path, state_path);
if (access(pad_path, R_OK) != 0) {
if (mode == DECRYPT_MODE_SILENT) {
fprintf(stderr, "Error: Required pad not found: %s\n", stored_chksum);
} else {
printf("Error: Required pad not found: %s\n", stored_chksum);
if (mode == DECRYPT_MODE_INTERACTIVE || mode == DECRYPT_MODE_FILE_TO_TEXT) {
printf("Available pads:\n");
char* selected = select_pad_interactive("Available pads:", "Available pads (press Enter to continue)", PAD_FILTER_ALL, 0);
if (selected) {
free(selected);
}
}
}
return 1;
}
// Validate pad integrity
int integrity_result = validate_pad_integrity(pad_path, stored_chksum);
if (integrity_result == 3) {
if (mode == DECRYPT_MODE_SILENT) {
fprintf(stderr, "Error: Pad integrity check failed!\n");
return 1;
} else if (mode == DECRYPT_MODE_INTERACTIVE) {
printf("Warning: Pad integrity check failed!\n");
printf("Expected: %s\n", stored_chksum);
printf("Continue anyway? (y/N): ");
fflush(stdout);
char response[10];
if (fgets(response, sizeof(response), stdin) == NULL ||
(response[0] != 'y' && response[0] != 'Y')) {
printf("Decryption aborted.\n");
return 1;
}
}
} else if (integrity_result != 0) {
if (mode == DECRYPT_MODE_SILENT) {
fprintf(stderr, "Error: Cannot verify pad integrity\n");
} else {
printf("Error: Cannot verify pad integrity\n");
}
return 1;
} else {
if (mode == DECRYPT_MODE_INTERACTIVE || mode == DECRYPT_MODE_FILE_TO_TEXT) {
printf("Pad integrity: VERIFIED\n");
}
}
// Decode base64 ciphertext
ciphertext = custom_base64_decode(base64_data, &ciphertext_len);
if (!ciphertext) {
if (mode == DECRYPT_MODE_SILENT) {
fprintf(stderr, "Error: Invalid base64 data\n");
} else {
printf("Error: Invalid base64 data\n");
}
return 1;
}
// Load pad data using universal function
unsigned char* pad_data;
if (load_pad_data(stored_chksum, pad_offset, ciphertext_len, &pad_data) != 0) {
if (mode == DECRYPT_MODE_SILENT) {
fprintf(stderr, "Error: Cannot load pad data\n");
} else {
printf("Error: Cannot load pad data\n");
}
free(ciphertext);
return 1;
}
// Decrypt using universal XOR operation
if (universal_xor_operation(ciphertext, ciphertext_len, pad_data, ciphertext) != 0) {
if (mode == DECRYPT_MODE_SILENT) {
fprintf(stderr, "Error: Decryption operation failed\n");
} else {
printf("Error: Decryption operation failed\n");
}
free(ciphertext);
free(pad_data);
return 1;
}
// Output based on mode
if (mode == DECRYPT_MODE_FILE_TO_FILE) {
// Write to output file
const char* output_file = output_target;
// Generate default output filename if not provided
char default_output[512];
if (output_file == NULL) {
strncpy(default_output, input_data, sizeof(default_output) - 1);
default_output[sizeof(default_output) - 1] = '\0';
char* ext = strstr(default_output, ".otp.asc");
if (ext) {
*ext = '\0';
} else {
strncat(default_output, ".decrypted", sizeof(default_output) - strlen(default_output) - 1);
}
output_file = default_output;
}
FILE* output_fp = fopen(output_file, "wb");
if (!output_fp) {
printf("Error: Cannot create output file %s\n", output_file);
free(ciphertext);
free(pad_data);
return 1;
}
if (fwrite(ciphertext, 1, ciphertext_len, output_fp) != (size_t)ciphertext_len) {
printf("Error: Cannot write decrypted data\n");
free(ciphertext);
free(pad_data);
fclose(output_fp);
return 1;
}
fclose(output_fp);
// Only show success messages in non-silent modes
if (mode != DECRYPT_MODE_FILE_TO_FILE) {
printf("File decrypted successfully: %s\n", output_file);
printf("Note: ASCII format does not preserve original filename/permissions\n");
}
} else {
// Text output to stdout - need to allocate space for null terminator
char* decrypted_text = malloc(ciphertext_len + 1);
if (!decrypted_text) {
printf("Error: Memory allocation failed for output\n");
free(ciphertext);
free(pad_data);
return 1;
}
memcpy(decrypted_text, ciphertext, ciphertext_len);
decrypted_text[ciphertext_len] = '\0';
if (mode == DECRYPT_MODE_SILENT) {
// Silent mode - just output the text
printf("%s\n", decrypted_text);
fflush(stdout);
} else {
// Interactive mode - with label
printf("Decrypted: %s\n", decrypted_text);
}
free(decrypted_text);
}
// Cleanup
free(ciphertext);
free(pad_data);
return 0;
}
int decrypt_text(const char* pad_identifier, const char* encrypted_message) {
// Use universal decrypt function with mode based on global interactive mode detection
(void)pad_identifier; // Suppress unused parameter warning - chksum comes from message
decrypt_mode_t mode = get_interactive_mode() ? DECRYPT_MODE_INTERACTIVE : DECRYPT_MODE_SILENT;
return universal_decrypt(encrypted_message, NULL, mode);
}
int encrypt_file(const char* pad_identifier, const char* input_file, const char* output_file, int ascii_armor) {
char* pad_chksum = find_pad_by_prefix(pad_identifier);
if (!pad_chksum) {
return 1;
}
char chksum_hex[MAX_HASH_LENGTH];
uint64_t current_offset;
char pad_path[MAX_HASH_LENGTH + 20];
char state_path[MAX_HASH_LENGTH + 20];
get_pad_path(pad_chksum, pad_path, state_path);
// Check if input file exists and get its size
struct stat input_stat;
if (stat(input_file, &input_stat) != 0) {
printf("Error: Input file %s not found\n", input_file);
free(pad_chksum);
return 1;
}
uint64_t file_size = input_stat.st_size;
if (file_size == 0) {
printf("Error: Input file is empty\n");
free(pad_chksum);
return 1;
}
// Check if pad file exists
if (access(pad_path, R_OK) != 0) {
printf("Error: Pad file %s not found\n", pad_path);
free(pad_chksum);
return 1;
}
// Read current offset
if (read_state_offset(pad_chksum, &current_offset) != 0) {
printf("Error: Cannot read state file\n");
free(pad_chksum);
return 1;
}
// Ensure we never encrypt before offset 32
if (current_offset < 32) {
printf("Warning: State offset below reserved area, adjusting to 32\n");
current_offset = 32;
if (write_state_offset(pad_chksum, current_offset) != 0) {
printf("Warning: Failed to update state file\n");
}
}
// Calculate XOR checksum of pad file
if (calculate_checksum(pad_path, chksum_hex) != 0) {
printf("Error: Cannot calculate pad checksum\n");
free(pad_chksum);
return 1;
}
// Check if we have enough pad space
struct stat pad_stat;
if (stat(pad_path, &pad_stat) != 0) {
printf("Error: Cannot get pad file size\n");
free(pad_chksum);
return 1;
}
if (current_offset + file_size > (uint64_t)pad_stat.st_size) {
printf("Error: Not enough pad space remaining\n");
printf("Need: %lu bytes, Available: %lu bytes\n",
file_size, (uint64_t)pad_stat.st_size - current_offset);
free(pad_chksum);
return 1;
}
// Generate output filename if not specified, using files directory
char default_output[512];
if (output_file == NULL) {
char temp_output[512];
if (ascii_armor) {
snprintf(temp_output, sizeof(temp_output), "%s.otp.asc", input_file);
} else {
snprintf(temp_output, sizeof(temp_output), "%s.otp", input_file);
}
// Apply files directory default path
get_default_file_path(temp_output, default_output, sizeof(default_output));
output_file = default_output;
}
// Open input file
FILE* input_fp = fopen(input_file, "rb");
if (!input_fp) {
printf("Error: Cannot open input file %s\n", input_file);
free(pad_chksum);
return 1;
}
// Open pad file
FILE* pad_file = fopen(pad_path, "rb");
if (!pad_file) {
printf("Error: Cannot open pad file\n");
fclose(input_fp);
free(pad_chksum);
return 1;
}
if (fseek(pad_file, current_offset, SEEK_SET) != 0) {
printf("Error: Cannot seek to offset in pad file\n");
fclose(input_fp);
fclose(pad_file);
free(pad_chksum);
return 1;
}
// Read and encrypt file
unsigned char buffer[64 * 1024];
unsigned char pad_buffer[64 * 1024];
unsigned char* encrypted_data = malloc(file_size);
uint64_t bytes_processed = 0;
printf("Encrypting %s...\n", input_file);
while (bytes_processed < file_size) {
uint64_t chunk_size = sizeof(buffer);
if (file_size - bytes_processed < chunk_size) {
chunk_size = file_size - bytes_processed;
}
// Read file data
if (fread(buffer, 1, chunk_size, input_fp) != chunk_size) {
printf("Error: Cannot read input file data\n");
free(encrypted_data);
fclose(input_fp);
fclose(pad_file);
free(pad_chksum);
return 1;
}
// Read pad data
if (fread(pad_buffer, 1, chunk_size, pad_file) != chunk_size) {
printf("Error: Cannot read pad data\n");
free(encrypted_data);
fclose(input_fp);
fclose(pad_file);
free(pad_chksum);
return 1;
}
// Use universal XOR operation for encryption
if (universal_xor_operation(buffer, chunk_size, pad_buffer, &encrypted_data[bytes_processed]) != 0) {
printf("Error: Encryption operation failed\n");
free(encrypted_data);
fclose(input_fp);
fclose(pad_file);
free(pad_chksum);
return 1;
}
bytes_processed += chunk_size;
// Show progress for large files (> 10MB)
if (file_size > 10 * 1024 * 1024 && bytes_processed % (1024 * 1024) == 0) {
// show_progress(bytes_processed, file_size, start_time); // MOVED TO src/util.c
}
}
if (file_size > 10 * 1024 * 1024) {
// show_progress(file_size, file_size, start_time); // MOVED TO src/util.c
printf("\n");
}
fclose(input_fp);
fclose(pad_file);
// Write output file
if (ascii_armor) {
// ASCII armored format - same as message format
FILE* output_fp = fopen(output_file, "w");
if (!output_fp) {
printf("Error: Cannot create output file %s\n", output_file);
free(encrypted_data);
free(pad_chksum);
return 1;
}
// Use universal ASCII armor generator
char* ascii_output;
if (generate_ascii_armor(chksum_hex, current_offset, encrypted_data, file_size, &ascii_output) != 0) {
printf("Error: Failed to generate ASCII armor\n");
fclose(output_fp);
free(encrypted_data);
free(pad_chksum);
return 1;
}
// Write the ASCII armored output to file
fprintf(output_fp, "%s", ascii_output);
fclose(output_fp);
free(ascii_output);
} else {
// Binary format
FILE* output_fp = fopen(output_file, "wb");
if (!output_fp) {
printf("Error: Cannot create output file %s\n", output_file);
free(encrypted_data);
free(pad_chksum);
return 1;
}
// Write binary header
// Magic: "OTP\0"
fwrite("OTP\0", 1, 4, output_fp);
// Version: 2 bytes
uint16_t version = 1;
fwrite(&version, sizeof(uint16_t), 1, output_fp);
// Pad checksum: 32 bytes (binary)
unsigned char pad_chksum_bin[32];
for (int i = 0; i < 32; i++) {
sscanf(chksum_hex + i*2, "%2hhx", &pad_chksum_bin[i]);
}
fwrite(pad_chksum_bin, 1, 32, output_fp);
// Pad offset: 8 bytes
fwrite(&current_offset, sizeof(uint64_t), 1, output_fp);
// File mode: 4 bytes
uint32_t file_mode = input_stat.st_mode;
fwrite(&file_mode, sizeof(uint32_t), 1, output_fp);
// File size: 8 bytes
fwrite(&file_size, sizeof(uint64_t), 1, output_fp);
// Encrypted data
fwrite(encrypted_data, 1, file_size, output_fp);
fclose(output_fp);
}
// Update state offset
if (write_state_offset(pad_chksum, current_offset + file_size) != 0) {
printf("Warning: Failed to update state file\n");
}
printf("File encrypted successfully: %s\n", output_file);
if (ascii_armor) {
printf("Format: ASCII armored (.otp.asc)\n");
} else {
printf("Format: Binary (.otp)\n");
}
// Pause before returning to menu to let user see the success message
print_centered_header("File Encryption Complete", 1);
// Cleanup
free(encrypted_data);
free(pad_chksum);
return 0;
}
int decrypt_file(const char* input_file, const char* output_file) {
// Check if input file exists
if (access(input_file, R_OK) != 0) {
printf("Error: Input file %s not found\n", input_file);
return 1;
}
FILE* input_fp = fopen(input_file, "rb");
if (!input_fp) {
printf("Error: Cannot open input file %s\n", input_file);
return 1;
}
// Read first few bytes to determine format
char magic[4];
if (fread(magic, 1, 4, input_fp) != 4) {
printf("Error: Cannot read file header\n");
fclose(input_fp);
return 1;
}
fseek(input_fp, 0, SEEK_SET); // Reset to beginning
if (memcmp(magic, "OTP\0", 4) == 0) {
// Binary format
return decrypt_binary_file(input_fp, output_file);
} else {
// Assume ASCII armored format, read entire file as text
fclose(input_fp);
return decrypt_ascii_file(input_file, output_file);
}
}
int decrypt_binary_file(FILE* input_fp, const char* output_file) {
// Read binary header
char magic[4];
uint16_t version;
unsigned char pad_chksum_bin[32];
uint64_t pad_offset;
uint32_t file_mode;
uint64_t file_size;
if (fread(magic, 1, 4, input_fp) != 4 ||
fread(&version, sizeof(uint16_t), 1, input_fp) != 1 ||
fread(pad_chksum_bin, 1, 32, input_fp) != 32 ||
fread(&pad_offset, sizeof(uint64_t), 1, input_fp) != 1 ||
fread(&file_mode, sizeof(uint32_t), 1, input_fp) != 1 ||
fread(&file_size, sizeof(uint64_t), 1, input_fp) != 1) {
printf("Error: Cannot read binary header\n");
fclose(input_fp);
return 1;
}
if (memcmp(magic, "OTP\0", 4) != 0) {
printf("Error: Invalid binary format\n");
fclose(input_fp);
return 1;
}
// Convert binary checksum to hex
char pad_chksum_hex[65];
for (int i = 0; i < 32; i++) {
sprintf(pad_chksum_hex + i*2, "%02x", pad_chksum_bin[i]);
}
pad_chksum_hex[64] = '\0';
printf("Decrypting binary file...\n");
printf("File size: %lu bytes\n", file_size);
// Check if we have the required pad
char pad_path[MAX_HASH_LENGTH + 20];
char state_path[MAX_HASH_LENGTH + 20];
get_pad_path(pad_chksum_hex, pad_path, state_path);
if (access(pad_path, R_OK) != 0) {
printf("Error: Required pad not found: %s\n", pad_chksum_hex);
printf("Available pads:\n");
char* selected = select_pad_interactive("Available pads:", "Available pads (press Enter to continue)", PAD_FILTER_ALL, 0);
if (selected) {
free(selected);
}
fclose(input_fp);
return 1;
}
// Determine output filename
char default_output[512];
if (output_file == NULL) {
snprintf(default_output, sizeof(default_output), "decrypted.bin");
output_file = default_output;
}
// Read encrypted data
unsigned char* encrypted_data = malloc(file_size);
if (fread(encrypted_data, 1, file_size, input_fp) != file_size) {
printf("Error: Cannot read encrypted data\n");
free(encrypted_data);
fclose(input_fp);
return 1;
}
fclose(input_fp);
// Open pad file and decrypt
FILE* pad_file = fopen(pad_path, "rb");
if (!pad_file) {
printf("Error: Cannot open pad file\n");
free(encrypted_data);
return 1;
}
if (fseek(pad_file, pad_offset, SEEK_SET) != 0) {
printf("Error: Cannot seek to offset in pad file\n");
free(encrypted_data);
fclose(pad_file);
return 1;
}
unsigned char* pad_data = malloc(file_size);
if (fread(pad_data, 1, file_size, pad_file) != file_size) {
printf("Error: Cannot read pad data\n");
free(encrypted_data);
free(pad_data);
fclose(pad_file);
return 1;
}
fclose(pad_file);
// Use universal XOR operation for decryption
if (universal_xor_operation(encrypted_data, file_size, pad_data, encrypted_data) != 0) {
printf("Error: Decryption operation failed\n");
free(encrypted_data);
free(pad_data);
return 1;
}
// Write decrypted file
FILE* output_fp = fopen(output_file, "wb");
if (!output_fp) {
printf("Error: Cannot create output file %s\n", output_file);
free(encrypted_data);
free(pad_data);
return 1;
}
if (fwrite(encrypted_data, 1, file_size, output_fp) != file_size) {
printf("Error: Cannot write decrypted data\n");
free(encrypted_data);
free(pad_data);
fclose(output_fp);
return 1;
}
fclose(output_fp);
// Restore file permissions
if (chmod(output_file, file_mode) != 0) {
printf("Warning: Cannot restore file permissions\n");
}
printf("File decrypted successfully: %s\n", output_file);
printf("Restored permissions and metadata\n");
// Pause before returning to menu to let user see the success message
print_centered_header("File Decryption Complete", 1);
// Cleanup
free(encrypted_data);
free(pad_data);
return 0;
}
int decrypt_ascii_file(const char* input_file, const char* output_file) {
// Use universal decrypt function with file-to-file mode
return universal_decrypt(input_file, output_file, DECRYPT_MODE_FILE_TO_FILE);
}
/*
// MOVED TO src/pads.c - commented out here
// Construct pad and state file paths from checksum
void get_pad_path(const char* chksum, char* pad_path, char* state_path) {
snprintf(pad_path, 1024, "%s/%s.pad", current_pads_dir, chksum);
snprintf(state_path, 1024, "%s/%s.state", current_pads_dir, chksum);
}
*/
// Universal pad data loader - consolidates pad loading and validation logic
// Loads pad data at specified offset and validates pad availability
int load_pad_data(const char* pad_chksum, uint64_t offset, size_t length, unsigned char** pad_data) {
if (!pad_chksum || !pad_data) {
return 1; // Error: null pointer
}
char pad_path[1024];
char state_path[1024];
get_pad_path(pad_chksum, pad_path, state_path);
// Check if pad file exists
if (access(pad_path, R_OK) != 0) {
return 2; // Error: pad file not found
}
// Check pad file size
struct stat pad_stat;
if (stat(pad_path, &pad_stat) != 0) {
return 3; // Error: cannot get pad file size
}
if (offset + length > (uint64_t)pad_stat.st_size) {
return 4; // Error: not enough pad space
}
// Allocate memory for pad data
*pad_data = malloc(length);
if (!*pad_data) {
return 5; // Error: memory allocation failed
}
// Open and read pad file
FILE* pad_file = fopen(pad_path, "rb");
if (!pad_file) {
free(*pad_data);
*pad_data = NULL;
return 6; // Error: cannot open pad file
}
if (fseek(pad_file, offset, SEEK_SET) != 0) {
fclose(pad_file);
free(*pad_data);
*pad_data = NULL;
return 7; // Error: cannot seek to offset
}
if (fread(*pad_data, 1, length, pad_file) != length) {
fclose(pad_file);
free(*pad_data);
*pad_data = NULL;
return 8; // Error: cannot read pad data
}
fclose(pad_file);
return 0; // Success
}
// Universal pad integrity validator - consolidates pad validation logic
// Verifies pad checksum matches expected value for security
int validate_pad_integrity(const char* pad_path, const char* expected_chksum) {
if (!pad_path || !expected_chksum) {
return 1; // Error: null pointer
}
char current_chksum[MAX_HASH_LENGTH];
if (calculate_checksum(pad_path, current_chksum) != 0) {
return 2; // Error: cannot calculate checksum
}
if (strcmp(expected_chksum, current_chksum) != 0) {
return 3; // Error: checksum mismatch
}
return 0; // Success - pad integrity verified
}
// Calculate checksum with progress display for large files
int calculate_checksum_with_progress(const char* filename, char* checksum_hex, int display_progress, uint64_t file_size) {
FILE* file = fopen(filename, "rb");
if (!file) {
return 1;
}
unsigned char checksum[32];
unsigned char buffer[64 * 1024]; // 64KB buffer for large files
size_t bytes_read;
// Initialize checksum
memset(checksum, 0, 32);
size_t total_bytes = 0;
time_t start_time = time(NULL);
// Calculate XOR checksum of entire file with progress
while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) {
// Process this chunk with XOR checksum
for (size_t i = 0; i < bytes_read; i++) {
unsigned char bucket = (total_bytes + i) % 32;
checksum[bucket] ^= buffer[i] ^ (((total_bytes + i) >> 8) & 0xFF) ^
(((total_bytes + i) >> 16) & 0xFF) ^ (((total_bytes + i) >> 24) & 0xFF);
}
total_bytes += bytes_read;
// Show progress for large files (every 64MB or if display_progress is enabled)
if (display_progress && file_size > 10 * 1024 * 1024 && total_bytes % (64 * 1024 * 1024) == 0) {
show_progress(total_bytes, file_size, start_time);
}
}
// Final progress update
if (display_progress && file_size > 10 * 1024 * 1024) {
show_progress(file_size, file_size, start_time);
printf("\n");
}
fclose(file);
// Now encrypt the checksum with the first 32 bytes of the pad
fseek(file = fopen(filename, "rb"), 0, SEEK_SET);
unsigned char pad_key[32];
if (fread(pad_key, 1, 32, file) != 32) {
fclose(file);
return 1;
}
fclose(file);
// XOR encrypt the checksum with pad data to create unique identifier
unsigned char encrypted_checksum[32];
for (int i = 0; i < 32; i++) {
encrypted_checksum[i] = checksum[i] ^ pad_key[i];
}
// Convert to hex string (64 characters)
for (int i = 0; i < 32; i++) {
sprintf(checksum_hex + (i * 2), "%02x", encrypted_checksum[i]);
}
checksum_hex[64] = '\0';
return 0;
}