Tôi đã loay hoay với mật mã (để sử dụng cho mục đích giải trí) và tôi đã tạo tập lệnh mã hóa one-time-pad của riêng mình bằng C. Bây giờ, như đã nói, tôi thoải mái thừa nhận rằng tôi hoàn toàn không phải là một chuyên gia về mật mã. Tôi biết rằng Quy tắc số 1 của mật mã là không được tự mình làm điều đó. Tuy nhiên, tôi thực sự quan tâm đến việc liệu tập lệnh mã hóa của tôi có an toàn (về mặt lý thuyết) hay không.
Đầu tiên, đây là một bản tóm tắt cơ bản về những gì tôi đang cố gắng đạt được bằng phương pháp mã hóa của mình:
Mục tiêu là đạt được Mã hóa One Time Pad, trong đó cả (1) bảng băm và (2) khóa mã hóa đều được sử dụng. Bảng băm (trong trường hợp này) được mã hóa trước với các giá trị là 01-98.
Khóa mã hóa được tính như sau:
- Nhận đầu vào ngẫu nhiên của người dùng (ít nhất có cùng độ dài với tin nhắn)
- Nhận chỉ mục tương ứng cho char trong hợp lệChars[] (mã nguồn bên dưới)
- Lấy số trong defaultHashTable tương ứng với chỉ mục của #2
Tin nhắn được mã hóa như sau:
- Nhận tin nhắn và chuyển đổi nó thành các giá trị defaultHashTable tương ứng
- Lấy khóa đã tạo trước đó và thêm nó vào tin nhắn đã chuyển đổi
- Bây giờ nó đã được mã hóa (giả sử cùng một khóa không bao giờ được sử dụng lại)
Ví dụ:
- Tin nhắn: xin chào
- Chuyển đổi sang giá trị defaultHashTable tương ứng: hello -> 0805121215
- Nhận ký tự ngẫu nhiên cho khóa: abkdh
- Chuyển đổi sang giá trị defaultHashTable tương ứng: abkdh -> 0102110408
- Thêm chốt: 0805121215 + 0102110408 = 0907231623
- Tin nhắn được mã hóa: 0907231623
Đây là mã nguồn (LƯU Ý: Đây là sự kết hợp của các hàm nằm trong các tệp tiêu đề C riêng biệt, vì vậy đó là lý do tại sao tôi không đăng hàm main()):
// Khóa (cho tin nhắn) sẽ có giá trị tối đa là [50,000][3] (do đó, một mảng có mỗi ký tự là 2 ký tự):
cấu trúc typedef {
khóa char[MAX_SIZE][3];
} Chìa khóa;
Khóa toàn cầu;
// Tin nhắn được mã hóa sẽ được trả về trong cấu trúc này (vì đây chỉ là một cách dễ dàng để trả về một mảng hai chiều từ một hàm trong C):
cấu trúc typedef {
char mã hóaMessage[MAX_SIZE][3];
} Tin nhắn được mã hóa;
// Bảng băm là một mảng hai chiều được mã hóa trước (được tải trong initDefaultHashTable()), sẽ được sử dụng cùng với khóa để mã hóa tin nhắn:
// LƯU Ý: Ngoài ra còn có một chế độ mã hóa khác mà tôi không đưa vào (trong nỗ lực làm cho điều này ngắn gọn), trong đó bạn phải nhập thủ công các số có hai chữ số ngẫu nhiên (97 trong số đó) cho bảng băm
cấu trúc typedef {
char hashtable[HASHTABLE_CAPACITY][3];
} DefaultHashTable;
DefaultHashTable defaultHashTable; // Khai báo một defaultHashTable toàn cầu sẽ lưu trữ hashtable này
// Tải defaultHashTable tương ứng với 1-98:
void initDefaultHashTable(){
for (int i = 0; i < HASHTABLE_CAPACITY; i++){
ký tự s[3];
sprintf(s, "%d", (i+1));
nếu (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];
}
}
}
// Các ký tự hợp lệ mà tin nhắn có thể chứa (97 trong số chúng):
char hợp lệChars[] = {'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'};
// Đối với trả về char không thành công (tôi cảm thấy có một cách tốt hơn để làm phần này):
char KHÔNG THÀNH CÔNG = (char)255;
// Tìm chỉ mục của một char hợp lệ (từ validChars) hoặc FALSE nếu nó không tồn tại:
int findChar(char c){
for (int i = 0; i < strlen(validChars); i++){
nếu (validChars[i] == c){
trả lại tôi;
}
}
trả về SAI;
}
// Trả về char từ chỉ số validChars đã cho:
char returnChar(int index){
trả về hợp lệChars [chỉ mục];
}
// Lấy chỉ mục của một giá trị bảng băm nhất định (từ defaultHashTable) và sau đó, nếu có, char tương ứng trong 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])
trả lại returnChar(i);
}
trả lại KHÔNG ĐẠT;
}
// Phần CHÍNH của mã (hàm c chính sẽ gọi phần này): Mã hóa bằng mã hóa một lần, nhưng sử dụng bảng băm mặc định để tiết kiệm thời gian:
void goThroughLightEnryptionProcess(char * str, char * write_to_file){
// Nạp defaultHashTable:
initDefaultHashTable();
// Sử dụng hàm để tạo khóa ngẫu nhiên (dựa trên đầu vào ngẫu nhiên của người dùng):
generateRandomKey(strlen(str), MAX_SIZE, FALSE);
// Nhập tin nhắn:
EncryptedMessage mã hóaMsg = otpEncrypt(defaultHashTable.hashtable, str, globalKey.key);
// Lặp lại và in nội dung (nếu không ghi vào tệp):
nếu (write_to_file == NULL){
for (int i = 0; i < strlen(str); i++){
for (int j = 0; j < 2; j++){
printf("%c",cryptMsg.encryptedMessage[i][j]);
}
}
printf("\n");
} khác {
// Viết tin nhắn được mã hóa vào tệp:
// LƯU Ý: đây là một thông số khác mà bạn có thể chuyển vào chương trình thực tế (lớn hơn nhiều so với thông số này), nơi bạn có thể ghi nó vào một tệp thay vì hiển thị nó trong thiết bị đầu cuối:
// LƯU Ý: Tôi không bao gồm mã mảng writeFileWithTwoDimensionalArray() này ở đây vì nó không liên quan, vì nó chỉ ghi vào một tệp.
writeFileWithTwoDimensionalArray(encryptMsg.encryptedMessage, HASHTABLE_CAPACITY, write_to_file);
}
}
// (Hàm trợ giúp) Nạp hai mảng char vào key:
void loadIntoKeyForRandoKey(int at, char n[3]){
for (int i = 0; i < 2; i++){
globalKey.key[at][i] = n[i];
}
}
// Tạo khóa dựa trên đầu vào ngẫu nhiên của người dùng:
void generateRandomKey(int password_length, int max_size, bool use_global){
// @Sử dụng globalHashTable | mặc địnhHashTable
// @Sử dụng khóa toàn cầu
phản hồi char [max_size];
printf("Nhập các ký tự ngẫu nhiên cho khóa (a-z,A-Z,!@#$%%^&*()-+=, độ dài tối thiểu của %d): ", password_length);
fgets(phản hồi, max_size, stdin);
// Xóa ký tự '\n' ở cuối:
ký tự *p;
if ((p = strchr(response, '\n')) != NULL){
*p = '\0';
} khác {
scanf("%*[^\n]");
scanf("%*c");
}
// Đảm bảo đầu vào của người dùng là >= password_length:
if (strlen(response) < password_length){
printf("\n[ LỖI ] : Các ký tự ngẫu nhiên phải lớn hơn hoặc bằng %d.\n", password_length);
trả về generateRandomKey(password_length, max_size, use_global);
}
// Chuyển đổi các ký tự ngẫu nhiên thành giá trị tương đương của chúng trong hashtable tương ứng:
for (int i = 0; i < password_length; i++){
int getCharIndex = findChar(phản hồi[i]);
// Hãy chắc chắn rằng nó đã được tìm thấy thành công:
nếu (getChar Index == FALSE){
printf("\n[ LỖI ] Ký tự '%c' không hợp lệ. Hãy thử lại.\n", phản hồi[i]);
trả về generateRandomKey(password_length, max_size, use_global); // Làm lại lần nữa
}
// Nạp giá trị hashtable tương ứng vào key:
nếu (use_global == TRUE){
loadIntoKeyForRandoKey(i, globalHashTable.hashtable[getCharIndex]);
} khác {
loadIntoKeyForRandoKey(i, defaultHashTable.hashtable[getCharIndex]);
}
}
// Viết khóa ngẫu nhiên vào một tệp:
createFileWithTwoDimensionalArray(globalKey.key, password_length, "key");
}
// (Chức năng trợ giúp) Để tải vào cấu trúc EncryptedMessage:
void loadIntoEncryptedMessage(int at, char n[3], EncryptedMessage *encryptedMsg){
nếu (strlen(n) == 1){
// Nối thêm '0':
char tmp = n[0];
n[0] = '0';
n[1] = tmp;
}
for (int i = 0; i < 2; i++){
mã hóaMsg->encryptedMessage[at][i] = n[i];
}
}
/*
Mã hóa một tin nhắn được cung cấp một mã băm và khóa hợp lệ
*/
EncryptedMessage otpEncrypt(char hashtable[][3], char * msg, char key[MAX_SIZE][3]){
EncryptedMessage được mã hóaMsg;
for (int i = 0; i < strlen(msg); i++){
// Chuyển đổi giá trị khóa thành số nguyên:
int convertKeyValueIntoInt = safeConvertToInt(key[i]);
// Hãy chắc chắn rằng nó đã chuyển đổi chính xác:
nếu (convertedKeyValueIntoInt == FALSE){
printf("[ LỖI ] : Khóa bị hỏng tại %d (giá trị = %s).\n", i, key[i]);
thoát(1);
}
// Chuyển đổi tin nhắn của người dùng thành giá trị tương đương trong hashtable:
int indexOfMsgChar = findChar(msg[i]);
// Đảm bảo rằng findChar() đã tìm đúng giá trị:
nếu (indexOfMsgChar == FALSE){
printf("[ LỖI ] : Mật khẩu (tin nhắn) bị hỏng tại %d (giá trị = %s). Điều này có thể xảy ra do ký tự '%c' không được phép.\n", i, msg, msg[i ]);
thoát(1);
}
char * tương ứngEncryptMsgChars = hashtable[indexOfMsgChar];
// Chuyển đổi ký tự mã hóa tương ứng thành int:
int convertEncryptMsgCharsIntoInt = safeConvertToInt(tương ứngEncryptMsgChars);
// Hãy chắc chắn rằng nó đã chuyển đổi chính xác:
nếu (convertedEncryptMsgCharsIntoInt == FALSE){
printf("[ LỖI ] : Bảng băm bị hỏng tại %d (giá trị = %s).\n", indexOfMsgChar, tương ứngEncryptMsgChars);
thoát(1);
}
// Thực hiện phép tính:
int mã hóaFrag = otpeAdd(convertedEncryptMsgCharsIntoInt, convertKeyValueIntoInt);
// Chuyển nó thành chuỗi:
char mã hóaFragStr[3];
sprintf(mã hóaFragStr, "%d", mã hóaFrag);
loadIntoEncryptedMessage(i, mã hóaFragStr, &encryptedMsg);
}
trả lạiMsg được mã hóa;
}
Câu hỏi ngay lập tức của tôi sẽ là: nếu tôi đang sử dụng bảng băm được mã hóa trước (mà bất kỳ ai cũng có thể suy luận), điều đó có làm cho mã hóa không an toàn không (mặc dù khóa tương ứng với các giá trị của bảng băm là hoàn toàn ngẫu nhiên thông qua đầu vào của người dùng)? Có phải nó chỉ an toàn nếu tôi chọn ngẫu nhiên các số của bảng băm (01-98) (và có khả năng chọn ngẫu nhiên hợp lệChars[])?
Tôi thực sự quan tâm liệu logic của tôi có đúng hay không, vì vậy mọi nhận xét, đề xuất hoặc phê bình sẽ được đánh giá cao.