简单文件加密解密系统
题目要求:
这个项目的目标是构造一个简单的文件加解密系统。
我们将采用一种非常简单的策略实现文件的加解密。使用方式为:
可执行文件名 encrypt 码本文件名 输出文件名
可执行文件名 decrypt 码本文件名 输出文件名
(可执行文件是对c++源程序进行编译运行所生成的文件。)
为实现加解密, 我们首先需要一个码本文件。一个基本的码本文件包含26个字母,a~z 打乱顺序的结果,比如:qwertyuiopasdfghjklzxcvbnm。码本文件的第一个字母表示 a 应当替换成的字符(该案例中,a 应替换成 q),第二个字母表示 b 应当替换成的字符(该案例中,b 应替换成 w),依此类推。
在获取了这个码本文件后,encrypt 需要对输入文件进行加密,即将相应的字母进行替换。比如,如果输入文件中包含了如下的内容:
hello world
基于前文所述的码本文件,系统应当输出:
itssg vgksr
注意:在替换时,会有一些不属于 a~z 的字符,比如输入中的空格,这些字符应原样输出。
相应的,decrypt 应当接收码本文件与加密后的文件作为输入,以解密后的文件作为输出。
扩展一:
现在,我们将整个系统进行扩展一下:码本文件将包含 256 个数字,为 0~255 打乱顺序的结果,比如:
123 234 1 0 255…
其中的第一个数 123 表示字符(char)0 应当被替换为(char)123,第二个字符表示(char)1应当被替换为(char)234,依此类推。
基于这个码本文件,修改 encrypt 与 decrypt,使得它可以为任意文件进行加密解密。
注意:如果要支持任意文件的加密解密,那么就需要使用二进制的方式打开文件。
扩展二:
尝试加密解密一个可执行文件(比如你的 C++开发环境中包含的可执行文件),看它是否可以正常工作。
注意:在尝试之前,一定要做好文件备份,避免因你的程序出错而导致原始的可执行文件无法运行。
功能实现:
int main(int argc, char* argv[])
{
bool flag = 0; //码本标志符(26个字母:0, 256个数字:1)
vector<string> code = JudgeAndRead_code(argv[2], flag); //判断并读取码本文件数据
cout << argv[2] << " 内容(" << code.size() << "):" << endl;
for (auto c : code) { //显示码本文件内容
cout << c << ' ';
}
cout << "\n\n\n";
string data = Read_data(argv[3]); //读取输入文件数据
cout << argv[3] << " 内容(" << data.size() << "):" << endl;
cout << data << "\n\n\n";
ofstream out_data(argv[4], ios::binary); //输出文件流
cout << argv[4] << " 内容(";
string opt = argv[1];
if (opt == "encrypt") {
if (flag == 0) { //码本为26个字母
encrypt(data, code[0], out_data); //编码
}
else {
encrypt_num(data, code, out_data); //解码
}
}
else if (opt == "decrypt") {
if (flag == 0) { //码本为256个数字
decrypt(data, code[0], out_data);
}
else {
decrypt_num(data, code, out_data);
}
}
else
cout << argv[1] << "无法识别\n文件编码请输入:encrypt,文件解码请输入:decrypt" << endl;
return 0;
}
本程序主要设计如上,由于需要在一个程序中需要兼顾基本功能和扩展功能,而二者的码本又相差甚远,故首先对码本文件进行判断:若码本为 26 个字母,则 flag 为 0;若码本为 256 个数字,则 flag 为 1。然后根据 falg 选择执行对应的编码与解码函数。主要函数如下所示。
1. 读取并判断码本文件
vector<string> JudgeAndRead_code(const string& path, bool& flag)
{
ifstream fin;
vector<string> code;
fin.open(path, ios::binary);
if (!fin.is_open()) {
cout << " 无法打开文件!" << endl;
exit(0);
}
string buff;
while (fin >> buff) { // fin >> 形式的读取会忽略任何白色空格字符
code.push_back(buff);
}
for(char ch : code[0]){
if(isdigit(ch)){
flag = 1;
break;
}
}
fin.close();
return code;
}
如上,使用一个 vector容器来存储码本文件数据。在读取数据时使用 “>>” 操作符形式进行读取,它在读取过程中会自动忽略任何白色空格字符(空格、制表符、换行符等),且遇到白色空格时会停止识别,从而达到将每个码本数据完整取出而不参杂任何其他字符(如:\r \n)。
判断码本文件类型只需判断取出数据第一个字符是否是数字,若是数字则属于 256 个数字的码本,flag = 1。否则,码本属于 26 个字母的码本,flag = 0。
2. 26 个字母码本对应的编码与解码
void encrypt(const string& data, const string& code, ofstream& out)
{
string res;
for (auto ch : data) {
if (ch >= 'a' && ch <= 'z') {
int num = ch - 'a';
res.push_back(code[num]);
}
else if (ch >= 'A' && ch <= 'Z') {
int num = ch - 'A';
res.push_back(toupper(code[num]));
}
else
res.push_back(ch);
}
cout << res.size() << "):" << '\n' << res << endl;
out << res;
}
void decrypt(const string& data, const string& code, ofstream& out) {
string res;
for (auto ch : data) {
if (ch >= 'a' && ch <= 'z') {
int pos = code.find(ch);
char c = 'a' + pos;
res.push_back(c);
}
else if (ch >= 'A' && ch <= 'Z') {
int pos = code.find(tolower(ch));
char c = 'A' + pos;
res.push_back(c);
}
else
res.push_back(ch);
}
cout << res.size() << "):" << '\n' << res << endl;
out << res;
}
对于 26 个字母的码本文件,其文本内容如下:
plokimjunhybgtvfrcdexswzaq
则在读取码本文件后,其全部数据内容只占vector容器的第一个单元,即data[0]。故编码与解码只需传递 data[0]给形参 code 即可。
26 个字母编码过程是使用码本中对应位置字母替换 a ~ z,例如: p 替换 a , l 替换 b。则只需一一判断输入文件数据,若字符 ch 为 a,则 ch - ‘a’ 得到的数字 num 就是其要替换的字符在码本中的位置,code[num]就是所替换的字符(对于大写, num = ch - ‘A’)。
解码是编码的反过程,先使用 find 函数查找到字符 ch 在 code 中的位置 pos,然后 pos +‘a’即可得到原本的字符(对于大写,字符 c = pos + ‘A’)。
3. 256 个数字码本对应的编码与解码
void encrypt_num(const string& data, const vector<string>& code, ofstream& out)
{
string res;
for (unsigned char ch : data) {
int num = ch;
char c = stoi(code[num]); //stoi将字符串转化为int型数字
res.push_back(c);
}
cout << res.size() << "):" << '\n' << res << endl;
out << res;
}
void decrypt_num(const string& data, vector<string>& code, ofstream& out)
{
string res;
for (unsigned char ch : data) {
int num = ch;
string str = to_string(num);
vector<string>::iterator iter = find(code.begin(), code.end(), str); //在code中查找str位置,返回为迭代器类型
char c = iter - code.begin();
res.push_back(c);
}
cout << res.size() << "):" << '\n' << res << endl;
out << res;
}
对于 256 个数字的码本文件,其文本内容如下:
205 240 162 226 237 136 227 152 188 102 193 124 244 219 71 150 246 108 94 17 175 47
64 66 65 7 222 76 138 99 77 81 192 155 56 39 25 3 151 101 61 68 172
167 24 160 97 19 179 180 198 33 21 224 197 15 120 196 239 44 83 38 46 103
84 95 213 200 170 23 70 149 163 148 251 186 211 189 100 42 191 52 72 53 67
215 245 207 144 146 45 181 93 147 153 80 116 114 49 4 88 16 90 127 255 202
85 55 178 221 229 10 13 105 177 58 0 60 234 34 50 141 242 156 134 28 176
118 48 1 210 6 187 119 243 128 232 166 5 236 137 73 253 217 214 212 69 111
79 184 51 87 216 135 168 158 249 92 35 182 107 235 126 31 2 254 133 233 18
201 174 8 159 82 37 113 9 63 41 104 59 26 231 145 89 122 110 142 86 164
29 30 169 154 223 112 140 78 75 220 238 54 143 195 131 130 228 139 121 109 12
161 125 57 74 190 250 171 209 199 40 123 204 247 183 173 98 185 208 248 241 14
27 203 218 230 43 96 22 194 129 11 115 165 32 132 91 36 157 117 225 206 20
106 62 252
读取码本文件后,vector code 每个单元为一个数字字符,共 256 个单元。
256 个数字编码过程是使用码本中数字所对应的字符去替换 0~255 字符,例如:char(205)替换 char(0), char(240) 替换 char(1)。 则取出输入文件的一个字符 ch, int num = ch; 就得到了其对应的数字(即第几个字符),code[num]为其需要转化为的字符对应的数字,由于code 中数字是以字符串形式存在,使用 stoi 将其转化为 int 型数据,然后赋给 char c,即可得到转化后的字符。
解码过程相反,使用 int num = ch 后,将其转化为字符串形式,然后再 code 中查找其位置(iter - code.begin()), 其位置数字对应字符即为原本的字符(char c = iter - code.begin())。
整体代码:
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <vector>
#include <algorithm>
using namespace std;
//读取输入文件数据
string Read_data(const string& path)
{
ifstream fin;
fin.open(path, ios::binary);
if (!fin.is_open()) {
cout << " 无法打开该文件!" << endl;
exit(0);
}
char ch;
string data;
while (fin.get(ch)) { //get()函数不忽略白色空格字符(空格、制表符、换行符)
data.push_back(ch);
}
fin.close();
return data;
}
//读取并判断码本文件数据
vector<string> JudgeAndRead_code(const string& path, bool& flag)
{
ifstream fin;
vector<string> code;
fin.open(path, ios::binary);
if (!fin.is_open()) {
cout << " 无法打开文件!" << endl;
exit(0);
}
string buff;
while (fin >> buff) { // fin >> 形式的读取会忽略任何白色空格字符
code.push_back(buff);
}
for(char ch : code[0]){
if(isdigit(ch)){
flag = 1;
break;
}
}
fin.close();
return code;
}
// 26 字母码本的编码解码
void encrypt(const string& data, const string& code, ofstream& out)
{
string res;
for (auto ch : data) {
if (ch >= 'a' && ch <= 'z') {
int num = ch - 'a';
res.push_back(code[num]);
}
else if (ch >= 'A' && ch <= 'Z') {
int num = ch - 'A';
res.push_back(toupper(code[num]));
}
else
res.push_back(ch);
}
cout << res.size() << "):" << '\n' << res << endl;
out << res;
}
void decrypt(const string& data, const string& code, ofstream& out) {
string res;
for (auto ch : data) {
if (ch >= 'a' && ch <= 'z') {
int pos = code.find(ch);
char c = 'a' + pos;
res.push_back(c);
}
else if (ch >= 'A' && ch <= 'Z') {
int pos = code.find(tolower(ch));
char c = 'A' + pos;
res.push_back(c);
}
else
res.push_back(ch);
}
cout << res.size() << "):" << '\n' << res << endl;
out << res;
}
// 256 个数字码本的编码解码
void encrypt_num(const string& data, const vector<string>& code, ofstream& out)
{
string res;
for (unsigned char ch : data) {
int num = ch;
char c = stoi(code[num]); //stoi将字符串转化为int型数字
res.push_back(c);
}
cout << res.size() << "):" << '\n' << res << endl;
out << res;
}
void decrypt_num(const string& data, vector<string>& code, ofstream& out)
{
string res;
for (unsigned char ch : data) {
int num = ch;
string str = to_string(num);
vector<string>::iterator iter = find(code.begin(), code.end(), str); //在code中查找str位置,返回为迭代器类型
char c = iter - code.begin();
res.push_back(c);
}
cout << res.size() << "):" << '\n' << res << endl;
out << res;
}
int main(int argc, char* argv[])
{
bool flag = 0; //码本标志符(26个字母:0, 256个数字:1)
vector<string> code = JudgeAndRead_code(argv[2], flag); //判断并读取码本文件数据
cout << argv[2] << " 内容(" << code.size() << "):" << endl;
for(auto c : code){ //显示码本文件内容
cout << c <<' ';
}
cout <<"\n\n\n";
string data = Read_data(argv[3]); //读取输入文件数据
cout << argv[3] << " 内容(" << data.size() << "):" << endl;
cout << data << "\n\n\n";
ofstream out_data(argv[4], ios::binary); //输出文件流
cout << argv[4] << " 内容(";
string opt = argv[1];
if (opt == "encrypt"){
if(flag == 0){ //码本为26个字母
encrypt(data, code[0], out_data); //编码
}
else{
encrypt_num(data, code, out_data); //解码
}
}
else if (opt == "decrypt"){
if(flag == 0){ //码本为256个数字
decrypt(data, code[0], out_data);
}
else{
decrypt_num(data, code, out_data);
}
}
else
cout << argv[1] << "无法识别\n文件编码请输入:encrypt,文件解码请输入:decrypt" << endl;
return 0;
}