diff --git a/Makefile b/Makefile index 0a39809..d81032a 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,19 @@ CC = gcc CFLAGS = -Wall -Wextra -std=c99 -LIBS = -LIBS_STATIC = -static +LIBS = -lm +LIBS_STATIC = -static -lm TARGET = otp SOURCE = otp.c +CHACHA20_SOURCE = nostr_chacha20.c VERSION_SOURCE = src/version.c # Default build target $(TARGET): $(SOURCE) - $(CC) $(CFLAGS) -o $(TARGET) $(SOURCE) $(VERSION_SOURCE) $(LIBS) + $(CC) $(CFLAGS) -o $(TARGET) $(SOURCE) $(CHACHA20_SOURCE) $(VERSION_SOURCE) $(LIBS) # Static linking target static: $(SOURCE) - $(CC) $(CFLAGS) -o $(TARGET) $(SOURCE) $(VERSION_SOURCE) $(LIBS_STATIC) + $(CC) $(CFLAGS) -o $(TARGET) $(SOURCE) $(CHACHA20_SOURCE) $(VERSION_SOURCE) $(LIBS_STATIC) clean: rm -f $(TARGET) *.pad *.state diff --git a/nostr_chacha20.c b/nostr_chacha20.c new file mode 100644 index 0000000..bb95d37 --- /dev/null +++ b/nostr_chacha20.c @@ -0,0 +1,163 @@ +/* + * nostr_chacha20.c - ChaCha20 stream cipher implementation + * + * Implementation based on RFC 8439 "ChaCha20 and Poly1305 for IETF Protocols" + * + * This implementation is adapted from the RFC 8439 reference specification. + * It prioritizes correctness and clarity over performance optimization. + */ + +#include "nostr_chacha20.h" +#include + +/* + * ============================================================================ + * UTILITY MACROS AND FUNCTIONS + * ============================================================================ + */ + +/* Left rotate a 32-bit value by n bits */ +#define ROTLEFT(a, b) (((a) << (b)) | ((a) >> (32 - (b)))) + +/* Convert 4 bytes to 32-bit little-endian */ +static uint32_t bytes_to_u32_le(const uint8_t *bytes) { + return ((uint32_t)bytes[0]) | + ((uint32_t)bytes[1] << 8) | + ((uint32_t)bytes[2] << 16) | + ((uint32_t)bytes[3] << 24); +} + +/* Convert 32-bit to 4 bytes little-endian */ +static void u32_to_bytes_le(uint32_t val, uint8_t *bytes) { + bytes[0] = (uint8_t)(val & 0xff); + bytes[1] = (uint8_t)((val >> 8) & 0xff); + bytes[2] = (uint8_t)((val >> 16) & 0xff); + bytes[3] = (uint8_t)((val >> 24) & 0xff); +} + +/* + * ============================================================================ + * CHACHA20 CORE FUNCTIONS + * ============================================================================ + */ + +void chacha20_quarter_round(uint32_t state[16], int a, int b, int c, int d) { + state[a] += state[b]; + state[d] ^= state[a]; + state[d] = ROTLEFT(state[d], 16); + + state[c] += state[d]; + state[b] ^= state[c]; + state[b] = ROTLEFT(state[b], 12); + + state[a] += state[b]; + state[d] ^= state[a]; + state[d] = ROTLEFT(state[d], 8); + + state[c] += state[d]; + state[b] ^= state[c]; + state[b] = ROTLEFT(state[b], 7); +} + +void chacha20_init_state(uint32_t state[16], const uint8_t key[32], + uint32_t counter, const uint8_t nonce[12]) { + /* ChaCha20 constants "expand 32-byte k" */ + state[0] = 0x61707865; + state[1] = 0x3320646e; + state[2] = 0x79622d32; + state[3] = 0x6b206574; + + /* Key (8 words) */ + state[4] = bytes_to_u32_le(key + 0); + state[5] = bytes_to_u32_le(key + 4); + state[6] = bytes_to_u32_le(key + 8); + state[7] = bytes_to_u32_le(key + 12); + state[8] = bytes_to_u32_le(key + 16); + state[9] = bytes_to_u32_le(key + 20); + state[10] = bytes_to_u32_le(key + 24); + state[11] = bytes_to_u32_le(key + 28); + + /* Counter (1 word) */ + state[12] = counter; + + /* Nonce (3 words) */ + state[13] = bytes_to_u32_le(nonce + 0); + state[14] = bytes_to_u32_le(nonce + 4); + state[15] = bytes_to_u32_le(nonce + 8); +} + +void chacha20_serialize_state(const uint32_t state[16], uint8_t output[64]) { + for (int i = 0; i < 16; i++) { + u32_to_bytes_le(state[i], output + (i * 4)); + } +} + +int chacha20_block(const uint8_t key[32], uint32_t counter, + const uint8_t nonce[12], uint8_t output[64]) { + uint32_t state[16]; + uint32_t initial_state[16]; + + /* Initialize state */ + chacha20_init_state(state, key, counter, nonce); + + /* Save initial state for later addition */ + memcpy(initial_state, state, sizeof(initial_state)); + + /* Perform 20 rounds (10 iterations of the 8 quarter rounds) */ + for (int i = 0; i < 10; i++) { + /* Column rounds */ + chacha20_quarter_round(state, 0, 4, 8, 12); + chacha20_quarter_round(state, 1, 5, 9, 13); + chacha20_quarter_round(state, 2, 6, 10, 14); + chacha20_quarter_round(state, 3, 7, 11, 15); + + /* Diagonal rounds */ + chacha20_quarter_round(state, 0, 5, 10, 15); + chacha20_quarter_round(state, 1, 6, 11, 12); + chacha20_quarter_round(state, 2, 7, 8, 13); + chacha20_quarter_round(state, 3, 4, 9, 14); + } + + /* Add initial state back (prevents slide attacks) */ + for (int i = 0; i < 16; i++) { + state[i] += initial_state[i]; + } + + /* Serialize to output bytes */ + chacha20_serialize_state(state, output); + + return 0; +} + +int chacha20_encrypt(const uint8_t key[32], uint32_t counter, + const uint8_t nonce[12], const uint8_t* input, + uint8_t* output, size_t length) { + uint8_t keystream[CHACHA20_BLOCK_SIZE]; + size_t offset = 0; + + while (length > 0) { + /* Generate keystream block */ + int ret = chacha20_block(key, counter, nonce, keystream); + if (ret != 0) { + return ret; + } + + /* XOR with input to produce output */ + size_t block_len = (length < CHACHA20_BLOCK_SIZE) ? length : CHACHA20_BLOCK_SIZE; + for (size_t i = 0; i < block_len; i++) { + output[offset + i] = input[offset + i] ^ keystream[i]; + } + + /* Move to next block */ + offset += block_len; + length -= block_len; + counter++; + + /* Check for counter overflow */ + if (counter == 0) { + return -1; /* Counter wrapped around */ + } + } + + return 0; +} diff --git a/nostr_chacha20.h b/nostr_chacha20.h new file mode 100644 index 0000000..93a2f35 --- /dev/null +++ b/nostr_chacha20.h @@ -0,0 +1,115 @@ +/* + * nostr_chacha20.h - ChaCha20 stream cipher implementation + * + * Implementation based on RFC 8439 "ChaCha20 and Poly1305 for IETF Protocols" + * + * This is a small, portable implementation for NIP-44 support in the NOSTR library. + * The implementation prioritizes correctness and simplicity over performance. + */ + +#ifndef NOSTR_CHACHA20_H +#define NOSTR_CHACHA20_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ============================================================================ + * CONSTANTS AND DEFINITIONS + * ============================================================================ + */ + +#define CHACHA20_KEY_SIZE 32 /* 256 bits */ +#define CHACHA20_NONCE_SIZE 12 /* 96 bits */ +#define CHACHA20_BLOCK_SIZE 64 /* 512 bits */ + +/* + * ============================================================================ + * CORE CHACHA20 FUNCTIONS + * ============================================================================ + */ + +/** + * ChaCha20 quarter round operation + * + * Operates on four 32-bit words performing the core ChaCha20 quarter round: + * a += b; d ^= a; d <<<= 16; + * c += d; b ^= c; b <<<= 12; + * a += b; d ^= a; d <<<= 8; + * c += d; b ^= c; b <<<= 7; + * + * @param state[in,out] ChaCha state as 16 32-bit words + * @param a, b, c, d Indices into state array for quarter round + */ +void chacha20_quarter_round(uint32_t state[16], int a, int b, int c, int d); + +/** + * ChaCha20 block function + * + * Transforms a 64-byte input block using ChaCha20 algorithm with 20 rounds. + * + * @param key[in] 32-byte key + * @param counter[in] 32-bit block counter + * @param nonce[in] 12-byte nonce + * @param output[out] 64-byte output buffer + * @return 0 on success, negative on error + */ +int chacha20_block(const uint8_t key[32], uint32_t counter, + const uint8_t nonce[12], uint8_t output[64]); + +/** + * ChaCha20 encryption/decryption + * + * Encrypts or decrypts data using ChaCha20 stream cipher. + * Since ChaCha20 is a stream cipher, encryption and decryption are the same operation. + * + * @param key[in] 32-byte key + * @param counter[in] Initial 32-bit counter value + * @param nonce[in] 12-byte nonce + * @param input[in] Input data to encrypt/decrypt + * @param output[out] Output buffer (can be same as input) + * @param length[in] Length of input data in bytes + * @return 0 on success, negative on error + */ +int chacha20_encrypt(const uint8_t key[32], uint32_t counter, + const uint8_t nonce[12], const uint8_t* input, + uint8_t* output, size_t length); + +/* + * ============================================================================ + * UTILITY FUNCTIONS + * ============================================================================ + */ + +/** + * Initialize ChaCha20 state matrix + * + * Sets up the initial 16-word state matrix with constants, key, counter, and nonce. + * + * @param state[out] 16-word state array to initialize + * @param key[in] 32-byte key + * @param counter[in] 32-bit block counter + * @param nonce[in] 12-byte nonce + */ +void chacha20_init_state(uint32_t state[16], const uint8_t key[32], + uint32_t counter, const uint8_t nonce[12]); + +/** + * Serialize ChaCha20 state to bytes + * + * Converts 16 32-bit words to 64 bytes in little-endian format. + * + * @param state[in] 16-word state array + * @param output[out] 64-byte output buffer + */ +void chacha20_serialize_state(const uint32_t state[16], uint8_t output[64]); + +#ifdef __cplusplus +} +#endif + +#endif /* NOSTR_CHACHA20_H */ diff --git a/otp.c b/otp.c index f16f11f..d905025 100644 --- a/otp.c +++ b/otp.c @@ -12,7 +12,9 @@ #include #include #include +#include #include "src/version.h" +#include "nostr_chacha20.h" // Custom base64 character set static const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; @@ -86,7 +88,6 @@ int launch_file_manager(const char* start_directory, char* selected_file, size_t // Core functions int generate_pad(uint64_t size_bytes, int show_progress); -int generate_pad_with_entropy(uint64_t size_bytes, int show_progress, int use_keyboard_entropy); int encrypt_text(const char* pad_identifier, const char* input_text); int decrypt_text(const char* pad_identifier, const char* encrypted_message); int decrypt_text_silent(const char* pad_identifier, const char* encrypted_message); @@ -95,12 +96,34 @@ int decrypt_file(const char* input_file, const char* output_file); int decrypt_binary_file(FILE* input_fp, const char* output_file); int decrypt_ascii_file(const char* input_file, const char* output_file); -// Keyboard entropy functions +// Enhanced entropy system functions +typedef struct { + size_t target_bytes; // Target entropy to collect + size_t collected_bytes; // Bytes collected so far + size_t unique_keys; // Number of unique keys pressed + double collection_start_time; // Start timestamp + double last_keypress_time; // Last keypress timestamp + unsigned char quality_score; // Entropy quality (0-100) + int auto_complete_enabled; // Allow auto-complete at minimum + unsigned char key_histogram[256]; // Track key frequency +} entropy_collection_state_t; + int setup_raw_terminal(struct termios* original_termios); void restore_terminal(struct termios* original_termios); -int collect_keyboard_entropy(unsigned char* entropy_buffer, size_t max_size, size_t* collected); -void simple_entropy_mix(unsigned char* urandom_buffer, size_t buffer_size, - const unsigned char* entropy_data, size_t entropy_size); +int collect_entropy_with_feedback(unsigned char* entropy_buffer, size_t target_bytes, + size_t* collected_bytes, int allow_early_exit); +void display_entropy_progress(const entropy_collection_state_t* state); +void draw_progress_bar(double percentage, int width); +void draw_quality_bar(double quality, int width, const char* label); +double calculate_timing_quality(const entropy_collection_state_t* state); +double calculate_variety_quality(const entropy_collection_state_t* state); +unsigned char calculate_overall_quality(const entropy_collection_state_t* state); +double get_precise_time(void); +int derive_chacha20_params(const unsigned char* entropy_data, size_t entropy_size, + unsigned char key[32], unsigned char nonce[12]); +int add_entropy_to_pad(const char* pad_chksum, const unsigned char* entropy_data, + size_t entropy_size, int show_progress); +int handle_add_entropy_to_pad(const char* pad_chksum); // Directory management int ensure_pads_directory(void); @@ -159,8 +182,6 @@ void print_usage(const char* program_name); //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// - - int main(int argc, char* argv[]) { // Load preferences first load_preferences(); @@ -202,44 +223,13 @@ int main(int argc, char* argv[]) { } } -int interactive_mode(void) { - char input[10]; - - while (1) { - show_main_menu(); - - if (!fgets(input, sizeof(input), stdin)) { - printf("Goodbye!\n"); - break; - } - - char choice = toupper(input[0]); - - switch (choice) { - case 'T': - handle_text_encrypt(); - break; - case 'F': - handle_file_encrypt(); - break; - case 'D': - handle_decrypt_menu(); - break; - case 'P': - handle_pads_menu(); - break; - case 'X': - case 'Q': - printf("Goodbye!\n"); - return 0; - default: - printf("Invalid choice. Please try again.\n"); - break; - } - } - - return 0; -} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// COMMAND LINE MODE +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// int command_line_mode(int argc, char* argv[]) { // Check for help flags first @@ -261,7 +251,7 @@ int command_line_mode(int argc, char* argv[]) { printf("Error: Invalid size format\n"); return 1; } - return generate_pad_with_entropy(size, 1, 0); // No keyboard entropy for command line + return generate_pad(size, 1); // Use simplified pad generation } else if (strcmp(argv[1], "encrypt") == 0 || strcmp(argv[1], "-e") == 0) { if (argc < 2 || argc > 4) { @@ -410,6 +400,54 @@ int command_line_mode(int argc, char* argv[]) { } } + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// INTERACTIVE MODE +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +int interactive_mode(void) { + char input[10]; + + while (1) { + show_main_menu(); + + if (!fgets(input, sizeof(input), stdin)) { + printf("Goodbye!\n"); + break; + } + + char choice = toupper(input[0]); + + switch (choice) { + case 'T': + handle_text_encrypt(); + break; + case 'F': + handle_file_encrypt(); + break; + case 'D': + handle_decrypt_menu(); + break; + case 'P': + handle_pads_menu(); + break; + case 'X': + case 'Q': + printf("Goodbye!\n"); + return 0; + default: + printf("Invalid choice. Please try again.\n"); + break; + } + } + + return 0; +} + + void show_main_menu(void) { printf("\n\n\n\n=========================== Main Menu - OTP %s ===========================\n\n", get_version() ); @@ -440,25 +478,11 @@ int handle_generate_menu(void) { return 1; } - // Ask about keyboard entropy - printf("\nAdd keyboard entropy for enhanced security? (y/N): "); - char entropy_choice[10]; - int use_keyboard_entropy = 0; - - if (fgets(entropy_choice, sizeof(entropy_choice), stdin)) { - if (entropy_choice[0] == 'y' || entropy_choice[0] == 'Y') { - use_keyboard_entropy = 1; - } - } - double size_gb = (double)size / (1024.0 * 1024.0 * 1024.0); - if (use_keyboard_entropy) { - printf("Generating %.2f GB pad with keyboard entropy...\n", size_gb); - } else { - printf("Generating %.2f GB pad...\n", size_gb); - } + printf("Generating %.2f GB pad...\n", size_gb); + printf("Note: Use 'Add entropy' in Pads menu to enhance randomness after creation.\n"); - return generate_pad_with_entropy(size, 1, use_keyboard_entropy); + return generate_pad(size, 1); } int handle_encrypt_menu(void) { @@ -1026,9 +1050,15 @@ void show_progress(uint64_t current, uint64_t total, time_t start_time) { } int generate_pad(uint64_t size_bytes, int display_progress) { - char temp_filename[32]; - char pad_filename[MAX_HASH_LENGTH + 10]; - char state_filename[MAX_HASH_LENGTH + 10]; + // Ensure pads directory exists + if (ensure_pads_directory() != 0) { + printf("Error: Cannot create pads directory\n"); + return 1; + } + + char temp_filename[64]; + char pad_path[MAX_HASH_LENGTH + 20]; + char state_path[MAX_HASH_LENGTH + 20]; char chksum_hex[MAX_HASH_LENGTH]; // Create temporary filename @@ -1099,190 +1129,6 @@ int generate_pad(uint64_t size_bytes, int display_progress) { return 1; } - // Rename file to its chksum - snprintf(pad_filename, sizeof(pad_filename), "%s.pad", chksum_hex); - snprintf(state_filename, sizeof(state_filename), "%s.state", chksum_hex); - - if (rename(temp_filename, pad_filename) != 0) { - printf("Error: Cannot rename pad file to chksum-based name\n"); - unlink(temp_filename); - return 1; - } - - // Set pad file to read-only - if (chmod(pad_filename, S_IRUSR) != 0) { - printf("Warning: Cannot set pad file to read-only\n"); - } - - // Initialize state file with offset 0 - if (write_state_offset(chksum_hex, 0) != 0) { - printf("Error: Failed to create state file\n"); - unlink(pad_filename); - return 1; - } - - double size_gb = (double)size_bytes / (1024.0 * 1024.0 * 1024.0); - printf("Generated pad: %s (%.2f GB)\n", pad_filename, size_gb); - printf("Pad chksum: %s\n", chksum_hex); - printf("State file: %s\n", state_filename); - printf("Pad file set to read-only\n"); - - return 0; -} - -int generate_pad_with_entropy(uint64_t size_bytes, int display_progress, int use_keyboard_entropy) { - if (ensure_pads_directory() != 0) { - printf("Error: Cannot create pads directory\n"); - return 1; - } - - char temp_filename[64]; - char pad_path[MAX_HASH_LENGTH + 20]; - char state_path[MAX_HASH_LENGTH + 20]; - char chksum_hex[MAX_HASH_LENGTH]; - - // Create temporary filename - snprintf(temp_filename, sizeof(temp_filename), "temp_%ld.pad", time(NULL)); - - FILE* urandom = fopen("/dev/urandom", "rb"); - if (!urandom) { - printf("Error: Cannot open /dev/urandom\n"); - return 1; - } - - FILE* pad_file = fopen(temp_filename, "wb"); - if (!pad_file) { - printf("Error: Cannot create temporary pad file %s\n", temp_filename); - fclose(urandom); - return 1; - } - - // Setup keyboard entropy collection if requested - struct termios original_termios; - unsigned char* entropy_buffer = NULL; - size_t entropy_collected = 0; - int terminal_setup = 0; - - if (use_keyboard_entropy) { - entropy_buffer = malloc(MAX_ENTROPY_BUFFER); - if (!entropy_buffer) { - printf("Error: Cannot allocate entropy buffer\n"); - fclose(urandom); - fclose(pad_file); - unlink(temp_filename); - return 1; - } - - if (setup_raw_terminal(&original_termios) == 0) { - terminal_setup = 1; - printf("Type random keys to add entropy (optional):\n"); - } else { - printf("Warning: Cannot setup terminal for keyboard entropy collection\n"); - use_keyboard_entropy = 0; - free(entropy_buffer); - entropy_buffer = NULL; - } - } - - unsigned char urandom_buffer[64 * 1024]; // 64KB buffer - unsigned char output_buffer[64 * 1024]; - uint64_t bytes_written = 0; - - if (display_progress) { - printf("Generating pad...\n"); - if (use_keyboard_entropy) { - printf("(Keyboard entropy: collecting...)\n"); - } - } - - while (bytes_written < size_bytes) { - uint64_t chunk_size = sizeof(urandom_buffer); - if (size_bytes - bytes_written < chunk_size) { - chunk_size = size_bytes - bytes_written; - } - - // Read from /dev/urandom - if (fread(urandom_buffer, 1, (size_t)chunk_size, urandom) != (size_t)chunk_size) { - printf("Error: Failed to read from /dev/urandom\n"); - if (terminal_setup) restore_terminal(&original_termios); - if (entropy_buffer) free(entropy_buffer); - fclose(urandom); - fclose(pad_file); - unlink(temp_filename); - return 1; - } - - if (use_keyboard_entropy && terminal_setup) { - // Collect available keyboard entropy - size_t chunk_entropy = 0; - collect_keyboard_entropy(entropy_buffer + entropy_collected, - MAX_ENTROPY_BUFFER - entropy_collected, &chunk_entropy); - entropy_collected += chunk_entropy; - - if (entropy_collected > 512) { // Have enough entropy to mix - // Copy urandom data to output buffer - memcpy(output_buffer, urandom_buffer, chunk_size); - - // Simple XOR mixing with keyboard entropy - simple_entropy_mix(output_buffer, chunk_size, entropy_buffer, entropy_collected); - - // Reset entropy buffer for next chunk - entropy_collected = 0; - } else { - // Not enough entropy yet, use urandom only - memcpy(output_buffer, urandom_buffer, chunk_size); - } - } else { - // No keyboard entropy, use urandom directly - memcpy(output_buffer, urandom_buffer, chunk_size); - } - - if (fwrite(output_buffer, 1, (size_t)chunk_size, pad_file) != (size_t)chunk_size) { - printf("Error: Failed to write to pad file\n"); - if (terminal_setup) restore_terminal(&original_termios); - if (entropy_buffer) free(entropy_buffer); - fclose(urandom); - fclose(pad_file); - unlink(temp_filename); - return 1; - } - - bytes_written += chunk_size; - - if (display_progress && bytes_written % PROGRESS_UPDATE_INTERVAL == 0) { - printf("\rProgress: %.1f%% ", (double)bytes_written / size_bytes * 100.0); - if (use_keyboard_entropy && terminal_setup) { - printf("(keyboard entropy: %.1fKB) ", (double)entropy_collected / 1024.0); - } - fflush(stdout); - } - } - - if (terminal_setup) { - restore_terminal(&original_termios); - } - if (entropy_buffer) { - free(entropy_buffer); - } - - if (display_progress) { - printf("\rProgress: 100.0%%"); - if (use_keyboard_entropy) { - printf(" (keyboard entropy: MIXED)"); - } - printf("\n"); - } - - fclose(urandom); - fclose(pad_file); - - // Calculate XOR checksum of the pad file - if (calculate_checksum(temp_filename, chksum_hex) != 0) { - printf("Error: Cannot calculate pad checksum\n"); - unlink(temp_filename); - return 1; - } - // Get final paths in pads directory get_pad_path(chksum_hex, pad_path, state_path); @@ -1326,7 +1172,7 @@ int generate_pad_with_entropy(uint64_t size_bytes, int display_progress, int use printf("Warning: Cannot set pad file to read-only\n"); } - // Initialize state file with offset 32 (first 32 bytes used for checksum encryption) + // Initialize state file with offset 32 (first 32 bytes reserved for checksum encryption) FILE* state_file = fopen(state_path, "wb"); if (state_file) { uint64_t reserved_bytes = 32; @@ -1342,10 +1188,139 @@ int generate_pad_with_entropy(uint64_t size_bytes, int display_progress, int use printf("Generated pad: %s (%.2f GB)\n", pad_path, size_gb); printf("Pad checksum: %s\n", chksum_hex); printf("State file: %s\n", state_path); - if (use_keyboard_entropy) { - printf("Enhanced with keyboard entropy!\n"); - } printf("Pad file set to read-only\n"); + printf("Use 'Add entropy' in Pads menu to enhance randomness.\n"); + + return 0; +} + +// In-place pad entropy addition using Chacha20 +int add_entropy_to_pad(const char* pad_chksum, const unsigned char* entropy_data, + size_t entropy_size, int display_progress) { + if (!pad_chksum || !entropy_data || entropy_size < 512) { + printf("Error: Invalid entropy data or insufficient entropy\n"); + return 1; + } + + // Derive Chacha20 key and nonce from entropy + unsigned char key[32], nonce[12]; + if (derive_chacha20_params(entropy_data, entropy_size, key, nonce) != 0) { + printf("Error: Failed to derive Chacha20 parameters from entropy\n"); + return 1; + } + + // Get pad file path + char pad_path[1024]; + char state_path[1024]; + get_pad_path(pad_chksum, pad_path, state_path); + + // Check if pad exists and get size + struct stat pad_stat; + if (stat(pad_path, &pad_stat) != 0) { + printf("Error: Pad file not found: %s\n", pad_path); + return 1; + } + + uint64_t pad_size = pad_stat.st_size; + + // Open pad file for read/write + FILE* pad_file = fopen(pad_path, "r+b"); + if (!pad_file) { + printf("Error: Cannot open pad file for modification: %s\n", pad_path); + printf("Note: Pad files are read-only. Temporarily changing permissions...\n"); + + // Try to make writable temporarily + if (chmod(pad_path, S_IRUSR | S_IWUSR) != 0) { + printf("Error: Cannot change pad file permissions\n"); + return 1; + } + + pad_file = fopen(pad_path, "r+b"); + if (!pad_file) { + printf("Error: Still cannot open pad file for modification\n"); + // Restore read-only + chmod(pad_path, S_IRUSR); + return 1; + } + } + + if (display_progress) { + printf("Adding entropy to pad using Chacha20...\n"); + printf("Pad size: %.2f GB (%lu bytes)\n", (double)pad_size / (1024.0*1024.0*1024.0), pad_size); + } + + // Process pad in chunks + unsigned char buffer[64 * 1024]; // 64KB chunks + unsigned char keystream[64 * 1024]; + uint64_t offset = 0; + uint32_t counter = 0; + time_t start_time = time(NULL); + + while (offset < pad_size) { + size_t chunk_size = sizeof(buffer); + if (pad_size - offset < chunk_size) { + chunk_size = pad_size - offset; + } + + // Read current pad data + if (fread(buffer, 1, chunk_size, pad_file) != chunk_size) { + printf("Error: Cannot read pad data at offset %lu\n", offset); + fclose(pad_file); + chmod(pad_path, S_IRUSR); // Restore read-only + return 1; + } + + // Generate keystream for this chunk + if (chacha20_encrypt(key, counter, nonce, buffer, keystream, chunk_size) != 0) { + printf("Error: Chacha20 keystream generation failed\n"); + fclose(pad_file); + chmod(pad_path, S_IRUSR); + return 1; + } + + // XOR existing pad with keystream (adds entropy) + for (size_t i = 0; i < chunk_size; i++) { + buffer[i] ^= keystream[i]; + } + + // Seek back and write modified data + if (fseek(pad_file, offset, SEEK_SET) != 0) { + printf("Error: Cannot seek to offset %lu\n", offset); + fclose(pad_file); + chmod(pad_path, S_IRUSR); + return 1; + } + + if (fwrite(buffer, 1, chunk_size, pad_file) != chunk_size) { + printf("Error: Cannot write modified pad data\n"); + fclose(pad_file); + chmod(pad_path, S_IRUSR); + return 1; + } + + offset += chunk_size; + counter += (chunk_size + 63) / 64; // Round up for block count + + // Show progress for large pads + if (display_progress && offset % (64 * 1024 * 1024) == 0) { // Every 64MB + show_progress(offset, pad_size, start_time); + } + } + + fclose(pad_file); + + // Restore read-only permissions + if (chmod(pad_path, S_IRUSR) != 0) { + printf("Warning: Cannot restore pad file to read-only\n"); + } + + if (display_progress) { + show_progress(pad_size, pad_size, start_time); + printf("\n✓ Entropy successfully added to pad using Chacha20\n"); + printf("✓ Pad integrity maintained\n"); + printf("✓ %zu bytes of entropy distributed across entire pad\n", entropy_size); + printf("✓ Pad restored to read-only mode\n"); + } return 0; } @@ -2110,38 +2085,107 @@ void restore_terminal(struct termios* original_termios) { fcntl(STDIN_FILENO, F_SETFL, flags & ~O_NONBLOCK); } -int collect_keyboard_entropy(unsigned char* entropy_buffer, size_t max_size, size_t* collected) { - struct timespec timestamp; +// Enhanced entropy collection with visual feedback +int collect_entropy_with_feedback(unsigned char* entropy_buffer, size_t target_bytes, + size_t* collected_bytes, int allow_early_exit) { + struct termios original_termios; + entropy_collection_state_t state = {0}; + + // Initialize state + state.target_bytes = target_bytes; + state.auto_complete_enabled = allow_early_exit; + state.collection_start_time = get_precise_time(); + + // Setup raw terminal + if (setup_raw_terminal(&original_termios) != 0) { + printf("Error: Cannot setup terminal for entropy collection\n"); + return 1; + } + + // Clear screen area for display + printf("\n\n\n\n\n\n"); + unsigned char entropy_block[16]; + struct timespec timestamp; uint32_t sequence_counter = 0; char key; - *collected = 0; + unsigned char seen_keys[256] = {0}; - while (*collected < max_size - 16) { + *collected_bytes = 0; + + while (state.collected_bytes < target_bytes) { + // Update display + state.quality_score = calculate_overall_quality(&state); + display_entropy_progress(&state); + + // Non-blocking read if (read(STDIN_FILENO, &key, 1) == 1) { + // Handle ESC key for early exit + if (key == 27 && allow_early_exit && state.collected_bytes >= 1024) { + break; // Early exit allowed + } + + // Record keypress timing + double current_time = get_precise_time(); + state.last_keypress_time = current_time; + + // Update key histogram + state.key_histogram[(unsigned char)key]++; + + // Get high precision timestamp clock_gettime(CLOCK_MONOTONIC, ×tamp); - // Create entropy block: [key][timestamp][sequence_counter] + // Create enhanced entropy block: [key][timestamp][sequence][quality_bits] entropy_block[0] = key; memcpy(&entropy_block[1], ×tamp.tv_sec, 8); memcpy(&entropy_block[9], ×tamp.tv_nsec, 4); - memcpy(&entropy_block[13], &sequence_counter, 3); + memcpy(&entropy_block[13], &sequence_counter, 2); + entropy_block[15] = (unsigned char)(current_time * 1000) & 0xFF; // Sub-millisecond timing // Add to entropy buffer - memcpy(entropy_buffer + *collected, entropy_block, 16); - *collected += 16; + if (state.collected_bytes + 16 <= MAX_ENTROPY_BUFFER) { + memcpy(entropy_buffer + state.collected_bytes, entropy_block, 16); + state.collected_bytes += 16; + } + sequence_counter++; + + // Track unique keys + if (!seen_keys[(unsigned char)key]) { + seen_keys[(unsigned char)key] = 1; + state.unique_keys++; + } } else { - // No key available, add some timing entropy + // No key available, add timing entropy clock_gettime(CLOCK_MONOTONIC, ×tamp); - if (*collected + 12 < max_size) { - memcpy(entropy_buffer + *collected, ×tamp, 12); - *collected += 12; + if (state.collected_bytes + 12 < MAX_ENTROPY_BUFFER) { + memcpy(entropy_buffer + state.collected_bytes, ×tamp, 12); + state.collected_bytes += 12; } usleep(1000); // 1ms delay } + + // Auto-complete at target if enabled + if (state.collected_bytes >= target_bytes) { + break; + } } + // Final display update + state.quality_score = calculate_overall_quality(&state); + display_entropy_progress(&state); + + // Summary + double collection_time = get_precise_time() - state.collection_start_time; + printf("\n\n✓ Entropy collection complete!\n"); + printf(" Collected: %zu bytes in %.1f seconds\n", state.collected_bytes, collection_time); + printf(" Quality: %d%% (Excellent: 80%%+, Good: 60%%+)\n", state.quality_score); + printf(" Unique keys: %zu\n", state.unique_keys); + + // Restore terminal + restore_terminal(&original_termios); + + *collected_bytes = state.collected_bytes; return 0; } @@ -2633,18 +2677,202 @@ unsigned char* custom_base64_decode(const char* input, int* output_length) { return decoded; } -// Simple keyboard entropy mixing function -void simple_entropy_mix(unsigned char* urandom_buffer, size_t buffer_size, - const unsigned char* entropy_data, size_t entropy_size) { - if (!entropy_data || entropy_size == 0) return; +// Enhanced entropy system implementation + +double get_precise_time(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec + ts.tv_nsec / 1000000000.0; +} + +void draw_progress_bar(double percentage, int width) { + int filled = (int)(percentage / 100.0 * width); + if (filled > width) filled = width; - for (size_t i = 0; i < buffer_size; i++) { - // XOR with entropy data in a rotating pattern - unsigned char entropy_byte = entropy_data[i % entropy_size]; - // Mix position information - entropy_byte ^= (i & 0xFF) ^ ((i >> 8) & 0xFF); - urandom_buffer[i] ^= entropy_byte; + printf("["); + for (int i = 0; i < filled; i++) { + printf("█"); } + for (int i = filled; i < width; i++) { + printf("░"); + } + printf("]"); +} + +void draw_quality_bar(double quality, int width, const char* label) { + int filled = (int)(quality / 100.0 * width); + if (filled > width) filled = width; + + // Color coding based on quality + const char* color; + if (quality >= 80) color = "\033[32m"; // Green + else if (quality >= 60) color = "\033[33m"; // Yellow + else color = "\033[31m"; // Red + + printf("%s", color); + draw_progress_bar(quality, width); + printf("\033[0m %-10s", label); // Reset color +} + +double calculate_timing_quality(const entropy_collection_state_t* state) { + // Analyze timing variance between keypresses + if (state->collected_bytes < 32) return 0.0; // Need minimum data + + // Simplified timing quality based on collection rate and variation + double elapsed = get_precise_time() - state->collection_start_time; + if (elapsed < 0.1) return 0.0; + + double rate = state->collected_bytes / elapsed; + + // Optimal rate is around 50-200 bytes/second (moderate typing with good timing variance) + if (rate >= 50 && rate <= 200) return 90.0; + if (rate >= 20 && rate <= 500) return 70.0; + if (rate >= 10 && rate <= 1000) return 50.0; + return 30.0; +} + +double calculate_variety_quality(const entropy_collection_state_t* state) { + // Analyze key variety and distribution + if (state->collected_bytes < 16) return 0.0; + + // Calculate entropy from key histogram + double entropy = 0.0; + size_t total_keys = 0; + + // Count total keypresses + for (int i = 0; i < 256; i++) { + total_keys += state->key_histogram[i]; + } + + if (total_keys == 0) return 0.0; + + // Calculate Shannon entropy + for (int i = 0; i < 256; i++) { + if (state->key_histogram[i] > 0) { + double p = (double)state->key_histogram[i] / total_keys; + entropy -= p * log2(p); + } + } + + // Convert entropy to quality score (0-100) + double max_entropy = log2(256); // Perfect entropy for 8-bit keyspace + double normalized_entropy = entropy / max_entropy; + + // Scale based on unique keys as well + double unique_key_factor = (double)state->unique_keys / 50.0; // 50+ unique keys is excellent + if (unique_key_factor > 1.0) unique_key_factor = 1.0; + + return (normalized_entropy * 70.0 + unique_key_factor * 30.0); +} + +unsigned char calculate_overall_quality(const entropy_collection_state_t* state) { + double timing = calculate_timing_quality(state); + double variety = calculate_variety_quality(state); + + // Simple collection progress bonus + double progress_bonus = (double)state->collected_bytes / state->target_bytes * 20.0; + if (progress_bonus > 20.0) progress_bonus = 20.0; + + // Weighted average + double overall = (timing * 0.4 + variety * 0.4 + progress_bonus); + if (overall > 100.0) overall = 100.0; + + return (unsigned char)overall; +} + +void display_entropy_progress(const entropy_collection_state_t* state) { + // Calculate percentages + double progress = (double)state->collected_bytes / state->target_bytes * 100.0; + if (progress > 100.0) progress = 100.0; + + double quality = state->quality_score; + double timing_quality = calculate_timing_quality(state); + double variety_quality = calculate_variety_quality(state); + + // Clear previous output and redraw + printf("\033[2K\r"); // Clear line + printf("\033[A\033[2K\r"); // Move up and clear + printf("\033[A\033[2K\r"); // Move up and clear + printf("\033[A\033[2K\r"); // Move up and clear + printf("\033[A\033[2K\r"); // Move up and clear + printf("\033[A\033[2K\r"); // Move up and clear + + // Header + printf("Adding Entropy to Pad - Target: %zu bytes\n\n", state->target_bytes); + + // Main progress bar + printf("Progress: "); + draw_progress_bar(progress, 50); + printf(" %.1f%% (%zu/%zu bytes)\n", progress, state->collected_bytes, state->target_bytes); + + // Quality indicators + printf("Quality: "); + draw_quality_bar(quality, 50, "OVERALL"); + printf("\n"); + + printf("Timing: "); + draw_quality_bar(timing_quality, 50, "VARIED"); + printf("\n"); + + printf("Keys: "); + draw_quality_bar(variety_quality, 50, "DIVERSE"); + printf("\n"); + + // Instructions + if (state->collected_bytes >= 1024 && state->auto_complete_enabled) { + printf("\nPress ESC to finish (minimum reached) or continue typing..."); + } else if (state->collected_bytes < 1024) { + printf("\nType random keys... (%zu more bytes needed)", 1024 - state->collected_bytes); + } else { + printf("\nType random keys or press ESC when satisfied..."); + } + + fflush(stdout); +} + +// Chacha20 key derivation from collected entropy +int derive_chacha20_params(const unsigned char* entropy_data, size_t entropy_size, + unsigned char key[32], unsigned char nonce[12]) { + if (!entropy_data || entropy_size < 512 || !key || !nonce) { + return 1; // Error: insufficient entropy or null pointers + } + + // Phase 1: Generate base key from entropy using enhanced XOR checksum method + unsigned char enhanced_checksum[44]; // 32 key + 12 nonce + memset(enhanced_checksum, 0, 44); + + // Mix entropy data similar to calculate_checksum but for 44 bytes + for (size_t i = 0; i < entropy_size; i++) { + unsigned char bucket = i % 44; + enhanced_checksum[bucket] ^= entropy_data[i] ^ + ((i >> 8) & 0xFF) ^ + ((i >> 16) & 0xFF) ^ + ((i >> 24) & 0xFF); + } + + // Phase 2: Add system entropy for additional randomness + unsigned char system_entropy[32]; + FILE* urandom = fopen("/dev/urandom", "rb"); + if (!urandom) { + return 2; // Error: cannot access system entropy + } + + if (fread(system_entropy, 1, 32, urandom) != 32) { + fclose(urandom); + return 2; // Error: insufficient system entropy + } + fclose(urandom); + + // Mix system entropy into derived key + for (int i = 0; i < 32; i++) { + enhanced_checksum[i] ^= system_entropy[i]; + } + + // Extract key and nonce + memcpy(key, enhanced_checksum, 32); + memcpy(nonce, enhanced_checksum + 32, 12); + + return 0; // Success } //////////////////////////////////////////////////////////////////////////////// @@ -3848,6 +4076,7 @@ int handle_pads_menu(void) { printf("\nPad Actions:\n"); printf(" \033[4mI\033[0mnfo - Show detailed pad information\n"); + printf(" \033[4mA\033[0mdd entropy - Enhance pad randomness\n"); printf(" \033[4mB\033[0mack to pad list\n"); printf("\nSelect action: "); @@ -3860,6 +4089,9 @@ int handle_pads_menu(void) { char action_choice = toupper(action[0]); if (action_choice == 'I') { return show_pad_info(pads[selected_pad].chksum); + } else if (action_choice == 'A') { + // Handle entropy addition + return handle_add_entropy_to_pad(pads[selected_pad].chksum); } // Default: back to pad list (recursive call) @@ -3996,6 +4228,103 @@ void get_directory_display(const char* file_path, char* result, size_t result_si } } +int handle_add_entropy_to_pad(const char* pad_chksum) { + printf("\n=== Add Entropy to Pad: %.16s... ===\n", pad_chksum); + printf("This will enhance the randomness of your pad using keyboard entropy.\n"); + printf("The entropy will be processed with Chacha20 and distributed throughout the entire pad.\n\n"); + + printf("Entropy collection options:\n"); + printf(" 1. Recommended (2048 bytes) - Optimal security\n"); + printf(" 2. Minimum (1024 bytes) - Good security\n"); + printf(" 3. Maximum (4096 bytes) - Maximum security\n"); + printf(" 4. Custom amount\n"); + printf("Enter choice (1-4): "); + + char choice_input[10]; + if (!fgets(choice_input, sizeof(choice_input), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + size_t target_bytes = 2048; // Default + int choice = atoi(choice_input); + + switch (choice) { + case 1: + target_bytes = 2048; + break; + case 2: + target_bytes = 1024; + break; + case 3: + target_bytes = 4096; + break; + case 4: + printf("Enter custom amount (512-8192 bytes): "); + char custom_input[32]; + if (!fgets(custom_input, sizeof(custom_input), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + size_t custom_amount = (size_t)atoi(custom_input); + if (custom_amount < 512 || custom_amount > 8192) { + printf("Error: Invalid amount. Must be between 512 and 8192 bytes.\n"); + return 1; + } + target_bytes = custom_amount; + break; + default: + target_bytes = 2048; // Default to recommended + break; + } + + printf("\nCollecting %zu bytes of entropy...\n", target_bytes); + + // Allocate entropy buffer + unsigned char* entropy_buffer = malloc(MAX_ENTROPY_BUFFER); + if (!entropy_buffer) { + printf("Error: Cannot allocate entropy buffer\n"); + return 1; + } + + // Collect entropy with visual feedback + size_t collected_bytes = 0; + int result = collect_entropy_with_feedback(entropy_buffer, target_bytes, &collected_bytes, 1); + + if (result != 0) { + printf("Error: Entropy collection failed\n"); + free(entropy_buffer); + return 1; + } + + if (collected_bytes < 512) { + printf("Error: Insufficient entropy collected (%zu bytes)\n", collected_bytes); + free(entropy_buffer); + return 1; + } + + printf("\nProcessing entropy and modifying pad...\n"); + + // Add entropy to pad + result = add_entropy_to_pad(pad_chksum, entropy_buffer, collected_bytes, 1); + + // Clear entropy buffer for security + memset(entropy_buffer, 0, MAX_ENTROPY_BUFFER); + free(entropy_buffer); + + if (result != 0) { + printf("Error: Failed to add entropy to pad\n"); + return 1; + } + + printf("\n🎉 SUCCESS! Your pad now has enhanced randomness!\n"); + printf("Press Enter to continue..."); + getchar(); + + return 0; +} + void print_usage(const char* program_name) { printf("OTP Cipher - One Time Pad Implementation %s\n", get_version()); printf("%s\n", get_build_info()); diff --git a/otp.o b/otp.o new file mode 100644 index 0000000..fcc4e39 Binary files /dev/null and b/otp.o differ