C语言实现Vigenere密码加密结果不符合预期(CS50习题)
CS50维吉尼亚(Vigenere)密码实现问题排查
加密规则说明
- 偏移映射规则:密钥为纯字母组成,
A/a对应偏移值0,B/b对应偏移值1,以此类推Z/z对应偏移值25;密文字符计算公式为ci = (pi + kj) % 26,其中pi为明文字母对应0-25的序号,kj为当前密钥字符对应偏移值 - 密钥复用规则:密钥逐字符循环取用,若密钥长度小于明文长度,循环重复密钥字符直至所有明文加密完成;非字母类明文字符直接输出,不消耗密钥字符
规则示例:使用密钥
ABC加密明文HELLO时,H取密钥A(0)偏移、E取B(1)偏移、第一个L取C(2)偏移,密钥用尽后循环回到A偏移第二个L、B偏移O,最终得到密文HFNLP,计算过程如下:
内容类型 字符序列 明文 H E L L O 密钥 A B C A B 偏移值 0 1 2 0 1 密文 H F N L P
- 官方运行参考:
$ ./vigenere bacon plaintext: Meet me at the park at eleven am ciphertext: Negh zf av huf pcfx bt gzrwep oz
异常复现
- 测试入参:密钥
baz,明文barfoo - 预期输出密文:
caqgon - 程序实际输出:
caqfgv,加密结果不符合规则
问题原代码
#include <cs50.h> #include <stdio.h> #include <stdlib.h> #include <ctype.h> #include <string.h> // Functions: bool check_arguments(int argc); bool is_key_alpha(string key); int shift(char c); int main(int argc, string argv[]) { if (check_arguments(argc) == false) { return 1; } // Declaring key string variable: string key = argv[1]; // Check containing any character that is not an alphabetic character if (is_key_alpha(key) == false) { return 1; } // Prompting user for plaintext: string plaintext = get_string("plaintext: "); // Ecipher: printf("ciphertext: "); for (int i = 0; i < strlen(plaintext); i++) { if (islower(plaintext[i])) { printf("%c", ((plaintext[i]) - 97 + shift(key[i])) % 26 + 97); } else if (isupper(plaintext[i])) { printf("%c", ((plaintext[i]) - 65 + shift(key[i])) % 26 + 65); } else { printf("%c", plaintext[i]); } } printf("\n"); return 0; } // FUNCTIONS : // Checking if there's more than one command-line argument // Checking if the command-line argument exists: bool check_arguments(int argc) { // Checking if there's more than one command-line argument if (argc > 2) { printf("Usage: ./vigenere keyword\n\n"); return false; } // Checking if the command-line argument exists: else if (argc < 2) { printf("Usage: ./vigenere keyword\n"); return false; } // If okey: else { return true; } } // containing any character that is not an alphabetic character bool is_key_alpha(string key) { for (int i = 0; i < strlen(key); i++) { if (isalpha(key[i]) == false) { printf("Key contains non-alphabetical chars"); return false; } } return true; } // convert character into the correct shift value int shift(char c) { // for ex. char = a == 97 ascii if (isalpha(c)) { if (isupper(c)) // The ASCII value of A is 65 { c = c - 65; } else if (islower(c)) // The ASCII value of a is 97 { c = c - 97; } } else { return c; } return c; }
错误原因
加密循环中直接用明文遍历索引i访问密钥字符,存在两个问题:
- 没有单独维护密钥的遍历索引,也没有对密钥长度取模实现循环复用。当明文长度超过密钥长度时,
key[i]会访问密钥字符串结束符之后的越界内存,读到随机值,偏移计算完全错误。 - 遇到非字母明文字符时没有跳过密钥计数,不符合“非字母字符不消耗密钥位”的规则。
修复方案
- 加密循环前单独定义密钥索引变量
key_idx,初始值为0,提前计算密钥长度key_len避免循环内重复计算。 - 仅当当前处理的字符为字母时,通过
key[key_idx % key_len]取对应密钥字符计算偏移,计算完成后key_idx自增1;非字母字符直接输出,不改动key_idx。
修复后的加密核心代码段:
int key_len = strlen(key); int key_idx = 0; printf("ciphertext: "); for (int i = 0; i < strlen(plaintext); i++) { if (islower(plaintext[i])) { int offset = shift(key[key_idx % key_len]); printf("%c", (plaintext[i] - 'a' + offset) % 26 + 'a'); key_idx++; } else if (isupper(plaintext[i])) { int offset = shift(key[key_idx % key_len]); printf("%c", (plaintext[i] - 'A' + offset) % 26 + 'A'); key_idx++; } else { printf("%c", plaintext[i]); } }
修复后用测试用例验证:密钥baz对应偏移序列为1、0、25,循环使用后加密barfoo得到的结果为caqgon,与预期一致,官方示例运行结果也符合要求。
内容的提问来源于stack exchange,提问作者Yaroslav Yenkala
相关产品推荐
相关产品推荐

