Encryption

C中的一次性加密

  • December 7, 2021

我一直在搞亂密碼學(用於娛樂用途),並且我用 C 語言創建了自己的一次性加密腳本。現在,話雖如此,我坦率地承認我絕不是密碼學專家。我知道密碼學的第一條規則是不要自己動手。但是,我真的對我的加密腳本是否(理論上)安全感興趣。

首先,這是我試圖用我的加密方法實現的基本情況:

目標是實現一次性加密,其中(1)雜湊表和(2)加密密鑰都被使用。雜湊表(在這種情況下)是預先編碼的,其值為 01-98。

加密密鑰計算如下:

  1. 獲取隨機使用者輸入(至少與消息長度相同)
  2. 獲取validChars中char對應的索引$$ $$(下面的原始碼)
  3. 獲取 defaultHashTable 中與#2 的索引對應的數字

消息加密如下:

  1. 取消息並將其轉換為對應的 defaultHashTable 值
  2. 獲取之前生成的密鑰並將其添加到轉換後的消息中
  3. 現在它已加密(假設不再使用相同的密鑰)

例如:

  1. 留言:你好
  2. 轉換為對應的 defaultHashTable 值:hello -> 0805121215
  3. 獲取密鑰的隨機字元:abkdh
  4. 轉換為對應的 defaultHashTable 值:abkdh -> 0102110408
  5. 添加密鑰:0805121215 + 0102110408 = 0907231623
  6. 加密消息:0907231623

這是原始碼(注意:這是單獨的 C 標頭檔中的函式組合,所以我不發布 main () 函式):

// 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:
   if (strlen(response) < password_length){
       printf("\n[ ERROR ] : Random characters must be greater than or equal to %d.\n", password_length);
       return generateRandomKey(password_length, max_size, use_global);
   }

   // 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){
           loadIntoKeyForRandoKey(i, globalHashTable.hashtable[getCharIndex]);
       } else {
           loadIntoKeyForRandoKey(i, defaultHashTable.hashtable[getCharIndex]);
       }
   }

   // Write the random key to a file:
   createFileWithTwoDimensionalArray(globalKey.key, password_length, "key");
}

// (Helper Function) For loading into the EncryptedMessage struct:
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:
       int encryptedFrag = otpeAdd(convertedEncryptMsgCharsIntoInt, convertedKeyValueIntoInt);
       
       // Convert it into a string:
       char encryptedFragStr[3];
       sprintf(encryptedFragStr, "%d", encryptedFrag);
       
       loadIntoEncryptedMessage(i, encryptedFragStr, &encryptedMsg);
   }
   return encryptedMsg;
}

我的直接問題是:如果我使用的是預編碼的雜湊表(任何人都可以推斷),這是否會使加密不安全(即使對應於雜湊表值的密鑰通過使用者輸入是完全隨機的)?僅當我隨機化雜湊表編號(01-98)(並可能隨機化validChars)時才安全嗎

$$ $$)? 老實說,我對我的邏輯是否成立很感興趣,所以任何評論、建議或批評都將不勝感激。

三個問題立即浮出水面:-

  1. 消息完整性的老栗子。Pure one time pad 沒有任何身份驗證方式,即它們是可延展的。
  2. The key (for the message) will be maximum of [50,000]. 我特別談到了 Tweet 大小的消息是有原因的。OTP 最初是通過打字機創建的,並且非常成功。但是它們很小,而且會有很多打字員。沒有統計測試可以證明 160 個字元的隨機性(合理輸入)。但是有50,000。在鍵盤上隨機輸入的 50,000 個字元不太可能就是這樣。頻率分析和啟發式將嚴重降低 OTP 假定的基於資訊的安全性。使用者真的會輸入 50,000 個字母嗎?這種大小的密鑰需要硬體設備 (TRNG)。
  3. 收件人將如何解密郵件,除非

$$ pronoun $$是否具有用於加密它的相同密鑰?那麼它將如何到達那裡?

3½.雜湊表幾乎是不必要的。只需使用 ASCII 值,因為 OTP 中的安全性來自密鑰。在這裡閱讀一些 OTP 標記的問題是值得的。

這是很多程式碼,歸結為:

void otp(size_t len, uint8_t *key, uint8_t *message) {
   for (size_t i = 0; i < len; i++) {
       message[i] ^= key[i];
   }
}

其他一切(除了確保len(key) == len(message)不僅是不必要的,而且積極減損它是一次性墊的實際實現。即使在這裡說“歸結為”也可能具有誤導性:這不僅僅是一個簡化版本;除了從真正隨機的源中獲取適當長度的密鑰是完全完整的。省略的部分可能只是

char *key = malloc(len);

assert(key != NULL);
assert(read(fd, key, len) == len);

我沒有看過你的“雜湊表”做了什麼,因為它所做的任何事情都是完全不必要的,極有可能是導致災難性安全損失的錯誤來源,並且幾乎可以肯定違反了構成一次性墊的基本定義.

一次性鍵盤必須具有真正隨機的密鑰。在鍵盤上鍵入的字元並不是真正隨機的。的輸出/dev/random並不是真正隨機的。獲得真正的隨機位很難,但確實存在可以為您收集它們的外部設備(假設您信任該設備)。這些密鑰必須與您的消息長度相同(或者更長,我想)。

最後,雖然它不是絕對必要的,但加密通常最好在位和字節上執行,而不是“字元”的一些概念。嘗試做後者是在為導致災難性故障的嚴重錯誤做好準備。

引用自:https://crypto.stackexchange.com/questions/91968