I have been messing around with cryptography (for recreational use), and I created my own one-time-pad encryption script in C. Now, with that being said, I freely admit that I am by no means a cryptography expert. I know that Rule No. 1 of cryptography is not to do it yourself. However, I am genuinely interested in whether my encryption script is (theoretically) secure.

First, here is a basic debrief of what I am attempting to achieve with my encryption method:

The goal is to achieve One Time Pad Encryption, where both (1) a hash table and (2) an encryption key are used. The hash table (in this case) is pre-coded where the values are 01-98.

The encryption key is calculated as follows:

1. Get random user input (at least the same length as the message)
2. Get corresponding index for char in validChars[] (source code below)
3. Get the number in defaultHashTable that corresponds to #2's index

The message is encrypted as follows:

1. Take the message and convert it into the corresponding defaultHashTable values
2. Take the previously generated key and add it to the converted message
3. Now it is encrypted (assuming the same key is never used again)

For example:

1. Message: hello
2. Convert to corresponding defaultHashTable values: hello -> 0805121215
3. Get random chars for key: abkdh
4. Convert to corresponding defaultHashTable values: abkdh -> 0102110408
5. Add the key: 0805121215 + 0102110408 = 0907231623
6. Encrypted message: 0907231623

Here is the source code (NOTE: This is a combination of functions that were in separate C header files, so that is why I am not posting a main () function):

// The key (for the message) will be maximum of [50,000][3] (so an array where each is 2 characters):
typedef struct {
char key[MAX_SIZE][3];
} Key;
Key globalKey;

// The encrypted message will be returned in this struct (because it is just an easy way to return a two dimensional array from a function in C):
typedef struct {
char encryptedMessage[MAX_SIZE][3];
} EncryptedMessage;

// The hash table is a pre-coded two dimensional array (which is loaded in initDefaultHashTable()), which will be used in conjunction with the key to encrypt messages:
// NOTE: There is also another encryption mode that I am not including (in an attempt to make this concise) where you have to manually type in random two digit numbers (97 of them) for the hash table
typedef struct {
char hashtable[HASHTABLE_CAPACITY][3];
} DefaultHashTable;
DefaultHashTable defaultHashTable; // Declare a global defaultHashTable that will store this hashtable

// Load the defaultHashTable with 1-98 respectively:
void initDefaultHashTable(){
for (int i = 0; i < HASHTABLE_CAPACITY; i++){
char s[3];
sprintf(s, "%d", (i+1));

if (i < 10){
char tmp = s[0];
s[0] = '0';
s[1] = tmp;
}

for (int j = 0; j < 2; j++){
defaultHashTable.hashtable[i][j] = s[j];
}
}
}

// Valid chars that the message can contain (97 of them):
char validChars[] = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','!','@','#','$','%','^','&','*','(',')','-','+',' ',',','.',':',';','\'','\"','[',']','{','}','_','=','|','\\','/','<','>','?','','~','\n','\t','0','1','2','3','4','5','6','7','8','9'}; // For char return fails (I feel like there is a better way to do this part): char FAILED = (char)255; // Find the index of a valid char (from validChars) or FALSE if it doesn't exist: int findChar(char c){ for (int i = 0; i < strlen(validChars); i++){ if (validChars[i] == c){ return i; } } return FALSE; } // Return char from validChars given index: char returnChar(int index){ return validChars[index]; } // Get the index of a given hash table value (from defaultHashTable) and then, if applicable, the corresponding char in validChars: char findHashTableValue(char n[3], char hashtable[][3]){ for (int i = 0; i < HASHTABLE_CAPACITY; i++){ if (hashtable[i][0] == n[0] && hashtable[i][1] == n[1]) return returnChar(i); } return FAILED; } // MAIN part of the code (the main c function would call this): Encrypts using one time pad encryption, but using a default hash table to save time: void goThroughLightEnryptionProcess(char * str, char * write_to_file){ // Load the defaultHashTable: initDefaultHashTable(); // Uses function to create random key (based off of random user input): generateRandomKey(strlen(str), MAX_SIZE, FALSE); // Enrypt the message: EncryptedMessage encryptMsg = otpEncrypt(defaultHashTable.hashtable, str, globalKey.key); // Loop through and print the contents (if not write to file): if (write_to_file == NULL){ for (int i = 0; i < strlen(str); i++){ for (int j = 0; j < 2; j++){ printf("%c", encryptMsg.encryptedMessage[i][j]); } } printf("\n"); } else { // Write encrypted message to file: // NOTE: this is another param you can pass in the actual program (which is much larger than this) where you can write it to a file instead of the displaying it in the terminal: // NOTE: I did not include this writeFileWithTwoDimensionalArray() array code in here because it is irrelevant, as it simply writes to a file. writeFileWithTwoDimensionalArray(encryptMsg.encryptedMessage, HASHTABLE_CAPACITY, write_to_file); } } // (Helper Function) Load the two char array into the key: void loadIntoKeyForRandoKey(int at, char n[3]){ for (int i = 0; i < 2; i++){ globalKey.key[at][i] = n[i]; } } // Generate a key based off of random user input: void generateRandomKey(int password_length, int max_size, bool use_global){ // @Uses globalHashTable | defaultHashTable // @Uses globalKey char response[max_size]; printf("Enter random characters for the key (a-z,A-Z,!@#$%%^&*()-+=, min length of %d): ", password_length);
fgets(response, max_size, stdin);

// Remove '\n' character at the end:
char * p;
if ((p = strchr(response, '\n')) != NULL){
*p = '\0';
} else {
scanf("%*[^\n]");
scanf("%*c");
}

// Make sure user input is >= password_length:
printf("\n[ ERROR ] : Random characters must be greater than or equal to %d.\n", password_length);
}

// Convert the random chars into their equivalence in the hashtable respectively:
for (int i = 0; i < password_length; i++){
int getCharIndex = findChar(response[i]);

// Make sure it was successfully found:
if (getCharIndex == FALSE){
printf("\n[ ERROR ] Character '%c' is invalid. Try again.\n", response[i]);
return generateRandomKey(password_length, max_size, use_global); // Do it again
}

// Load corresponding hashtable value into key:
if (use_global == TRUE){
} else {
}
}

// Write the random key to a file:
}

void loadIntoEncryptedMessage(int at, char n[3], EncryptedMessage *encryptedMsg){
if (strlen(n) == 1){
// Append a '0':
char tmp = n[0];
n[0] = '0';
n[1] = tmp;
}

for (int i = 0; i < 2; i++){
encryptedMsg->encryptedMessage[at][i] = n[i];
}
}

/*
Encrypts a message given a valid hashtable and key
*/
EncryptedMessage otpEncrypt(char hashtable[][3], char * msg, char key[MAX_SIZE][3]){
EncryptedMessage encryptedMsg;

for (int i = 0; i < strlen(msg); i++){
// Convert the key value into integer:
int convertedKeyValueIntoInt = safelyConvertToInt(key[i]);

// Make sure it converted correctly:
if (convertedKeyValueIntoInt == FALSE){
printf("[ ERROR ] : The key is corrupted at %d (value = %s).\n", i, key[i]);
exit(1);
}

// Convert the user's message into its equivalence in the hashtable:
int indexOfMsgChar = findChar(msg[i]);

// Make sure that findChar() found the value correctly:
if (indexOfMsgChar == FALSE){
printf("[ ERROR ] : The password (msg) is corrupted at %d (value = %s). This may have occurred because a char '%c' is not allowed.\n", i, msg, msg[i]);
exit(1);
}

char * correspondingEncryptMsgChars = hashtable[indexOfMsgChar];

// Convert corresponding encryptMsg chars into int:
int convertedEncryptMsgCharsIntoInt = safelyConvertToInt(correspondingEncryptMsgChars);

// Make sure it converted correctly:
if (convertedEncryptMsgCharsIntoInt == FALSE){
printf("[ ERROR ] : Hash table is corrupted at %d (value = %s).\n", indexOfMsgChar, correspondingEncryptMsgChars);
exit(1);
}

// Make the calculation:

// Convert it into a string:
char encryptedFragStr[3];
sprintf(encryptedFragStr, "%d", encryptedFrag);

}
return encryptedMsg;
}


My immediate question would be: if I am using a pre-coded hash table (that anyone could infer), does that make the encryption insecure (even though the key that corresponds to the hash table values are completely random via user input)? Is it only secure if I randomize the hash table numbers (01-98) (and potentially randomize the validChars[])?

I am honestly interested whether my logic holds, so any comments, suggestions, or criticisms would be much appreciated.

• Hiya. Excellent choice to use OTPs. But, "(1) Get random user input (at least the same length as the message)" . How? If I encode a Tweet sized message, where does the random input come from? Remember that a OTP has to be generated physically via mechanical or biological hardware, not software. Jul 9 at 16:19
• @PaulUszak Hi! I am getting random user input currently by having the user spontaneously type out random characters that are of or greater length than the message. Is that secure? Jul 10 at 4:16

2. The key (for the message) will be maximum of [50,000]`. I specifically talked about Tweet sized messages for a reason. OTPs were originally created via typewriters and were very successfully. But they were small and there would have been many typists. There is no statistical test than can disproved the randomness of 160 characters (sensibly typed). But there are for 50,000. It is highly unlikely that 50,000 characters typed at random on a keyboard will be just that. Frequency analysis and heuristics will severely downgrade the presumed information based security of the OTP. And are users really going to type 50,000 letters? A hardware device (TRNG) is necessary for keys of that size.