一、实验目的
(1)了解古典密码
(2)掌握仿射密码的加解密过程和攻击算法并且编程实现
(3)掌握维吉尼亚密码的加解密过程和攻击算法并且编程实现
(4)了解单表与多表密码算法的统计分析
硬件:运行Windows操作系统的计算机
软件:C语言
二、方案设计
实验原理:
1、仿射密码
加密变换:
解密变换:
其中,a,b是密钥,是满足 ,且
的整数,(
表示a与26是互素的。表示a的逆元,即
。
2、维吉尼亚密码
维吉尼亚密码是一种使用凯撒密码的字母表进行加密的算法,属于多表替代密码。该算法通过多个凯撒密码字母表对明文中的每个字母进行替代,使得同一个字母在不同位置可能被替换为不同的密文字母,从而增加了密码的难度。
具体来说,维吉尼亚密码使用一个密钥来确定使用哪个凯撒密码字母表(如图一所示)进行加密。在加密过程中,根据密钥中的每个字母选择对应的凯撒密码字母表,按照顺序依次进行替代。当密钥的字母用完时,重新开始使用第一个字母。
图1 维吉尼亚密码表
例如,假设明文为:
ATTACKATDAWN
选择某一关键词并重复而得到密钥,如关键词为LEMON时,密钥为:
LEMONLEMONLE
对于明文的第一个字母A,对应密钥的第一个字母L,于是使用表格中L行字母表进行加密,得到密文第一个字母L。类似地,明文第二个字母为T,在表格中使用对应的E行进行加密,得到密文第二个字母X。以此类推,可以得到:
明文:ATTACKATDAWN密钥:LEMONLEMONLE密文:LXFOPVEFRNHR
解密的过程则与加密相反。例如:根据密钥第一个字母L所对应的L行字母表,发现密文第一个字母L位于A列,因而明文第一个字母为A。密钥第二个字母E对应E行字母表,而密文第二个字母X位于此行T列,因而明文第二个字母为T。以此类推便可得到明文。
加解密前需先将明密文按照密钥长度进行分组。用数字0-25代替字母A-Z,维吉尼亚密码的加解密可以写成同余的形式:
三、方案实现
1.仿射密码
1.1加密
1.1.1算法流程图
图2 仿射密码加密算法流程图
1.1.2主要函数
int gcd (int a, int b)——传入两个数(这里初始b将传入26),实现利用欧几里得算法计算最大公约数,并返回这两个数的最大公约数。
void randam_a_b (int* a, int*b)——传入作为密钥的a、b,,以时间为随机数种子,产生0-25之间的两个随机数a、b,并且通过函数gcd(欧几里得算法)验证a是否和26互素,若不满足,则重新生成a、b,若满足,返回密钥a、b。
主函数——实现加密并且交互。接受明文消息,调用randam_a_b算法得到a、b,的值,实现加密后,输出明密文和相应的密钥。
1.1.3仿射密码加密算法代码实现
#include "stdio.h"
#include"string.h"
#include"ctype.h"
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#define max 100//假设最大明文长度是100
int gcd(int a, int b) {
if (b == 0) {
return a;
} else {
return gcd(b, a % b);
}
}
void randam_a_b(int* a, int* b) {
srand(time(0));//设置种子
while (1)
{
*a = rand() % 26;
*b = rand() % 26;
if (gcd(*a, 26) == 1)break;//题设中a,b是大于等于0小于等于25的
//计算a是否和26互素,如果不互素,重新生成
}
}
int main() {
int a;
int b;
randam_a_b(&a, &b);//初始化参数
printf("please enter your message!\n");
char message[max] = { 0 };
gets(message);
int len = strlen(message);
int C[max];
for (int i = 0; i < len; i++) {
C[i] = tolower(message[i]) - 'a';
C[i] = (C[i] * a + b) % 26;
}
//交互
printf("your message is:\n%s\n", message);
printf("the value of a is %d\n", a);
printf("the value of b is %d\n", b);
printf("your code is:\n");
for (int i = 0; i < len; i++) {
C[i] = tolower(message[i]) - 'a';
C[i] = (C[i] * a + b) % 26;//仿射密码
printf("%c", C[i] + 'a');
}
}
1.2 解密
1.2.1流程图
图3 仿射密码解密算法流程图
1.2.2主要函数
int extended_gcd(int a, int b, int* x, int* y)——传入需要计算最大公约数的两个数(这里初始b将传入26)和储存系数的地址,实现扩展欧几里得算法,返回最大公约数,并且计算系数x、y。
int mod_inverse(int a, int m)——传入a和26,调用extended_gcd,判断a是否合法(与26互素),若不合法,返回-1,若合法,返回a的逆元。
主函数——实现解密和交互。接受密钥,调用mod_inverse得到,接受密文,运行解密算法,输出相应的明文。
1.2.3仿射密码解密算法代码实现
#include "stdio.h"
#include"string.h"
#include"ctype.h"
#include <unistd.h>
#include <stdlib.h>
#define max 100//假设最大密文长度是100
// 函数声明
int extended_gcd(int a, int b, int* x, int* y);
int mod_inverse(int a, int m);
int main() {
int a, b;
int an;//a关于26的逆元
printf("Please enter the keys 'a' and 'b,separated by a space.:\n");
scanf("%d %d", &a, &b);
an = mod_inverse(a, 26);
if (an == -1) {
printf("the value of a is incorrect.\n");
} else {
printf("please enter your code here\n");
int message[max] = { 0 };
char C[max];
getchar();
gets(C);
int len = strlen(C);
for (int i = 0; i < len; i++) {
message[i] = tolower(C[i]) - 'a';
message[i] = ((message[i] - b) * an) % 26;
while (message[i] < 0)
{
message[i] = message[i] + 26;
}
printf("%c", message[i] + 'a');
}
}
return 0;
}
// 扩展的欧几里得算法
int extended_gcd(int a, int b, int* x, int* y) {
if (a == 0) {
*x = 0;
*y = 1;
return b;
}
int x1, y1;
int gcd = extended_gcd(b % a, a, &x1, &y1);
*x = y1 - (b / a) * x1;
*y = x1;
return gcd;
}
// 求整数a模m的逆
int mod_inverse(int a, int m) {
int x, y;
int gcd = extended_gcd(a, m, &x, &y);
if (gcd != 1) {
return -1; // 不存在逆元
} else {
return (x % m + m) % m; // 确保逆元是正数
}
}
1.3暴力破解算法
1.3.1流程图
图4 仿射密码暴力破解算法流程图
1.3.2主要函数
int gcd(int a, int b)——传入两个数,实现利用欧几里得算法计算最大公约数,返回最大公约数,判断如果与26互素,将值添加入a数组。
int Attack(char* C, int a, int b)——传入密文和猜测的a、b值,运行与解密相同的算法,将猜测的明文输出。
主函数——实现交互和攻击。先调用gcd求解所有可能的a取值,再遍历所有可能的a、b取值,调用Attack输出猜测明文,选择有意义的明文,实现暴力破解。
1.3.3仿射密码暴力破解代码实现
#include "stdio.h"
#include"string.h"
#include"ctype.h"
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define max 100
int a[max];
int count = 0;//用于计数有多少个a满足
char c[max] = { 0 };
int message[max];
int gcd(int a, int b) {
if (b == 0) {
return a;
} else {
return gcd(b, a % b);
}
}
int Attack(char* C, int a, int b) {
// FILE* file;
// 写入数据到文件
// file = fopen("Possible_plaintext_set.txt", "w");
// if (file == NULL) {
// printf("无法打开文件\n");
// return -1;
// }
int len = strlen(C);
for (int i = 0; i < len; i++) {
message[i] = tolower(C[i]) - 'a';
message[i] = ((message[i] - b) * a) % 26;
while (message[i] < 0)
{
message[i] = message[i] + 26;
// fprintf(file, "%c", message[i] + 'a');
}
// fprintf(file, "\n");
printf("%c", message[i] + 'a');
}
printf("\n");
// fclose(file);
}
int main()
{
printf("please enter your code here\n");
gets(c);
//计算所有可能的a,满足a与26互素,存入a数组
for (int i = 1;i < 26;i++) {
if (gcd(i, 26) == 1) {
a[count] = i;
count++;
}
}
int count1 = 0;
for (int i = 0;i < count;i++)
{
for (int j = 1;j <= 25;j++)
{
Attack(c, a[i], j);
count1++;
}
}
printf("%d", count1);
return 0;
}
2.维吉尼亚密码
2.1加密
2.1.1流程图
图5 维吉尼亚密码加密算法流程图
2.1.2主要函数
主函数——实现交互和加密。先将密钥和明文转化为数字方便计算,利用
计算出明文,实现加密,输出加密后的密文。
2.1.3维吉尼亚密码加密算法代码实现
#include "stdio.h"
#include"string.h"
#include"ctype.h"
#include <unistd.h>
#define max 100
int main() {
char key[max];
printf("please enter the key:\n");
gets(key);
char message[max];
int c[max];
printf("please enter the message:\n");
gets(message);
int len1 = strlen(key);
int keynum[len1];
for (int i = 0; i < len1; i++) {
if (isupper(key[i]))key[i] = tolower(key[i]);//转化为小写
keynum[i] = key[i] - 'a';
}
int len2 = strlen(message);
printf("your message is:%s\n", message);
printf("your key is:%s\n", key);
printf("your code is:\n");
for (int i = 0; i < len2; i++) {
if (isupper(message[i])) {
message[i] = tolower(message[i]);
}
c[i] = (message[i] - 'a' + keynum[i % len1]) % 26;
while (c[i] < 0)
{
c[i] += 26;
}
printf("%c", c[i] + 'a');
}
}
2.2解密
2.2.1流程图
图6 维吉尼亚密码解密算法流程图
2.2.2 主要函数
主函数——解密与加密很相似,由
可知,只需将原程序的加运算改成减运算即可。
2.2.3维吉尼亚密码解密算法流代码实现
#include "stdio.h"
#include"string.h"
#include"ctype.h"
#include <unistd.h>
#define max 100
int main() {
char key[max];
printf("please enter the key:\n");
gets(key);
char message[max];
int c[max];
printf("please enter the code:\n");
gets(message);
int len1 = strlen(key);
int keynum[len1];
for (int i = 0; i < len1; i++) {
if (isupper(key[i]))key[i] = tolower(key[i]);//转化为小写
keynum[i] = key[i] - 'a';
}
int len2 = strlen(message);
for (int i = 0; i < len2; i++) {
if (isupper(message[i])) {
message[i] = tolower(message[i]);
}
c[i] = (message[i] - 'a' - keynum[i % len1]) % 26;
while (c[i] < 0)
{
c[i] += 26;
}
printf("%c", c[i] + 'a');
}
}
2.3重合指数法破解
2.3.1流程图
图7 维吉尼亚密码重合指数算法破解流程图
2.3.2主要函数
calculate_coincidence_index(const char* text, int length)——遍历文本中的每个字符,统计每个字母出现的次数。然后,计算每个字母出现次数的平方和,并将其除以文本长度(字符总数)的平方,得到重合指数。
find_key_length(const char* ciphertext, int* key_length)——对于每个可能的密钥长度(从1到MAX),将密文分割成多个子串,每个子串对应一个密钥位置。然后,调用calculate_coincidence_index计算每个子串的重合指数,并取平均值。选择平均重合指数最接近英语重合指数(这里取值是0.0667)的密钥长度作为猜测结果。
decrypt_with_key(const char* ciphertext, const char* key, char* plaintext)——遍历密文中的每个字符,如果字符是字母,则根据密钥中的对应字符进行解密。
brute_force_decrypt(const char* ciphertext, int key_length)——,对于每个可能的密钥(基于猜测的密钥长度,采用递归算法遍历所有可能的字母组合),调用用decrypt_with_key函数来解密密文,并打印出尝试的密钥和对应的猜测明文,由于数量过多,将其写入Possible_plaintext_set.txt文档中。
主函数——接受密文,调用find_key_length函数来猜测密钥长度,然后调用brute_force_decrypt函数来尝试解密密文,在所有猜测的密文里选择有意义的句子,实现破译。
2.3.3 维吉尼亚密码的重合指数破解代码实现
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define MAX_LENGTH 10 // 假设组合的最大长度为10
#define ALPHABET_SIZE 26 // 小写字母的数量
char alphabet[] = "abcdefghijklmnopqrstuvwxyz";
char KEY[MAX_LENGTH + 1]; // 存储组合的字符串,多一个位置用于存储'\0'
#define MAX 10
#define e 0.0667
double calculate_coincidence_index(const char* text, int length) {
int letter_count[26] = { 0 };
double index = 0.0;
for (int i = 0; i < length; i++) {
if (isalpha(text[i])) {
letter_count[tolower(text[i]) - 'a']++;
}
}
for (int i = 0; i < 26; i++) {
index += (double)letter_count[i] * (letter_count[i] - 1);
}
index /= (double)length * (length - 1);
return index;
}
void find_key_length(const char* ciphertext, int* key_length) {
double best_index = 0.0;
int best_length = 0;
for (int len = 1; len <= MAX; len++) {
double total_index = 0.0;
for (int i = 0; i < len; i++) {
char subtext[strlen(ciphertext) / len + 1];
int j = i;
int k = 0;
while (j < strlen(ciphertext)) {
subtext[k++] = ciphertext[j];
j += len;
}
subtext[k] = '\0';
total_index += calculate_coincidence_index(subtext, strlen(subtext));
}
total_index /= len;
if (total_index > best_index) {
best_index = total_index;
best_length = len;
}
}
*key_length = best_length;
}
void decrypt_with_key(const char* ciphertext, const char* key, char* plaintext) {
int key_length = strlen(key);
int j = 0;
for (int i = 0; ciphertext[i] != '\0'; i++) {
if (isalpha(ciphertext[i])) {
int shift = tolower(key[j]) - 'a';
if (islower(ciphertext[i])) {
plaintext[i] = 'a' + (ciphertext[i] - 'a' - shift + 26) % 26;
} else {
plaintext[i] = 'A' + (ciphertext[i] - 'A' - shift + 26) % 26;
}
j = (j + 1) % key_length;
} else {
plaintext[i] = ciphertext[i];
}
}
plaintext[strlen(ciphertext)] = '\0';
}
// void brute_force_decrypt(const char* ciphertext, int key_length) {
// char possible_plaintext[1000];
// char key[MAX + 1];
// for (int i = 0; i < 26; i++) {
// for (int j = 0; j < key_length; j++) {
// key[j] = 'a' + i + j;
// }
// key[key_length] = '\0';
// // decrypt_with_key(ciphertext, KEY, possible_plaintext);
// printf("尝试密钥:%s\n", key);
// printf("猜测明文:%s\n", possible_plaintext);
// }
// }
// 递归函数,用于生成所有可能的字母组合
void brute_force_decrypt(const char* ciphertext, int n, int index) {
FILE* file;
// 写入数据到文件
file = fopen("F:/visiua/my c program/cryptography/Possible_plaintext_set.txt", "a");
if (file == NULL) {
printf("无法打开文件\n");
return;
}
char possible_plaintext[1000];
if (index == n) {
KEY[index] = '\0'; // 字符串结束标志
decrypt_with_key(ciphertext, KEY, possible_plaintext);
fprintf(file, "尝试密钥:%s猜测明文:%s\n", KEY, possible_plaintext);
// printf("尝试密钥:%s\n", KEY);
// printf("猜测明文:%s\n", possible_plaintext);
fclose(file);
return;
}
for (int i = 0; i < ALPHABET_SIZE; i++) {
KEY[index] = alphabet[i];
brute_force_decrypt(ciphertext, n, index + 1);
}
}
int main() {
char ciphertext[1000];
int key_length;
printf("请输入密文:");
fgets(ciphertext, sizeof(ciphertext), stdin);
// 移除换行符
ciphertext[strcspn(ciphertext, "\n")] = 0;
find_key_length(ciphertext, &key_length);
printf("猜测的密钥长度为:%d\n", key_length);
brute_force_decrypt(ciphertext, key_length, 0);
return 0;
}
四、数据分析
1.仿射密码
1.1加密与解密
选择明文:ilovecryptography
运行加密算法可以得到密文:mfyzearsnvyirwnxs
以及随机生成的密钥a、b的值:a=15,b=22
图8 仿射密码密码加密运行结果图
由加密算法得到的密钥以及密文输入到解密算法中,运行解密算法可以得到解密后的明文:ilovecryptography,与原文作对比结果正确。
图9 仿射密码密码解密运行结果图
1.2暴力破解
密文:mfyzearsnvyirwnxs
输入后遍历所有可能的a、b的值,一共300组,
图10 仿射密码密码暴力破解算法运行结果图
在所有猜测的明文中得到有意义的一组ilovecryptography。
图11 仿射密码密码暴力破解算法运行结果图
2.维吉尼亚密码
2.1加密与解密
选择明文:
ilovecryptographyilovecryptographyilovecryptographyilovecryptographyilovecry ptographyilovecryptography
选择密钥:abc
运行加密算法可以得到密文:
imqvferzrtpirbrhzklpxedtyqvohtaqjyjnowgcsapuqgscpiaimqvferzrtpirbrhzklpxedt yqvohtaqjyjnowgcsapuqgscpia
图12 维吉尼亚密码加密算法运行结果图
由加密算法得到的密钥以及密文输入到解密算法中,运行解密算法可以得到解密后的明文:
ilovecryptographyilovecryptographyilovecryptographyilovecryptographyilovecry ptographyilovecryptography
与原文作对比结果正确。
图13 维吉尼亚密码解密算法运行结果图
2.2重合指数法破解
密文:
imqvferzrtpirbrhzklpxedtyqvohtaqjyjnowgcsapuqgscpiaimqvferzrtpirbrhzklpxedt yqvohtaqjyjnowgcsapuqgscpia
图14 维吉尼亚密码重合指数法破解运行结果图
基于猜测长度,遍历所有可能的字母组合,一共组数据。
图15 维吉尼亚密码重合指数法破解运行结果图
在所有猜测的明文中得到有意义的一组:
ilovecryptographyilovecryptographyilovecryptographyilovecryptographyilovecry ptographyilovecryptography
实现破解。
五、思考与总结
1.仿射密码中用到了哪些运算?分别实现什么功能?请简述。
答:
仿射密码中使用了加法和乘法这两种基本运算。
乘法运算:在仿射密码中,乘法用于实现明文字母到密文字母的替换。加密时,将明文对应的数字与密钥a相乘,再进行模26运算得到密文数字。解密时,需要使用a的乘法逆元来进行反变换,即将密文数字与a的乘法逆元相乘,再进行适当的计算以恢复出明文数字。
加法运算:加法在仿射密码中用于实现明文字母的位移。加密时,乘以a后的结果再加上密钥b,然后取模26得到最终的密文数字。在解密过程中,从密文数字中减去b,以便将其恢复到原始的数值位置上。
2.请简要分析仿射密码和维吉尼亚密码的安全性。
答:
仿射密码的安全性相对较低,而维吉尼亚密码的安全性相对较高但也存在局限。
仿射密码的安全性分析:
由于密钥空间不大,一共300组,在唯时密文易受暴力破解。而且其加密方法简单,有明密文对时,它容易受到频率分析和线性密码分析等攻击手段的威胁。
维吉尼亚密码的安全性分析:
密钥长度:影响维吉尼亚密码是由多个恺撒密码组成的多表替代密码,如果使用足够长的密钥,则可以提高其安全性。
算法原理的复杂性:维吉尼亚密码通过使用不同的恺撒密码对应不同位置的明文字母进行加密,相较于单一恺撒密码更为复杂。其加密和解密过程涉及到对每一位明文(或密文)根据密钥进行位移操作,这增加了破解难度。
3.实验过程中还遇到了什么问题,如何解决的?通过该实验有何收获?
本次实验的问题主要集中在如何实现重合指数破解:
问题1:如何猜测密钥长度?
答:
由于密钥是循环使用的,在使用重合指数来估计密钥长度时,我们假设密文是由一个维吉尼亚密码加密的,其中密钥是周期性的,即密钥长度是固定的。如果我们能够找到一个密钥长度,使得密文按照这个长度分割成多个子串后,每个子串的重合指数接近自然语言的重合指数,那么这个密钥长度就很可能是正确的。
具体来说,我们遍历所有可能的密钥长度,对于每个长度,我们将密文分割成多个子串,每个子串对应一个密钥位置。然后,我们计算每个子串的重合指数,并取平均值。如果这个平均值接近自然语言的重合指数(对于英语来说大约是0.0667),那么这个密钥长度就被认为是好的。
在选择密钥长度时,我们以平均重合指数接近自然语言的重合指数为标准。如果某个密钥长度的平均重合指数最接近自然语言的重合指数,我们就选择这个长度作为密钥长度的估计。
这种方法的原理是,当密文按照正确的密钥长度分割时,每个子串将主要包含来自同一密钥位置的字母,因此每个子串的字母频率分布将更接近原始语言的字母频率分布,从而导致更高的重合指数。如果密钥长度不正确,那么子串中的字母将来自不同的密钥位置,其频率分布将更加均匀,导致较低的重合指数。
问题2:如何在密钥长度不定的情况下遍历所有可能的密钥组合?
答:
原思想是通过for循环遍历,但是密钥长度是后知的,即只有在猜测完成之后才知道密钥的长度,因此,在编程时,我们不能确定需要多少层for循环,并且,过多的for循环会带来很大的代码冗余。后来采用递归的方式实现在后知长度下的组合遍历,传入两个参数,一个是现阶段已经生成的字符串,一个是生成的字符个数,但生成个数和猜测key的长度相等时,我们将猜测密钥传入解密算法,实现在密钥后知的情况下遍历所有可能的密钥组合。
收获
通过本次实验,我不仅深入掌握了古典密码学的基本理论,还通过亲身实践,熟练运用了仿射密码的加解密方法。同时,我学会了如何利用暴力攻击算法进行破解,并成功实现了相应的编程任务。此外,我也对维吉尼亚密码的加解密过程有了更为深入的了解,初步了解了重合指数攻击算法,并顺利完成了相关的编程实践。