#define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #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, ¤t_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, ¤t_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(¤t_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; }