C中的一次性加密
我一直在搞亂密碼學(用於娛樂用途),並且我用 C 語言創建了自己的一次性加密腳本。現在,話雖如此,我坦率地承認我絕不是密碼學專家。我知道密碼學的第一條規則是不要自己動手。但是,我真的對我的加密腳本是否(理論上)安全感興趣。
首先,這是我試圖用我的加密方法實現的基本情況:
目標是實現一次性加密,其中(1)雜湊表和(2)加密密鑰都被使用。雜湊表(在這種情況下)是預先編碼的,其值為 01-98。
加密密鑰計算如下:
- 獲取隨機使用者輸入(至少與消息長度相同)
- 獲取validChars中char對應的索引$$ $$(下面的原始碼)
- 獲取 defaultHashTable 中與#2 的索引對應的數字
消息加密如下:
- 取消息並將其轉換為對應的 defaultHashTable 值
- 獲取之前生成的密鑰並將其添加到轉換後的消息中
- 現在它已加密(假設不再使用相同的密鑰)
例如:
- 留言:你好
- 轉換為對應的 defaultHashTable 值:hello -> 0805121215
- 獲取密鑰的隨機字元:abkdh
- 轉換為對應的 defaultHashTable 值:abkdh -> 0102110408
- 添加密鑰:0805121215 + 0102110408 = 0907231623
- 加密消息: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)時才安全嗎
$$ $$)? 老實說,我對我的邏輯是否成立很感興趣,所以任何評論、建議或批評都將不勝感激。
三個問題立即浮出水面:-
- 消息完整性的老栗子。Pure one time pad 沒有任何身份驗證方式,即它們是可延展的。
The key (for the message) will be maximum of [50,000]
. 我特別談到了 Tweet 大小的消息是有原因的。OTP 最初是通過打字機創建的,並且非常成功。但是它們很小,而且會有很多打字員。沒有統計測試可以證明 160 個字元的隨機性(合理輸入)。但是有50,000。在鍵盤上隨機輸入的 50,000 個字元不太可能就是這樣。頻率分析和啟發式將嚴重降低 OTP 假定的基於資訊的安全性。使用者真的會輸入 50,000 個字母嗎?這種大小的密鑰需要硬體設備 (TRNG)。- 收件人將如何解密郵件,除非
$$ 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
並不是真正隨機的。獲得真正的隨機位很難,但確實存在可以為您收集它們的外部設備(假設您信任該設備)。這些密鑰必須與您的消息長度相同(或者更長,我想)。最後,雖然它不是絕對必要的,但加密通常最好在位和字節上執行,而不是“字元”的一些概念。嘗試做後者是在為導致災難性故障的嚴重錯誤做好準備。