类RC4的文件加密

前置知识

异或运算(以下异或用“⊕”代替)

ABA ⊕ B
000
011
101
110

异或的一些规律
A⊕0=A
B⊕B = 0
满足交换律:A⊕B⊕C = A⊕(B⊕C)
所以:(A⊕B)⊕B = A⊕(B⊕B) = A
有什么用呢?
假设A是原文件,我们随便搞个B文件出来(与A文件等长度),加密过程就是把A变成A⊕B,解密过程就是再异或一遍B,重新得到A,这里的B就是我们的秘钥(key)

主体思路

大致流程

密钥
密钥
原文
密文

思路

由于原文件是不定长的,而秘钥需要与原文件严格等长,我的解决方案是用一个无限长序列(比如斐波拉切数列)代替秘钥:

struct Key{
	char a = 1;
	char b = 1;
	char next(){
		char c = a+b;
		a = b;
		b = c;
		return c;
	}
};

当然这样的话就被写死了,我们可以传个char进来作为a的初始值:

struct Key{
	char a,b;
	Key(char x){
		a = x;
		b = 1;
	}
	char next(){
		char c = a+b;
		a = b;
		b = c;
		return c;
	}
};

不过这样变化还是太少了,只有256种秘钥,那我们稍微搞复杂点:
把a和b该成long long的,就有18446744073709551616(2^64)种秘钥了
然后初始值a由一个字符串决定,这样我们就有模有样地搞出了密码这个功能。
不过虽然可以通过main函数传参的方式实现了拖拽文件到.exe上“用XXXX打开”的效果:
用xxxx打开
用xxxx打开

但是这样输入还是有点麻烦,站内找到
前辈“文件选择器”的实现(原文链接):

string openDlg(){
    TCHAR szBuffer[MAX_PATH] = {0};   
    OPENFILENAME ofn= {0};   
    ofn.lStructSize = sizeof(ofn);   
    ofn.hwndOwner = NULL;   
    ofn.lpstrFilter = ("所有文件(*.*)\0*.*\0");//要选择的文件后缀   
    ofn.lpstrInitialDir =  ("C:\\Program Files");//默认的文件路径   
    ofn.lpstrFile = szBuffer;//存放文件的缓冲区   
    ofn.nMaxFile = sizeof(szBuffer)/sizeof(*szBuffer);   
    ofn.nFilterIndex = 0;   
    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_EXPLORER ;//标志如果是多选要加上OFN_ALLOWMULTISELECT  
    BOOL bSel = GetOpenFileName(&ofn);   
    return szBuffer;
}

缝合上去,这样就稍微“现代”一点了:
在这里插入图片描述

完整代码

#include <bits/stdc++.h>
#include <bits.h>
#include <conio.h>
#include <unistd.h>
using namespace std;

const string passwordSet = " aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ0123456789";

struct Key{
	long long a,b,i;
	Key(string s) {
		a = 0;
		for(int i=0; i<s.size(); i++){
			a *= passwordSet.size();
			a += passwordSet.find(s[i]);
		}
		b = 1;
		i=0;
	}
	char next(){
		long long c = a+b;
		a = b;
		b = c;
		i++;
		char ret = b>>((i%8)*8);
		return ret;
	}
};


string openDlg(){
    TCHAR szBuffer[MAX_PATH] = {0};   
    OPENFILENAME ofn= {0};   
    ofn.lStructSize = sizeof(ofn);   
    ofn.hwndOwner = NULL;   
    ofn.lpstrFilter = ("所有文件(*.*)\0*.*\0");//要选择的文件后缀   
    ofn.lpstrInitialDir =  ("C:\\Program Files");//默认的文件路径   
    ofn.lpstrFile = szBuffer;//存放文件的缓冲区   
    ofn.nMaxFile = sizeof(szBuffer)/sizeof(*szBuffer);   
    ofn.nFilterIndex = 0;   
    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_EXPLORER ;//标志如果是多选要加上OFN_ALLOWMULTISELECT  
    BOOL bSel = GetOpenFileName(&ofn);   
    return szBuffer;
}

int main(int strlen, char *str[]){

	string fileName;
	if (strlen>=2){
		fileName = str[1];
	}else{
		printf("请选择你需要加/解密的文件:\n");
		fileName = openDlg();
	}
	FILE *fp = fopen(fileName.c_str(), "rb+");
	if(fp){
		cout << "请输入加/解密密码:";
		string password;
		while(1){
			cin>>password;
			bool ok = true;
			for (int i = 0; i < password.size(); ++i){
				if(passwordSet.find(password[i])==string::npos){
					ok = false;
				}
			}
			if(ok){
				break;
			}else{
				printf("密码只能由数字和字母大小写组成\n");
				printf("请重新输入加密密码:\n");
			}
		}
		Key key(password);

		int fileSize = max(50L, filelength( fileno(fp) ) );
		cout << "!!!加/解密即将开始,过程中请勿退出此程序,否则将损害该文件!!!" << endl;
		cout << "是否继续(y/n)" << endl;
		while(getch()!='y');

		int progressCounter = 0;
		char ch;
		for(int i=0; i<50; i++){
			putchar('-');
		}putchar('\n');
		while(fread(&ch, sizeof(ch), 1, fp)){
			if(++progressCounter%(fileSize/50) == 0){
				putchar('-');
				int printCnt = printf("(%d/%d)", progressCounter, fileSize);
				while (printCnt--){
					putchar('\b');
				}
			}
			ch ^= key.next();
			fseek(fp, -1L, SEEK_CUR);//向前移位
			fwrite(&ch, sizeof(ch), 1, fp);
			fseek(fp, 0L, SEEK_CUR);//切换读写模式
		}
		fclose(fp);
		cout << endl;
		printf("文件'%s'加/解密完成\n", fileName.c_str());
	}else{
		printf("文件'%s'打开失败\n");
	}
	
	printf("按任意键退出");
	getch();
	return 0;
}

效果

原文件:
在这里插入图片描述
输入密码123456进行加密:
在这里插入图片描述

加密后:
在这里插入图片描述

再解密回来(其实和加密过程就是一样的)
在这里插入图片描述
解密完毕,圣经回来啦
在这里插入图片描述
同样的图片、视频等其他文件也是一样的,加密过就会乱码或者无法打开(不然勒),解密后就能回来(废话):
加密前:
加密前:
加密后:
在这里插入图片描述

P.S.

前辈的代码里”添加到右键菜单”的,缝合进去的话可以实现这样的效果:
直接右键需要加/解密的文件,点击菜单里的“加/解密”
在这里插入图片描述
不过那段代码有点长,缝合进来的话代码量一下子就堆上去了,为了保持代码的简洁性,这里就不加了(这个借口应该还可以)。

如果解密时密码输错了会怎么样?
源文件A经密钥B异或成A⊕B,又经过错误的密钥C异或,也就变成的A⊕B⊕C,要重新得到源文件A,我们需要再对密钥B和C分别异或一遍(由于异或运算满足交换律,所以顺序随意)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值