【密码学】破解维吉尼亚密码(C++代码实现)

问题简述

维吉尼亚密码是使用一系列凯撒密码组成密码字母表的加密算法,属于多表密码的一种简单形式。

在一个凯撒密码中,字母表中的每一字母都会作一定的偏移,例如偏移量为3时,A就转换为了D、B转换为了E……而维吉尼亚密码则是由一些偏移量不同的凯撒密码组成。

为了生成密码,需要使用表格法。这一表格(如图所示)包括了26行字母表,每一行都由前一行向左偏移一位得到。具体使用哪一行字母表进行编译是基于密钥进行的,在过程中会不断地变换。

例如,假设明文为:

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,维吉尼亚密码的加密文法可以写成同余的形式:

解密方法则能写成:

维吉尼亚由于其加密的复杂性和密钥的不确定性曾经一度被称为“不可破解的密码”。

破解实现

我选择的破解方法是重合指数法。

①我们先编写一个能把密文按照某个步长进行分组的函数makeGroup(),参数beforeText为密文内容,参数step为分组的步长。

1.	string* makeGroup(string beforeText, int step) {  
2.	    string* afterText = new string[step];  
3.	    long length = beforeText.length();  
4.	    for (long i = 0; i < length; i++) {  
5.	        afterText[i % step] += beforeText[i];  
6.	    }  
7.	    return afterText;  
8.	};  

②接下来我们就可以开始实质的第一步,那就是先推测密钥的长度,我们按照不同步长对密文进行分组,随后计算出对应的重合指数(公式如下)并打印到控制台。

 

//求密钥长度,根据不同密钥长度对应的重合指数,正常文本为0.0635左右
int GetKeyLength(string cipherText) {
	//尝试1-30的密钥长度
	double smallest = 1;
	int keyLength = 0;
	for (int i = 1; i < 30; i++) {
		double index = 0;
		string* afterText = makeGroup(cipherText, i);
		for (int j = 0; j < i; j++) {
			double num = 0;
			//ASCII码97-122对应a-z
			for (int p = 97; p < 123; p++) {
				int count = 0;
				for (int q = 0; q < afterText[j].length(); q++) {
					if (afterText[j][q] == p) {
						count++;
					}
				}
				num += (double(count) / afterText[j].length()) * ((double(count) - 1) / (afterText[j].length() - 1));
			}
			index += num;
		}
		cout <<"密钥长度为"<<i<<"时,重合指数="<< index / i << endl;
	}
	return keyLength;
}

运行该函数即可得到如下:

所以经过分析,密钥的长度应该是8的倍数。

③接下来我们需要将推测的密钥长度代入,按照推测的密钥长度对明文分组,对每组的字母进行频率分析,进而得到该密钥长度对应的密钥。

//频率分析,确定e对应的密钥
string frequencyCount(string cipherText, int step) {
	string* afterText = makeGroup(cipherText, step);
	string key;
	for (int i = 0; i < step; i++) {
		//ASCII码97-122对应a-z
		int E = 97;
		double max = 0;
		for (int j = 97; j < 123; j++) {
			int count = 0;
			for (int k = 0; k < afterText[i].length(); k++) {
				if (afterText[i][k] == j) {
					count++;
				}
			}
			cout << char(j) << "的频率=" << double(100 * count) / afterText[i].length() << endl;
			if ((double(100 * count) / afterText[i].length()) > max) {
				max = (double(100 * count) / afterText[i].length());
				E = j;
			}
		}
		key += char(E);
	}
	return key;
}

 

控制台得到的输出如下,我们找到频率最高的两个或者三个,它们很有可能就是密钥中e对应的字母。

可知e最有可能变成了p或t,通过查表逆推得到对应的密钥字母为L或者P,保险起见两种可能在解密时都要试一试。

当然,我们还可以把查表这个过程变成程序来实现,简单高效,避免查表的繁琐。

//获取密钥
string GetKey(string cipherText, int keyLength) {
	string key = frequencyCount(cipherText, keyLength);
	for (int i = 0; i < key.length(); i++) {
		key[i] = (key[i] - 101 + 26) % 26 + 'a';
	}
	cout << "根据频率分析法密钥最可能为:" << key << endl;
	return key;
}

通过这种方法我们得到了密钥的几种可能的形式,如lagrange、lagrwnve等等,其实现在就可以猜测密钥大概率就是lagrange,因为只有lagrange有实际意义。

④既然得到了密钥,下一步就是解密了,解密的方法就是按照密钥长度对密文进行分组,然后按照对应的密钥字进行查表逆推即可,我们还是把它写成了程序。

//密文解密,参数key为密钥
void decrypt(string cipherText, string key) {
	//分组
	string* afterText = makeGroup(cipherText, key.length());
	//对照密码表倒推替换
	for (int i = 0; i < key.length(); i++) {
		for (int j = 0; j < afterText[i].length(); j++) {
			afterText[i][j] = (afterText[i][j] - key[i] + 26) % 26 + 'a';
		}
	}
	//组装明文内容
	string plainText;
	for (int j = 0; j < afterText[0].length(); j++) {
		for (int i = 0; i < key.length() && j < afterText[i].length(); i++) {
			plainText += afterText[i][j];
		}
	}
	cout << plainText << endl;
}
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mitch311

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值