目录
1、原理
Playfair密码是一种使用一个关键词方格来加密字符对的加密算法,是一种多表代换的对称加密技术。
它依据一个5*5的正方形组成的密码表来编写,密码表里排列有25个字母。如果一种语言字母超过25个,可以去掉使用频率最少的一个。如,法语一般去掉w或k,德语则是把i和j合起来当成一个字母看待。英语中z使用最少,可以去掉它。可以很好的防御频率分析法的攻击。
同时Playfair密码对明文和密文有一定的要求:在加密之前首先需要整理明文,将明文中两个字母组成一组,如果两个字母相同则分成两组,在每组的后面加字母X或Q。如果明文字母个数是奇数,在最后一个字母之后加字母X或Q。密文的字母个数一定是偶数,任意两个同组的字母都不会相同。
Playfair密码加密的一般过程:
1、输入明文
2、输入密钥得到密码表
3、整理明文
4、加密明文得到密文
首先通过将密钥字母放在密码表的头部(第一行及之后或第一列及之后),之后将其他字母按字母顺序填充到密码表中以得到密码表。由于英文字母有26个,所以一般会将i和j放在同一个位置上。之后整理明文,最后加密明文。
加密规则:
-
若m1,m2在同一行,对应密文c1,c2分别是紧靠m1,m2 右端的字母。其中第一列被看做是最后一列的右方。
-
若m1,m2在同一列,对应密文c1,c2分别是紧靠m1,m2 下方的字母。其中第一行被看做是最后一行的下方。
-
若m1,m2不在同一行,不在同一列,则c1,c2是由m1,m2确定的矩形的其他两角的字母(横向替换或者纵向替换)。
解密就是加密的逆过程。
-
若c1,c2在同一行,对应明文m1,m2分别是紧靠c1,c2 左端的字母。其中第一列被看做是最后一列的右方。
-
若c1,c2在同一列,对应明文m1,m2分别是紧靠c1,c2 上方的字母。其中第一行被看做是最后一行的下方。
-
若c1,c2不在同一行,不在同一列,则m1,m2是由c1,c2确定的矩形的其他两角的字母(横向替换或者纵向替换)。
2、流程图
3、编程实现
难点:
1、如何根据密钥生成密码表
2、如何将密文解密恢复成明文
难点1:
由于密码表是25个字母,可以将j和i都看作字母i,即将明文中的j全部看作是i。
对于密码表,先将密钥字母填入密码表,再按字母顺序将未填入的字母填入到密码表中。
void initkey() {
cout << endl << "请输入密钥以生成密码表:" << endl;
string key = "";//输入密钥
cin >> key;
int i = 0;
int j = 0;
for (char ch : key) {
if (ch == ' ' || vis[ch - 'a'])continue;
if (ch == 'j')ch = 'i';
m.insert(myMap::value_type(ch - 32, { i,j }));//不知道哪根筋抽了用map来存储密码表
vis[ch - 'a'] = 1;
i += j == 4;
j = (j + 1) % 5;
}
for (int k = 0; k < 26; k++) {
if (k == 9)continue;
if (!vis[k]) {
m.insert(myMap::value_type(k + 'A', { i,j }));
vis[k] = 1;
i += j == 4;
j = (j + 1) % 5;
}
}
//for (auto &temp : m) {
// cout << temp.first << " " << temp.second.first << " " << temp.second.second << endl;
//}
cout << "成功生成密码表" << endl;
}
难点2:
密文恢复成明文就是明文加密成密文的逆过程,但是由于明文中的j变成了i,如果明文为奇数或者相邻两个字母相同时,还会加X,所以最后得到的明文并不是真正的明文,需要经过一些处理,在这里省略了处理的过程(其实是不会处理),毕竟主要还是学习密码的思想。
具体代码如下:
默认输入为小写字母不带空格
主函数
#include"Playfair.h"
int main() {
while (1) {
show(); //菜单界面
keyDown(); //按键处理
system("pause");
system("cls");
}
}
Playfair.h
#pragma once
#include<cstdio>
#include<iostream>
#include<string>
#include<Windows.h>
#include<unordered_map>
using namespace std;
void init();
void initkey();
void show();
void keyDown();
void readFile();
void saveFile();
void encrypt();
void decrypt();
Playfair.cpp
#include "Playfair.h"
string finalStr;
string fileStr;
typedef unordered_map<char, pair<int, int>> myMap;//密码表
myMap m;//5*5密码表 大写
int vis[26];//生成密码表标记 去掉j
void init() {
finalStr = "";
fileStr = "";
m = myMap();
memset(vis, 0, sizeof(vis));
}
void show()
{
cout << "****************Playfair密码****************" << endl;
cout << "\t\t1.加密文件" << endl;
cout << "\t\t2.解密文件" << endl;
cout << "\t\t3.退出" << endl;
cout << "******************************************" << endl;
}
void keyDown()//按键处理
{
int userkey = 0;
cin >> userkey;
switch (userkey) {
case 1:
cout << "-----------------加密文件-----------------" << endl;
readFile();
initkey();
encrypt();
saveFile();
init();
break;
case 2:
cout << "-----------------解密文件-----------------" << endl;
readFile();
initkey();
decrypt();
saveFile();
init();
break;
case 3:
exit(0);
break;
}
}
void readFile()//读取文件
{
cout << "请输入文件名:" << endl;
string fileName;
cin >> fileName;
FILE* fp = fopen(fileName.c_str(), "r+");
if (fp == nullptr) {
cout << "未找到相关文件" << endl;
return;
}
else {
cout << "成功打开文件" << endl;
}
char ch;
int pos = 0;
while ((ch = fgetc(fp)) != EOF) {
fileStr += ch;
}
cout << endl << "明文为:" << endl;
cout << fileStr << endl;
fclose(fp);
}
void saveFile()//保存文件
{
string fileName;
cout << endl << "请输入要保存信息的文件名:" << endl;
cin >> fileName;
FILE* fp = fopen(fileName.c_str(), "w+");
if (fp == nullptr) {
cout << endl << "保存文件失败" << endl;
return;
}
else {
cout << endl << "保存成功" << endl;
}
fprintf(fp, "%s", finalStr.c_str());
fclose(fp);
}
void initkey() {
cout << endl << "请输入密钥以生成密码表:" << endl;
string key = "";//输入密钥
cin >> key;
int i = 0;
int j = 0;
for (char ch : key) {
if (ch == ' ' || vis[ch - 'a'])continue;
if (ch == 'j')ch = 'i';
m.insert(myMap::value_type(ch - 32, { i,j }));
vis[ch - 'a'] = 1;
i += j == 4;
j = (j + 1) % 5;
}
for (int k = 0; k < 26; k++) {
if (k == 9)continue;
if (!vis[k]) {
m.insert(myMap::value_type(k + 'A', { i,j }));
vis[k] = 1;
i += j == 4;
j = (j + 1) % 5;
}
}
//for (auto &temp : m) {
// cout << temp.first << " " << temp.second.first << " " << temp.second.second << endl;
//}
cout << "成功生成密码表" << endl;
}
char researchkey(int x, int y) {//在密码表里面查找明文字母对应的密文字母
for (myMap::iterator it = m.begin(); it != m.end(); it++) {
if (it->second.first == x && it->second.second == y) {
return it->first;
}
}
}
void encrypt() {//整理明文并加密
string temp;//存放大写明文
for (int i = 0; i < fileStr.size(); i++) {// 通过这个for循环去除非法字符以及将明文字母变为大写,将'J'变为'I'
if ((fileStr[i] >= 'a' && fileStr[i] <= 'z') || (fileStr[i] >= 'A' && fileStr[i] <= 'Z')) {
if (fileStr[i] == 'j' || fileStr[i] == 'J') {
temp += 'I';
}
else {
temp += fileStr[i] >= 'a' ? fileStr[i] - 'a' + 'A' : fileStr[i];
}
}
}
if (temp.size() % 2 == 1) {
temp += 'X';
}
//cout << temp << endl;
int j1 = 0, j2 = 0, l1 = 0, l2 = 0; //两个字符的坐标
char p1, p2; //两个字符
for (int i = 0; i < temp.size(); i += 2) {
p1 = temp[i];
p2 = temp[i + 1];
j1 = m[p1].first;
l1 = m[p1].second;
j2 = m[p2].first;
l2 = m[p2].second;
if (p1 != p2) {
//printf("%c %c %d %d %d %d\n",p1,p2,j1,l1,j2,l2);
if (j1 == j2) {
finalStr += researchkey(j1, (l1 + 1) % 5);
finalStr += researchkey(j2, (l2 + 1) % 5);
}
else if (l1 == l2) {
finalStr += researchkey((j1 + 1) % 5, l1);
finalStr += researchkey((j2 + 1) % 5, l2);
}
else if (j1 != j2 && l1 != l2) {
finalStr += researchkey(j1, l2);
finalStr += researchkey(j2, l1);
}
}
else {
//p1p3 p2p3
char p3 = 'X';
int j3, l3;
j3 = m[p3].first;
l3 = m[p3].second;
if (j1 == j3) {
finalStr += researchkey(j1, (l1 + 1) % 5);
finalStr += researchkey(j3, (l3 + 1) % 5);
}
else if (l1 == l3) {
finalStr += researchkey((j1 + 1) % 5, l1);
finalStr += researchkey((j3 + 1) % 5, l3);
}
else if (j1 != j3 && l1 != l3) {
finalStr += researchkey(j1, l3);
finalStr += researchkey(j3, l1);
}
if (j2 == j3) {
finalStr += researchkey(j2, (l2 + 1) % 5);
finalStr += researchkey(j3, (l3 + 1) % 5);
}
else if (l2 == l3) {
finalStr += researchkey((j2 + 1) % 5, l2);
finalStr += researchkey((j3 + 1) % 5, l3);
}
else if (j3 != j2 && l3 != l2) {
finalStr += researchkey(j2, l3);
finalStr += researchkey(j3, l2);
}
}
}
cout << endl << "生成的密文为:" << endl;
cout << finalStr << endl;
}
void decrypt() {//解密密文
string temp = fileStr;
int j1 = 0, j2 = 0, l1 = 0, l2 = 0; //两个字符的坐标
char p1, p2; //两个字符
for (int i = 0; i < temp.size(); i += 2) {
p1 = temp[i];
p2 = temp[i + 1];
j1 = m[p1].first;
l1 = m[p1].second;
j2 = m[p2].first;
l2 = m[p2].second;
if (j1 == j2) {
finalStr += researchkey(j1, (l1 + 4) % 5) + 32;
finalStr += researchkey(j2, (l2 + 4) % 5) + 32;
}
else if (l1 == l2) {
finalStr += researchkey((j1 + 4) % 5, l1) + 32;
finalStr += researchkey((j2 + 4) % 5, l2) + 32;
}
else if (j1 != j2 && l1 != l2) {
finalStr += researchkey(j1, l2) + 32;
finalStr += researchkey(j2, l1) + 32;
}
}
cout << endl << "明文为:" << endl;
cout << finalStr << endl;
}
4、总结
Playfair密码作为一种多表代换密码,不同位置上相同的字母会被加密成不一样的密文,可以有效的防止频率分析法的攻击。不过在使用Playfair之前双方需要进行一些规定,比如密码表是去掉z还是将i和j放到一个位置上,是加字母Q还是加字母X等等。所以没有必要去纠结一些细节,现在也不会再使用这样的古典密码算法,学习密码算法的思想才是关键。