Điểm:0

Mã hóa one-time-pad trong C

lá cờ vn

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:

  1. 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)
  2. Nhận chỉ mục tương ứng cho char trong hợp lệChars[] (mã nguồn bên dưới)
  3. 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:

  1. Nhận tin nhắn và chuyển đổi nó thành các giá trị defaultHashTable tương ứng
  2. Lấy khóa đã tạo trước đó và thêm nó vào tin nhắn đã chuyển đổi
  3. 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ụ:

  1. Tin nhắn: xin chào
  2. Chuyển đổi sang giá trị defaultHashTable tương ứng: hello -> 0805121215
  3. Nhận ký tự ngẫu nhiên cho khóa: abkdh
  4. Chuyển đổi sang giá trị defaultHashTable tương ứng: abkdh -> 0102110408
  5. Thêm chốt: 0805121215 + 0102110408 = 0907231623
  6. 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.

Paul Uszak avatar
lá cờ cn
Xin chào. Lựa chọn tuyệt vời để sử dụng OTP. Nhưng, _"(1) 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)"_ . Thế nào? Nếu tôi mã hóa một tin nhắn có kích thước Tweet, đầu vào ngẫu nhiên đến từ đâu? Hãy nhớ rằng OTP **phải** được tạo ra một cách vật lý thông qua phần cứng cơ học hoặc sinh học, **không** phần mềm.
lá cờ vn
@PaulUszak Xin chào! Tôi hiện đang nhận đầu vào ngẫu nhiên của người dùng bằng cách yêu cầu người dùng tự động nhập các ký tự ngẫu nhiên có độ dài bằng hoặc lớn hơn thông báo. Điều đó có an toàn không?
lá cờ zw
Không thể phủ nhận rằng con người rất tệ trong việc tạo đầu vào ngẫu nhiên. OTP *yêu cầu, theo định nghĩa* đầu vào thực sự ngẫu nhiên làm khóa của nó.
Điểm:1
lá cờ cn

Ba vấn đề ngay lập tức nổi lên: -

  1. Hạt dẻ cũ của thông điệp toàn vẹn. Các miếng đệm một lần thuần túy không có bất kỳ phương tiện xác thực nào, tức là chúng dễ uốn nắn.

  2. Khóa (cho tin nhắn) sẽ có giá trị tối đa là [50.000]. Tôi đặc biệt nói về các tin nhắn có kích thước Tweet là có lý do.OTP ban đầu được tạo thông qua máy đánh chữ và rất thành công. Nhưng chúng nhỏ và sẽ có nhiều người đánh máy. Không có kiểm tra thống kê nào có thể bác bỏ tính ngẫu nhiên của 160 ký tự (được nhập hợp lý). Nhưng có cho 50.000. Rất khó có khả năng 50.000 ký tự được gõ ngẫu nhiên trên bàn phím sẽ chỉ có vậy. Phân tích tần suất và chẩn đoán sẽ hạ cấp nghiêm trọng khả năng bảo mật dựa trên thông tin giả định của OTP. Và người dùng có thực sự gõ 50.000 chữ cái không? Một thiết bị phần cứng (TRNG) là cần thiết cho các khóa có kích thước đó.

  3. Người nhận sẽ giải mã tin nhắn như thế nào, trừ khi [đại từ] có cùng một khóa đã được sử dụng để mã hóa nó? Vì vậy, làm thế nào nó sẽ đạt được điều đó?

3½. Bảng băm gần như không cần thiết. Chỉ cần sử dụng các giá trị ASCII vì bảo mật trong OTP đến từ khóa. Thật đáng giá khi đọc một số câu hỏi được gắn thẻ OTP đây.

Điểm:0
lá cờ zw

Đây là một nhiều mã cho một cái gì đó tóm tắt thành:

void otp(size_t len, uint8_t *key, uint8_t *message) {
    for (size_t i = 0; i < len; i++) {
        tin nhắn [i] ^= khóa [i];
    }
}

Mọi thứ khác (ngoài việc đảm bảo len(key) == len(tin nhắn) không chỉ là không cần thiết, mà còn chủ động làm mất đi khả năng triển khai thực tế của bộ đệm một lần. Ngay cả khi nói "rút gọn thành" ở đây cũng có thể gây hiểu nhầm: đây không chỉ là một phiên bản đơn giản hóa; ngoài việc lấy một khóa có độ dài phù hợp từ một nguồn thực sự ngẫu nhiên, đó là hoàn toàn hoàn chỉnh. Phần bị bỏ qua đó có thể nhiều hơn một chút

char *key = malloc(len);

khẳng định (khóa != NULL);
khẳng định (đọc (fd, khóa, len) == len);

Tôi chưa xem "bảng băm" của bạn làm gì bởi vì bất cứ điều gì nó đang làm là hoàn toàn không cần thiết, cực kỳ có khả năng là nguồn gây ra lỗi gây mất bảo mật nghiêm trọng và gần như chắc chắn vi phạm định nghĩa cơ bản về những gì cấu thành một lần đệm .

Một miếng đệm một lần phải có các khóa thực sự ngẫu nhiên. Các ký tự gõ trên bàn phím không thực sự ngẫu nhiên. đầu ra của /dev/ngẫu nhiên không thực sự là ngẫu nhiên. Việc thu thập các bit thực sự ngẫu nhiên rất khó, nhưng vẫn tồn tại các thiết bị bên ngoài sẽ thu thập chúng cho bạn (giả sử bạn tin tưởng thiết bị đó). những phím đó phải có cùng độ dài với tin nhắn của bạn (hoặc dài hơn, tôi cho là vậy).

Và cuối cùng, trong khi nó không phải là một điều cần thiết nghiêm ngặt, mật mã thường được thực hiện tốt nhất trên bit và byte và không phải là một số khái niệm về "nhân vật". Cố gắng làm điều sau là tự đặt mình vào những lỗi nghiêm trọng dẫn đến thất bại thảm hại.

Mark avatar
lá cờ ng
Điều đáng nói là "bảng một lần phải có các khóa thực sự ngẫu nhiên" không hoàn toàn đúng --- bạn có thể thay thế nguồn ngẫu nhiên bằng PRG (ở chế độ bộ đếm) để nhận $\mathsf{CTR}\$$ mã hóa. Tất nhiên, điều này không hoàn toàn an toàn, nhưng có lẽ đáng được nhắc đến đối với người mới sử dụng để triển khai mã hóa đặc biệt đơn giản.
lá cờ zw
Tại thời điểm đó, đó là mật mã luồng chứ không phải mật mã dùng một lần. Đây không chỉ là một sự khác biệt về mặt học thuật, nó có nghĩa là mấu chốt của toàn bộ nhiệm vụ giờ đây đã khác về cơ bản. Nó không chỉ là XORing bit, phần khó là thiết kế và viết CSPRNG.
Mark avatar
lá cờ ng
Có, nhưng nó vẫn hữu ích về mặt khái niệm để hiểu mật mã dòng. OTP về mặt khái niệm thì đơn giản, nhưng quản lý khóa thì khó. CTR$ chỉ đơn giản là thay thế việc quản lý khóa (cứng) bằng vấn đề (khó) là thiết kế PRG. May mắn thay, trong khi cả hai đều khó khăn, chúng tôi dường như thực sự có thể thiết kế PRG, trong khi thực tế việc quản lý khóa OTP trên quy mô lớn dường như (hầu hết) là vô vọng.
Paul Uszak avatar
lá cờ cn
Hấp dẫn. Tại sao bạn tin rằng `/dev/random` không thực sự ngẫu nhiên? Tôi đang đề cập đến một khối (ed) (s). Bạn đang nói về cái mới? Ngoài ra, điều đó không khó vì bạn có thể làm điều đó mà không cần bất kỳ bộ công cụ bên ngoài nào - cpu jitter - `System.nanoTime()` hoặc `haveged`, hoặc thư viện entropy của Arduino.
lá cờ zw
`/dev/random` và `/dev/urandom` [đều là đầu ra từ cùng một CSPRNG](https://www.2uo.de/myths-about-urandom/). Ngay cả khi bạn muốn tranh luận rằng hạt nhân ước tính tỷ lệ 1:1 giữa entropy đầu vào và entropy đầu ra so với hạt nhân trước đó, thì sự đảm bảo đó sẽ biến mất sau khi bạn gửi nó qua CSPRNG, thứ chỉ đảm bảo an toàn tính toán và không có bằng chứng bảo mật hoàn hảo. `haveged` thêm entropy vào công cụ ước tính entropy hạt nhân, nhưng điều này vẫn dẫn đến cùng một vấn đề cơ bản.
lá cờ zw
Dù bằng cách nào, vấn đề không phải là không thể có được những con số thực sự ngẫu nhiên. Vấn đề là bản thân OTP về cơ bản là tầm thường, phần "khó" là lấy các số thực sự ngẫu nhiên và không ngẫu nhiên "có vẻ tốt với tôi". Và sau đó, tất nhiên, nếu bạn có một kênh mà bạn có thể chia sẻ chúng một cách an toàn với bên thứ ba, thì bạn cũng có thể chỉ trao đổi tin nhắn qua kênh đó.

Đăng câu trả lời

Hầu hết mọi người không hiểu rằng việc đặt nhiều câu hỏi sẽ mở ra cơ hội học hỏi và cải thiện mối quan hệ giữa các cá nhân. Ví dụ, trong các nghiên cứu của Alison, mặc dù mọi người có thể nhớ chính xác có bao nhiêu câu hỏi đã được đặt ra trong các cuộc trò chuyện của họ, nhưng họ không trực giác nhận ra mối liên hệ giữa câu hỏi và sự yêu thích. Qua bốn nghiên cứu, trong đó những người tham gia tự tham gia vào các cuộc trò chuyện hoặc đọc bản ghi lại các cuộc trò chuyện của người khác, mọi người có xu hướng không nhận ra rằng việc đặt câu hỏi sẽ ảnh hưởng—hoặc đã ảnh hưởng—mức độ thân thiện giữa những người đối thoại.